BrandGhost

We Need TWO?! - C# Plugin Architecture For ASP.NET Core

In the previous videos I made on this topic, we looked at two different ways that we can setup Autofac for dependency injection inside of ASP.NET Core. There were pros and cons to both approaches, but for the most part, they'll do the trick for the average application. When it comes to building C# plugin architectures, I want to ensure that I have access to all of the dependencies that I'm interested in. That means in ASP.NET Core, I need to set the bar high for my plugins. See how I was able to combine both of the previous methods into this one!
View Transcript
dependency injection can be incredibly valuable when it comes to building plugin systems it doesn't matter if we working with asp.net core or not being able to resolve dependencies can allow us to build some really powerful applications hi my name is Nick centino and I'm a principal software engineering manager at Microsoft recently I put out a couple of videos about working with autofac inside of asp.net core if you haven't seen those yet I'll make sure to put a link above and you can check those out and then come back here to continue on in those videos I was showing different ways that we could set up autofact and the pros and cons that we have with that but in this video I want to take those two concepts and kind of marry them together to get the best of both worlds and that's going to afford US the opportunity to start building plugins a quick reminder that if you find this kind of content valuable make sure to subscribe to the channel and check out that pin comment for my courses on don't train with that said let's go over to visual studio and check this stuff out so to start things off I'm just going to show a little bit of code from one of the original videos and this is because we're going to continue to build on top of this the big difference is going to be that I've moved away from the default sample weather application where we had this weather forecast route that's kind of just the built-in sample app like I mentioned so we have that here but in the more recent example I've kind of moved away from it so we can see that we have the recommended way to use autofac like I mentioned this is covered in one of my original videos on this topic but you can see this use service provider Factory and then we set that up with the autofac instance here and within that we can go configure our autofac container Builder this is the way that they recommend to do it and it does have a couple of small limitations but for most people this is probably a great start from here what I wanted to do is jump over to the new program that I've started and if we go look at that we can see it's just one line of code right in the entry point yes we could have in the other example started refactoring things away to go get them to be something like one line of code I just wanted to show you from the start this is a pretty power ful thing and I like this because the more opportunities we have for you know code that doesn't have to change as we go to extend it it just means there's less things that can go break and that's a pretty powerful thing here we have this full resolve web API and we go to run it but what does that do well let's jump over to that class inside of here this is code that we've seen in one of the previous examples this code looks very much like the second example that I had a video on where we kind of skipped the recommended way to approach things and we're creating our own container Builder so we are going to leverage that pattern here what we do once we've built everything up is we go resolve this configured web application which I'll show you in a moment and from there we go ask the web application instance this is the one that asp.net creates for us we go ask that to run so this line here is basically what you would already be doing in every asp.net core app that you go make before I jump over to this configured web application I want to show you my container Builder because if you're paying close attention my container Builder is not the autofac container Builder this is my own class so what does that look like well it's just a little wrapper around the autofac container Builder as you can see on screen right now the reason that I've pulled this out into its own dedicated class is because I want to kind of encapsulate the different mechanisms that you might want to use for assembly scanning it's worth pausing on this for a moment too because if you're not familiar with what assembly scanning is especially in the context of plugins and dependency injection it's worth understanding if you look at the code that I have highlighted on line 12 so this line right here we can see that the assembly that I'm referring to is just the current executing assembly and if you're not familiar with that if we look on the left hand side in the solution Explorer it's going to be just this project that we have at the top that's going to be the executing assembly that we're inside of when it comes to looking for things when I say looking for things this next line on line 13 says that we're going to ask our current assembly for the different autofac modules that are inside of it and autofac modules are just a different structure that we can use to be able to register dependencies onto our container you take this concept and you think about the fact that we might want to have other dlls so say other plugins that are from completely other projects that were built we could take those dlls put them into a folder and then we could change this from line 12 to actually go look through the folder and look for different assemblies and we can start to pull in those modules to register their dependencies in so this is a really powerful way that you could go extend all of this to be able to go have other plugins pulled in because you would leverage plugins to have their own modules to add into your application that's a quick primer on what you could do with this to extend it to Beyond just our current assembly now I'm going to jump over to the configured web application before we get into something that's a little bit more complicated but the configured web application if you look here on the instructor it takes in two different dependencies one is going to be just the standard web application that asp.net core gives us and the other is going to be this list of markers these markers are a pre-application configured marker you can see the record type is right here and it doesn't have anything on it it's just a marker class and I'll explain a little bit more about how I'm using markers why they feel a little bit gross but how you could take this pattern and extend it to do more powerful things this is all going to be related to how autofac goes and resolves dependencies so we'll see this in just a moment but the concept if you think about this Constructor up here is that this class configured web application cannot be created unless we have the pre-application configured markers so this is an important thing to be thinking about as we go forward so far we've been seeing sort of this skeleton application and I'm calling it a skeleton application because it doesn't really do a whole lot except get us bootst wrapped to be able to have our other functionality pulled in but there's one more piece that we have to look at and that's setting up some of the core infr structure for resolving dependencies so I'm going to go over to our first module this module is going to be where we start to marry together the first and second videos that I put together on this concept we already saw that I was using the container Builder Standalone from autofac so we got to see that in the my container Builder class right and from here we're going to look at our first module where we start to register some other dependencies that we need the first one is going to be the web application Builder that's this first registration again this is going to go right onto our container Builder that you already saw the next thing that we're going to do is make sure that we can have the the configuration so I configuration will be added into that container Builder as well these are two things from the previous videos that I said these are core dependencies that I want to make sure I have access to this next part is going to be where we have the new Secret Sauce that kind of links it all together and gets the best out of both worlds buckle up because this is going to get maybe a little bit complicated but I think if we walk through it together nice and slow it's not so bad I'm going to come back to this cached part in just a moment so bear with me on that cuz you might be saying why do we have this variable pulled out the front why do we have this caching here this is a bit of an edge case that we have to think through but we need to kind of see the other pieces first so what we're going to do inside of here is we're going to resolve the web application Builder off of the context so this is a registration method for autofac which means that this registration will run when someone tries to resolve whatever this thing is returning you can see that it's returning a web application instance so that means the first time that someone says Hey autofac container I need a web application instance it's going to make sure that it goes and runs this you can see that it does single instance so in theory this should only happen once it does it once saves the instance and that way the next time someone asks for it they already have access to it that's like saying on this line here where we go to resolve the web application Builder if I scroll back up it's going to go run this registration code inside of here one time because it's also single instance so hopefully that pattern makes sense because you need to be thinking through this as we go through a lot of this stuff so we're going to ask for the web application Builder and this is another spot where we have these markers and these pre-application build markers are just an opportunity that if anyone wanted to go register things onto the web application Builder instance if they need to do that before we try to go build anything this is their opportunity to make sure that those registrations are run what I mean by that and how that works is that when we go ask this context we say hey go give me the list of prea application build markers it has to go find all of the registrations like this type of code here that go and return this type that way you can go run anything you want right you can force it to go run before this line because they will go return this typ in my opinion it's a little bit of a hack to make autofact do what you want in a particular order that's why I kind of said earlier it seems a little bit weird but this is something that you can extend you could also kind of mask this pattern by behind different things I just wanted to walk through it so you can see how I'm leveraging it if we did need any code to go run before we start going to do this other work this is a way that we can put a marker kind of in our flow of execution to force other things to happen first this next part is going to be quite familiar to what we saw from the previous video you can see that I'm using the second way and it's the original way to go get this autofax service provider Factory put onto this used service Prov Factory method here right so we have the container Builder Standalone and the recommended way being used together but how the heck can this possibly work because we're dealing now with two different autofac containers that's going to come down to this part right here this is sort of the magic that makes this all work together because what we're able to do is take the registrations from the one container Builder and that's going to be this context that's passed in right this is the one right trade off of this registration method up here we can take the container Builder essentially get the component registry and then copy all of those registrations onto the one that's going to be for this autofax service provider Factory so to back up a second we have two container Builders this one that's at the entry point to the program and this other one that's going to be the container builder for this web application instance that we're creating these are two different container Builders what we're able to do is start copying the registrations from one of the container Builders to the other though so now they both will end up having similar sets of registrations without this this would all fall apart what we're able to do from there as well is make sure that we have our web application Builder added on I should double check though this might be unnecessary and it might be unnecessary because it's already in here I should have debugged this and checked first but we might not even need this bit of code which is pretty cool this here here is going to set up our web application to use autofac its own container builder for the web application itself then we go build that web application and we're going to save it to this cached web application instance I still need to come back to this because we have to go a little bit further but we return that as part of this whole registration what we've seen so far is how to set up our overall container Builder and then a second container builder for the web application instance itself now from here if we go a little bit lower we're going to see that we can go ask for the web application Builder right this is another autofac registration we can set up https this way and then we can say this is a pre-application configured marker if you recall what I was saying about markers this means that this code has to go run before anyone who needs this marker can continue and who needed this marker I'm not sure if you recall but this configured web application needs the pre-application configured marker that means in order to finally get this thing down here on line 62 if anyone asks for an instance of this autofac will say sure let me go make that for you CU I can see that I have this registration and then when it checks out the Constructor for this it says do I have a web application and that's what this part is going to provide so there's a registration for it and then it says well the configured web application also needs a list of all of these right here there's one of them right so this code must go run before we can create an instance of the configured web application this is just an order of operations that has to happen the reason that I pulled this code out like this instead of just jamming it up here and putting it right after this line for example CU we have the access to the web application is that I just wanted to show you that if you wanted to go create your own types of plugins or other separate modules you can essentially go tack on this kind of behavior if you pulled this out into its own assembly with its own module you can start configuring the web application itself so this is a powerful way to do it it's just a little bit Overkill it's kind of unnecessary in this particular case I just wanted to illustrate that's how it would work now we're going to go look at our first plugin and when we do that we'll come back to why we need this cached web application because it is a little bit out of place and it's still needs an explanation but let's check our first plugin if we go to this plug-in module this code is very much like we saw in the previous examples like I said the differences that I changed this route so you can see this just says hello I didn't really have to go change this I just wanted to simplify it a little bit because it was doing the weather forecast stuff we just don't need that we have a holow API that we're going to have it has the same types of dependencies and it's just going to return a two string of those dependencies when you call this route that's what that does but let's start from the top just so we can walk through it these three registrations are because I want to have three different types that we can go work with this is from the same two previous videos where we have dependency a b and c and if I scroll to the bottom of this file you can see that I have those dependencies here and the point of these was not because these are special it's just that these dependencies on their Constructors so line 50 line 56 and line 62 these take in different dependencies that I wanted to make sure my plugins had access to so the web application Builder itself that means you could write plugins that can go modify this or extend it the web application itself so that means that again you can go write plugins that modify and extend this that example of using https that's a perfect example of what you'd be able to do if you had this and the last one here is the eye configuration and a good example of this is I like writing plugins they can go read things like the environment variables to conf figure different parameters on Startup so these three things all have Constructors that take in a dependency that I'm interested in we're going to see that this setup allows this to work which was sort of one of the boxes I had to check in making a good plug-in system and then the other two things that I wanted I wanted to make sure we could have minimal API syntax that's what this is so that does work and the last part was that I wanted to make sure on the minimal apis themselves we could go resolve these dependencies the work around for this when this doesn't work is that we could resolve them ahead of time so on line 23 right after line 22 I could go resolve these off of the container we could just make sure that we have access to them that's a way that it could work but I wanted to make sure directly on the Callback that we have for the minimal API that we could essentially have these passed in this setup does allow this all to work and it's really cool and and the last little bit before I go run this as you can see this is one more pre-application configured marker the reason that this is here is that it forces this route to get registered onto the application that's built before we allow our program to go resolve the configured web application instance because once the program can resolve configured web application instance it's just going to go run it if we allowed it to be run before this code got to execute this route would not be put onto our web application that's why we need to force the order of operations here so hopefully that's an example that makes sense and if you're building plugins and you want to be able to control what code is executing first inside of your registrations this is where you could go come up with a different marker system if you were doing all of this without using autofac when you start to pull out code into different plugins you have this complexity that's added on that you need to consider what needs to run first if your particular plug-in system for your program doesn't really care because it doesn't matter what runs first this can stay pretty simple but as soon as you start to need ordering you need to have your plugins or something be able to control the order of operations so in my particular case I'm using this marker pattern to be able to do that okay I'm going to go run this I want to prove that when we call this hello method we should be able to have these three things resolved automatically by hitting this route and then we can go call two string on all of them and it will show us that we have an instance of all of them the two string data is not that valuable cuz it's just proving that we don't have null come back or something like that so let's go run it and see what happens great success we have all three dependencies coming in if you see the two string parts that we have here we can see we have an instance of a web application Builder an instance of the web application itself and we have this configuration manager that implements eye configuration so we do have all three of these dependencies completely accessible to us on a minimal API on the definition of that being passed in when that route is hit so to me this checks all of the boxes that I wanted to have when doing a plug-in system for aspe but there's one thing that I still have to come back to and it's a bit of an edge case and I almost made this entire video without catching it what I want to do is I want to go back to our module that we have and if we go check the module let me close some of these things here the module that we have here ends up saying that we need this cached web application and here's the weird thing I'm going to put a break point here because I told you and this was my understanding that when we have single instance defined here we should only ever go run this code once but I think there's a slight Nuance that comes into play when we start to copy these register ations over let's go ahead and run this again and we'll see what happens when we hit this break point okay so happens very quick it's actually way quicker than I thought but we hit this break point and the first thing that we're going to do is kind of all this code that you see on the screen right cached web application when I hover over it is currently null this is the very first time this code is run we're going to end up going through and putting all this stuff onto the container so I'll put another break point in here right so we end up getting to this point where we're copying the registrations over that's cool if I check out the registrations this is going to be a little bit messy to show maybe I will use my locals here um what's a good way to do this let me add it a watch this is another shout out I do have a video on using debug watches if you're not familiar with this kind of stuff in Visual Studio it just makes it a lot easier to navigate so I have all of these dependencies pulled in here on this container Builder already so that's this on the context these registrations already exist if we look we already have a web application there that's awesome right it should be the web application that we're building right because that's what we're doing in this callback we have that we have the web application Builder itself so like I was saying we probably don't need this bit of code here on line 43 that's all right but what's going to happen now is if I press F5 you can't see it on my other screen but it ended up hitting the route so we're hitting the hello route right now and you can see that it came back in to this registration method now I thought this should be impossible because of this single instance but what I think is happening is because we've copied the registration over it doesn't know that it's already been resolved that's why it's going to try to do it again but we don't need to go run this whole thing again we've already built our web application the fact that we are currently hitting a route and if you don't believe me just let me pull this screen over this is like you know it started running it's hitting this route currently and I hit the break point we don't need to go run this again we have the web application running and that's why I decided hey let me just keep a cached version of this that way when we step into here it says hey it's not null just return the one that we already have it's essentially trying to give us this single instance Behavior because I'm not totally sure why that's not working for us if you're an autofac expert and you're watching this feel free to leave a comment below because I'd be happy to hear more about this but I think it has something to do with this here copying the registrations and the fact that the way this is all working is like a funny order of operations because we need to be able to call this build method and calling the build method is what's going to go run this I think that there's just some weird order of operation stuff coupled with this bit of code here that means the single instance Behavior doesn't do what we expect anyway this little trick here this is an edge case this is allowing us to make sure that we don't break anything and we still use the same web application instance with all of this said in my opinion this is giving us a lot of flexibility to go add plugins because if I wanted to I could go add new routes just like this only by going to add a new project right I don't have to go modify any existing code aside from the ass scanning stuff but if you assume that you were like hey I already want to support different assemblies from different files you would go do that one time once it's supported you can go add in new projects build them have their dlls put into your bin directory and you just get new functionality or the ability to change functionality right away without altering existing code for example if you wanted to go add a goodbye route right you could go at it right here if you wanted to but if you said hey look I don't want to modify any existing code I want to be able to extend this application without touching stuff just adding net new code you can totally go do that with a new plugin some of the tradeoffs that we have to think about are the complexity of ordering right so I mentioned that if you have things that need to rely on the order that they're executing I'm using markers this could be a little bit weird for some people it might not feel right you might want to have something that's Central that defines an order so you'd have a manifest of some sort that says go execute these things in this order you could build plugin systems that way that's not how I'm doing it right now I don't like having a lot of like centralization in my plugin systems but you know that's a personal decision I think the other thing that I wanted to call out in terms of tradeoffs is that single instance Behavior we looked at I haven't yet seen any other situation where that's happening it seems to be an issue in particular when you're using the minimal apis and passing things in this way here so that means that we're resolving it off of the applications container Builder not the overall container Builder right so we had to copy stuff into the applications container Builder I don't know if there's going to be some other weird situations it does not happen for the other two dependencies just the web application itself so it might be very limited but I wanted to call it out because there could be some other GES with doing this so hopefully you found this insightful and if you want to see me building more plug-in systems you can check out this video next when it's ready to see asp.net core plugins come into action thanks and I'll see you next time

Frequently Asked Questions

What is the main purpose of using dependency injection in a plugin system for ASP.NET Core?

The main purpose of using dependency injection in a plugin system is to resolve dependencies effectively, allowing us to build powerful applications. It enables us to manage and extend our application's functionality without tightly coupling components, making it easier to add or modify plugins.

Why do we need two different Autofac container builders in this setup?

We need two different Autofac container builders because one is used at the entry point of the program, while the other is specifically for the web application instance. This allows us to manage registrations separately and ensures that both containers can share the necessary dependencies without conflicts.

What are pre-application configured markers and why are they important?

Pre-application configured markers are a way to control the order of operations in our plugin system. They ensure that certain registrations or configurations are executed before the application starts running. This is crucial for ensuring that all necessary dependencies are available when the application is initialized.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload