I owe a lot to Autofac for many years of dependency injection support in C#... So in this video, we'll walk through my vibe-coded version of a popular Autofac capability that I have working with SOURCE GENERATORS!
View Transcript
What's really neat about this class or this interface, these things do not exist in my code. They're automatically generated. In this video, I wanted to talk through some functionality that I built into Needler using C-Pilot CLI that I thought was super cool. It's really excited about it and I figured I would share. And if you've watched my live streams, then you've seen that we actually walked through this using C-Pilot, going back and forth and specking it out and then having it build it for us. But the functionality that I want to talk about is automatically generating factory methods and also entire factory classes using source generation. If you're not familiar with needler, it is a dependency injection sort of helper framework that I have. It scans assemblies if you're using reflection mode. And recently I used co-pilot to go basically build out an entire source
version of it. And that's super cool because with source generation we can do so many awesome things. I'm just starting to scratch the surface and I figured this is a cool feature that we could go build. So, let's jump over to Visual Studio and talk about it. Historically, I guess like over the past, I don't know, uh, 8 years or so, like I have been someone that uses dependency injection framework called Autofac. And it's kind of fallen out of popularity because the built-in Microsoft one is just gotten better and better and better. And if we go way back, Autofac was really dominating dependency injection, at least in my opinion. There's other libraries and things like that, but the built-in one just wasn't great. And there is a piece of functionality that's really cool in Autofac, which is if you think about the name AutoFac, Autoactory,
right? So, one of the cool things that we could do is if we have certain types, so like this class right here, what we're able to do is define a public static delegate on it. And essentially that is a factory signature that we want to use and it helps us solve this particular problem right here. You might say, "Well, Nick, well, what's the problem? This is just a normal constructor. It takes in a dependency and a connection string." And that's right. On the surface, there's not really a problem with this, right? You can go instantiate this class. Not an issue. But when we think about dependency injection, one of the challenges with a class like this is that this is a service that can be automatically passed in. No issue on that because we could wire up some implementation of an I console time provider.
By the way, this is a totally fabricated class just for demonstrating things. We can go wire this dependency up to the service collection. We can wire this entire class up. But when it comes to things like primitive types, like you're probably not registering strings onto your dependency container. It's kind of weird because how do you know which string you're supposed to be resolving? And of course, there's ways around this, right? You could go make an entire configuration type to pass in, read the connection string off of that. Like, this isn't something that cannot be solved with some of the magic we're going to look at, but autofac allowed you to basically define a factory method on the class and then that factory method would just take a single string. And that meant that when you registered this type with autofac, you could get a function
and that function only needed to take the connection string and it would automatically figure out how to pass in a time provider. So I'm going to jump over to my program.cs which has some demo code. We're going to see that we can do that now with needler. But this is essentially what it would look like. You could ask either in your constructor, right, is a passed in type or you could ask the service provider, give me a function that takes in a string and returns a database connection, right? So you could ask for the factory. This is essentially what it is. It's a factory that builds a con a database connection type and only takes in a string. We don't need to pass in, if I jump back to it, we don't need to pass in this. This I console time provider magically gets wired
up through dependency injection for us. So Needler now supports this type of thing. We're going to look at how it does that very briefly, but I wanted to show you this is one version of that. Now if I go back to the plug-in types, one of the other things that when I was putting this together with Needler is I said that's pretty cool, but I think that we can do even better than that because that's what autofact does. Autofact requires that you actually define that, right? So if I jump back here, if I take this kind of thing, you actually need to in autofaced it and I and it's been a little while so I apologize, but I believe it's like public static. You actually I don't think you put a funk, but I think you do public static delegate um database connection and you
say factory and you do string and I don't know if the naming has to be this is obviously not exactly right or it's not static. It's something like this and you define a a delegate, right? So you need to go put this on your type. You need to then go register it onto the container. We can skip over all of that with needler now. So we don't need that at all. We put generate factory onto the type and it will automatically we don't have to go register it on the container ourselves. Needler will automatically go create factory functions for us. And if you have different sets of parameters, so if there was a connection string or an overload that took in connection string and something else or just different flavors of the constructor, it will go make factory methods for all of those as long
as you put generate factory on there. And it does it at compile time thanks to source generation, which is I think super cool that we can just do stuff like this. But I wanted to go a little bit further. And the reason for that is that I don't think that this type of thing is particularly intuitive. What do I mean by that? Well, I'm going to just use some screen real estate here to show you. But if I wanted to make a new type and it needed a factory that could make database connections. Okay, so let's let's just pretend we have something here public sealed class Nix type. And I'm just going to I'll skip the um primary constructors cuz I know that kind of drives some people nuts. But if we did nyx type whatever I'm just going to autocomplete that and I need
a database connection let's say. Okay. What I would need to do is I would need to know the parameters that get passed in. So I would need to know that if the factory method for a database connection must take in a string which maybe is obvious. So let's put connection factory here. Maybe that's obvious for some simple cases. You're like, "Okay, if I want a new database connection, I understand I need a connection string. Not too complicated." But if there were other overloads, another string or something, and maybe it wasn't a database connection, it's something else. You need to know the signature just to get the factory method, which I don't know. I don't I feel like that's not super obvious. I think for something like a database connection, maybe not so bad, but I don't I just don't think it's obvious. And so what
I wanted to do was go one step further and basically give us an actual factory implementation which I think is super neat that we can do this automatically. So what does that mean? Well, instead of doing this, I can actually say I database connection factory. What's really neat about this class or this interface, actually there's both. There's a class and an interface. These things do not exist in my code. they're automatically generated. So if I F12 onto this and go to the definition, we now jump into this generated code. So if I just I'm going to quickly scroll through this file, everything that you see in here is automatically generated by Needler using source generation. So it was able to find, if I'm just going to jump back to plug-in types, when it's scanning all of the code, it goes, okay, there's a generate factory
attribute on here. it will go register that funk that we saw based on the constructor parameters. It's actually really cool because I even got it to pull in the XML doc comments. So if you put in on the connection constructor these dot comments for these params. If I jump back over to the generated code um when you go to use this you can see it has a connection string like this is automatically pulled in from the XML doc comments on the constructor. I think that's super neat. All the credit to co-pilot. I just had the idea. And so this interface gets automatically created for us. And it's backed by a class that's also automatically created for us. We basically get a first class factory. It's literally a type that we can go resolve. I think that's kind of cool because intuitively if I want a
factory I would probably type something like this and then I don't have to guess at or I don't have to research and know what um what the signature is going to be to create it just to resolve it. of course when I have to go use it right now I need to know right so I need to know that create okay I see it takes a connection string but if there were multiple overloads for create then it would actually show them all and you can see in my IntelliSense here right it says connection string the database connection string blah blah blah with an example again that's all source generated pulling from the original part right here actually this is not the same one this has a time provider on it here. So, I wonder which one this actually is. Oh, sorry. I'm I'm being kind
of stupid. My apologies. This one is injected. We actually don't need to worry about that. It's taking this parameter because that's the only one that needs to be passed into the factory, right? The other one is automatically passed in. Let's go back here. You can see that it takes in the console time provider and that will get wired up through dependency injection for us. These are two pieces of functionality that I was inspired to kind of take from AutoFac and we're using source generation now to do that in a in a different way. So I'm going to walk you through briefly what we have in some of this demo code. By the way, this is all available in GitHub if you want to see it. My goal with these videos talking about Needler is not to convince you to go use it. It's an opinionated
framework. It works for me. I build it because I use it. But it's actually, I think, kind of interesting to talk through the different things we can do. If we look at this demo and we'll run it. There's some other cool stuff in this project as well. This one to start with is resolving a database connection factory. This is completely source generated. And then you can see that we can basically create a new uh database connection using the create method off the factory. This is the only parameter that has to be passed in because the other dependencies are passed into the constructor. This is kind of just bogus, but you can see that we're using the actual database connection. So query is something that existed on here. Okay, so it's all just kind of faked out code. It's not a real database connection, but you
get the idea. I think if we don't want to use the actual interface that's generated, we can use the funk like I demonstrated originally. Same concept. We can go use it after. Another interesting thing that we can do is if you said okay well when I want to generate the type maybe I don't want people to be able to resolve a database connection. Maybe you want your factory to I don't I don't think we have it but if you oh we do um if you only wanted the interface for your database connection. I have another example here with something it's just another madeup type called a request handler. We basically can force with this attribute. we can force the request handler to be registered only in a way that lets us have a factory that gives us the interface back. So if we want to
go look at this implementation, I'll jump over to it. You can see that it has the generate factory attribute, but we have this type parameter on it. Okay. And needler does have analyzers and some other checks that if you put a an interface that's not the right thing on here, it will complain at you. So if request handler doesn't implement I request handler or some other thing that you put on here, it will fail to compile and there's analyzers to guide you. The point is that if you had this factory that was automatically created from this and you said, "Well, I want the factory to return an interface because maybe you want to do something like mock it out in your tests, whatever sort of reason you might have, you can force the generated factory to return the interface instead of the concrete type." That's
what that looks like. It's not maybe as obvious with this funk, but you can see that the funk is returning the interface. If we look at I report generator factory, I think this one's not the same, but let's let's kind of do it here. So instead of handler funk, let's do handler factory and we should be able to resolve an I request handler factory. Okay. And then that means if I were to do I request handler just to make it obvious. Request handler. Sure. this create method if we jump into it, it was forced to generate with the interface instead of the type. Okay, so you have a little bit of control over that. Again, all done through source generation just because we changed the attribute to have this type passed in. I think that's pretty cool. And the last one is just demonstrating that
if you have different constructors, then we can we can go see that. So, let's go to plug-in types. And it's probably in here. Yeah, we have a report generator. Okay. Okay. And if we look at the report generator type, it has two constructors on it. Right? This is the first one and it has a parameter passed in a dependency. The same thing on the other constructor has a dependency. But both constructors have these other parameters that don't really make a lot of sense to be resolved from your service collection, right? You probably aren't adding a string onto a service collection to resolve later. Same with an integer. Like I said earlier in this video, you could go register some custom, you know, DTO or configuration and resolve that dynamically. That's totally an option. But if you want the ability to construct these on the fly
with a factory, Neler will now give you a factory or you can resolve the different functions. Every time you see one of these factories, you can also get the funk for it as well. And then you have access to both overloads for create. pretty interesting. If you only want the funk or you only want the interface like sorry the factory you can actually control that as well. So if we go here uh I I'm not sure if I have it yeah mode. So it's mode and then you can do factory generation mode all interface or funk. So you can actually say I don't want like I think Nick those interfaces are dumb. I don't want a factory interface or you think the funks are dumb you don't want those. You can control that when you make your factory. But again, the really cool thing with
this, in my opinion, is not only the source generation part, but what I like about Needler, and this is again opinionated, it's my opinion, is that report generator is never manually registered onto a service collection ever. Needler will go discover these types automatically. And the discovery now works with source generation. So, if I scroll back up through here, there's a lot of different examples. We should go run this just because it's, I think, pretty cool. But if we go back up to the top, we build what's called a syringe and needler. We say in this case that we want to use source generation. We build our service provider. And you can see that right away we're resolving things. At no point do I go register these report generators or other types like these factories. These are all automatically discovered. Okay, so let's go run this.
I hope I didn't change things by accident. Leave them in. We'll see what happens. And I will do some follow-up videos on some of these other features. So, we'll run this. There's going to be a whole lot that gets output. But, um, just to show you very briefly, if I go back up to the top, there's a whole bunch of different features that we have. Interceptors are a functionality kind of like decorators, but we have a little bit more control over how they're applied. So, super cool. So, I'll do a video on interceptors that are source generated. You can do method level interceptors, not just on the whole class. There's automatic decorators as well. And then the factory delegates is what we were just looking at. So this is just to show you that all of that code that I was walking through is not
I don't know not like I coded it and it doesn't compile. It doesn't work or anything like that. It is compiling. It's running. And then if we were to go publish this, this example actually trims cuz it's an AOT application. So it will go trim things out. And if you were to go run the trimmed version, it still works. So it's to sort of prove that we don't need reflection at all to do any of this. It's all source generated. So that's the video. I just wanted to walk you through what it's able to do. If you want to see how the source generation actually works, there is this really big type registry generator. You can see that if we look at some of it, it's literally writing like it's a string builder and it's writing out code, right? This is C code. You can
see that it's adding in the doc comments. You can see that it's going through the injectable parameters and it writes out C code, which is pretty neat. And I think once you like for me at least once you start looking into it kind of just opens up a whole different world where you're like well what else can we do? So once I had this working I had this idea where I said well what about the doc comments? Can I start pulling in some of the doc comments from the original type and have that on the factory? Like why not? I wouldn't want to do that by hand. That seems like it would be crazy. But if I can have it automatically do it with source generation why wouldn't I? Right? So, I'm kind of trying to explore new ideas and this has been a lot
of fun and I really appreciate having AI to help me with this because it's pushed me to kind of look at things a different way cuz I probably wouldn't have gotten around to trying this stuff out if it weren't for AI doing a lot of the heavy lifting for me. I'm just kind of coming up with the ideas. So, hope you enjoyed that. It's kind of an interesting thing. And if you want to check out Needler, it is on GitHub. If I jump over to here and pull this up, you can see that it's just my GitHub here and Cosantino needler. Again, not suggesting that you jump into this to go use it and it's the best thing ever, but I think that it's got some interesting, you know, coding patterns and if you're interested in stuff like this, you can pull down the code
and you can play around with it and try it out. So, I am trying to include demo applications, right? So, if you go into the source and you go into examples, there are a handful of different examples you can play with. Some of them are source generated, some are reflection, but there you go, all here. And uh try it out. Let me know what you think and if you think there's some interesting features for dependency injection you'd like to see source generated, we can see if we can build it into Needler. So, thanks for watching and I will see you in the next video. Take care.