Needlr is my opinionated framework that I use for scanning assemblies for types to register for dependency injection. Like Autofac and Scrutor, you can automatically register types that you can resolve from the dependency container later once the IServiceProvider has been built!
View Transcript
I don't have to do this every time I want to add functionality. I simply just define a plugin like this. Look, I'm not against extension methods in general, but personally, I just can't stand them when it comes to setting up dependency injection and having the start of your application be littered with all of these extension methods to go hook things up. So, I set out to go package the approach that I've been using in a lot of my plug-in based applications to go make this a lot easier. I'm going to be walking through an example of setting up something in Needler, which is my dependency injection initialization framework that's very opinionated. We'll walk through what that looks like versus an ASP.NET Core by default. All right. So, to kick things off, I just have the very simple sample weather application that we get when we
go make a new ASP.NET on net core application. What we generally find in these applications is that over time when we are working with things that we want to wire up into dependency injection, we have to go to the builder and then we say builder services. Well, you can already see co-pilot trying to do some of it for me. But we do this kind of pattern where we start adding in new extension methods that we can tack in. But what happens over time is that as you're adding more and more functionality to your application, you kind of have this thing going on. I'm just not a fan of it. Obviously, you're not going to have the exact same extension method like this piling up. I'm just trying to show you that you keep having all of these things getting tacked on. And the reason that
that happens is because a lot of the time we're doing something like add scoped or you're adding a singleton, right? So, you end up having this kind of thing where you have your type added here. And you keep doing this because you have more and more things are getting added into your application and you're using dependency injection. So you need to get it all registered in one spot. For me, this is a big no no because I don't like adding new functionality into my applications and then having to go to one core spot to start piling all of this stuff in. What people do is they take this code and then they pull it into an extension method. So that instead of having, you know, these five lines that I have highlighted, it just looks like instead of that you'd have builder.services services dot add
cool types and you'd have this kind of extension method that you'd go create right collapses down the amount of code but again like it doesn't solve the problem it just keeps shrinking it and then you keep having that problem come up again personally for me I build a lot of plug-in architectures so that means that in my different projects that I have and this could still happen within the same project I like kind of isolating things into different modules and then personally I like it being dynamic ically discovered and that ends up meaning that for all of the registrations that I would end up doing otherwise the pattern is going to look almost the exact same and every time I add a new class I basically would have to go to the same spot. So I build discovery frameworks there's some things like screwdor that
you might be familiar with. I have some other videos on my channel walking through that. But basically, if you use Autofac, you might see this kind of module concept. Screwdor will be able to go navigate assemblies and scan them for types and then register them. I just I combined some of these concepts in my own development and I figured it's time to go package this together. So, I figured we'd walk through this weather app and I'll show you what it looks like in Needler. Needler is still at this point in time very much like an alpha stage kind of thing. I do use it for brand ghost already but I'm trying to make it more generic so that I can release it as a package and that way when I go build the next set of uh web applications and it doesn't have to be
web applications either the next thing I'm building in C I follow a plug-in architecture anyway so I might as well reuse this. Okay. So in this example, uh just to make it, uh a little bit more realistic, right? I'm going to pull out the minimal API registration that we have here, I'm just going to pull that into an extension method so we can see how this pattern starts to propagate. Okay, so public static weather forecast extensions, sure, thanks very much, co-pilot. It's not what I want, though. I want uh public static void add weather routes. Sure, something like that. Okay, that's actually good enough, right? You can see that copilot just basically completed the code that was above. So we still need this weather forecast data transfer object. It's just a record. But we pull this off and then we can say this is on
the application itself. So you would do app dot add weather forecast, right? So we start to build out these extension methods that go do registration for us. And then if you wanted to, you'd go pull this off into its own class and you start cleaning up your entry point because otherwise you're going to have a million different things of this entry point as your program gets bigger and bigger and more complex. This is the kind of pattern we start to see. But the next part of this is like okay well let's say we have this code and we wanted to go start refactoring it. I realize this is the weather sample application so it might be like overkill what I'm going to show you. I'm just doing it so you can see some of the concepts. But you might say, "Okay, well, I actually want
to make this more testable or I want to have a weather service that we're getting this weather from instead of just this dummy kind of stuff that we see here." So, you might go do something like public sealed class weather service or like weather provider, whatever you want to call it. And then you'd have something that's like uh I'm not going to go make custom objects for this stuff, but you'd have like I guess we do have this one. I'm just going to reuse this. So, we have like public i read only list of these things. And I could say get forecast. Um, sure. And it's just going to I'm going to let co-pilot do its thing. Doesn't like that. Oh, let's make that public, too. Cool. So, all that I'm doing is kind of pulling this out into a a new class that we're
going to go inject now. And that means that we can get rid of all this code that's in here. And I'm just going to provide this on the minimal API. Okay. So weather provider I'm just going to call it that the term provider get forecast. Okay. This is a pattern that we see because if you have these minimal APIs that have these services or providers or just other classes that you want to work with, this kind of thing is a lot more testable. It might be something that you want to go interact with the database or entity framework core or something whatever you have other parts of your system that you want to interact with and you start pulling these pieces apart so they're a little bit easier to work with. But now that means that we have a weather provider and if we want
to use dependency injection so that it gets passed in here. We have to go add that back on to our container. So you could go back into this extension method and maybe it won't work there, but you would have like on the builder. So you go do something like this. So thank you co-pilot, right? So we want to add singleton would be fine in this case because there's no state for it. You'd go adding like stuff onto this service collection. But the whole point that I'm trying to avoid is as I continue to build out my applications, this keeps growing and I can keep pulling these things into more extension methods, trying to keep this, you know, more more concise, but I'm going to keep adding more extension methods and keep manually registering things. This is something that when I set out to build Neler,
I was just capturing all of the patterns I use for discovering types and automatically registering them in an opinionated way. Hopefully so far that makes sense. This probably isn't news to you. If you've built anything in ASP.NET Core, you've probably seen the weather sample application and you've seen some of these patterns coming up. I'm not going to show you something that I'm saying is, you know, is the right way or is the better way, but this is the way that I like to use because it fits more naturally with my own development. Okay. So, this is very much uh as the same application. Uh it's slightly different just because I've used a little bit of a different approach here, but I'm going to walk you through what needler does and why it's opinionated and how it works. In this case, my two lines here on
line four and five, this is literally all that I need to go build the application. So I have a class I'm playing on dependency injection puns here. So needler like a needle, right? And then there's a syringe because they're needles injection. It's funny. I don't know. I didn't not really good at names. deal with it. But I have this syringe class and it's basically what I have is a fluent builder syntax. So there's more methods on here. So I can say like, you know, do it for a web application. We can add or, you know, change how we're registering things. But this is literally the most simple setup that I have in needler for going to make a web application. So this essentially does what we saw in the other file in the other project to go build a web application for us. This line
gets us the web application, but what you're not seeing behind the scenes is that this will go do type discovery and registration for us automatically. So, what does that mean? Well, I have a plugin right below. I can go define this in another class. And you might say, well, Nick, isn't that really the same as having the extension method? And yes, it's almost the exact same, but I don't have to go manually add the extension method. So what I'm not doing here is I want to go extend this with new routes. I don't have to come back in here and say, you know, something like add new routes. I don't have to do this every time I want to add functionality. I simply just define a plugin like this. For me, this is a huge advantage for building plug-in architectures. I don't want to have
to come back into the main entry point and keep adding the new functionality in manually. I just want my plug-in to build get dropped in and the new functionality is present. Again, this is how I build applications. I'm not saying that this is the only way or the one right way, but this is what works for me and I repeat it in basically everything I do. This minimal API is very much like what we saw in the other one. But I'm using a web application plugin in this case. This is a needler type. So needler on startup will go look for web application plugins. if you are using this approach where you're building a web application and it will go look for the plugins to go configure the web application and that means that you can add in your own routes and things like that.
So I'm adding in the weather minimal API here and then I'm doing the same thing where I went and made a weather provider. So that means that this has to support dependency injection, right? This has to come off of the dependency injection container in order for this minimal API to work. Okay, you don't see me registering that type anywhere up here, right? There's no magic for how I'm setting that up. So, you might say, okay, well, how is it happening then? And it's happening because needler will go discover these types. So, there are certain types that by default, Neidler will not go register. So, if you have a record, for example, needler will not go register records automatically because those are generally data transfer objects. So, again, it's it's opinionated. In my opinion, those are not things that are supposed to be registered automatically, but
I do want classes to be, and they need to be classes where I have things that can be injected by default. So, an I configuration is something that could be injected off of the dependency container, but if I had a constructor that took in a string or an integer, the dependency container is not going to know which string or what integer automatically. So if we want automatic type discovery and registration, it will work on things that generally can be constructed through dependency injection frameworks automatically. Needler will scan for this and register it and it will register it as both the class itself and if there's an interface on it, it will add the interface for the registration as well. Again, that means I don't have to take this and then somewhere up at the top on the service list, I don't have to say services
and then I realize this is already the built application. But if this was the builder, I don't have to go add singleton and then try to add in the weather provider, right? I don't need to do this anymore because Needler itself will go discover the types for me and do this behind the scenes. In a simple application like this, it doesn't look like it's much less code at all because the only thing that's really different here is that I'm taking off the methods to go register stuff onto the dependency container. But what's happening is that if I wanted to go add, I don't know, this is a terrible example cuz it's the sample weather application. If I had a, you know, an e-commerce site, the other like stereotypical uh sample application, and I wanted to go add in, you know, a product screen or some
other type of feature, all that I would need to go do is go build out the plugin for it, and I never have to come back to the entry point to go add more things on. So, my entry point remains two lines long even though I keep adding more functionality. Whereas a lot of other traditional C dependency injection with the extension method approach, we have to keep coming back to the entry point, adding new extension methods or going to the extension methods we've already made and those lists of what we're adding on keep getting longer. We just get rid of all of that with needler. If I go run this now, what we should be able to see, let's pick the minimal API here, and I can put a break point in. We'll put it here. And I'll put it here. You can probably add
one here. So when I go run this. Okay. So on startup, you can see that it might be a little bit tricky to see, but this web application line is grayed out. This is a different part of the call stack, but we end up making it into this configure method on this plugin once we're calling build web application. So that's neededler going and discovering these uh plug-in types and then it's going to go call the configure method which we see right here. And for us in this implementation that's registering this weather route. So we'll take that breakpoint off and press F5. Great. And you can't see it cuz it's on my other screen but now it's here when I call this right. So on launch we're going to weather and you'll see that the break point was already hit. So this is just sort of
proof that the minimal API is getting hit. And the other piece of evidence here is this weather provider is passed in. It's not null. This is a real weather provider instance. And if I press F5, you can see that we end up stepping into the get weather call that's right down here. So this is all, you know, wired up uh as I was just explaining. And that means that we get the weather pulled off of here. That's really it in a nutshell. This example should not really seem groundbreaking, but what I'm trying to illustrate is that for how I build applications, this part, my entry point of the program should always remain extremely thin because I don't want to have to keep registering more things all in one spot. I like that personally being separated out into the plugins where they get registered automatically. So
if that's something that sounds interesting to you, needler is the framework that I am building for that. It's currently a nougat package in alpha, but this is the thing that I will keep building on because I build applications this way. And as I mentioned at the beginning of this, brand ghost is the application that I am building for cross-osting and scheduling social media content. And it is completely plug-in based because there is a lot of different social media platforms and things to integrate with. And I use basically this exact concept where I go and discover the types automatically register them and my entry point for brand ghost remains very very thin as a result. So thank you so much for watching. Hope you found that interesting and remember to check out Needler. Thanks and I'll see you next time.