ENDLESS Possibilities - Dynamic Plugin Loading in ASP.NET Core Blazor
September 19, 2023
• 1,635 views
We've covered the basics of plugin loading in ASP.NET Core Blazor using Autofac, but what if you wanted to do this AFTER startup? In this video, we'll see how along with a bunch of questions you are going to want to answer for your own application design!
You can read the article about this topic here:
https://www.devleader.ca/2023/09/15/blazor-plugin-architecture-how-to-manage-dynamic-loading-lifecycle/
Have you subscribed to my weekly newsletter yet? A 5-minute read every weekend, right to y...
View Transcript
all right so before you create your next asp.net core application I want you to stop and do this one thing and of course that's figuring out how you're going to implement a plug-in architecture into your next project in my recent videos I've been talking about how we can load plugins into an asp.net core Blazer application and actually change some of the UI elements based on plugins now I've actually got this request on a couple of different platforms and that's how we can do Dynamic plug-in loading instead of just loading plugins on Startup so I recently published an article on this and we're going to go through the same Concepts in this video but we're going to cover the basics of how we can do Dynamic plug-in loading and what sorts of things we might want to consider of course when we start to explore things
like this we're going to have to think about how our plugins work with our core application or if we have plugins that load other plugins how the plugin itself will work with its parent so yes I'll explain how this all works but you might finish this video with more questions than when you started so with that said let's jump over to visual studio start looking at some some of the code for this and then discussing what implications we should be considering all right so when true plug-in fashion I'm going to illustrate how we can make the necessary changes to our example application from the previous video and if you haven't seen that I'll make sure to link it above so you can go watch that and come right back here before we continue so in the last video we actually looked at making all the
examples as plugins themselves and that way I can go ahead and modify things in isolation and just to prove it all three files that I had to touch to make this change are only located at the top here in Visual Studio as you can see so those are the only three things I had to go do and we were able to get Dynamic plug-in loading for one of the types of plugins that we're interested in so the concept here that I want to go over is that we had this idea of a plug-in provider and in the previous videos I talked about how we had this super simple generic plugin provider that just took a type parameter of the type of plug-in you wanted to work with now this was really simple in the fact that we just had Auto fact injecting our different plugins
that were registered of that type and then from there it just returned the list of plugins on this get plugins method so very simple in terms of How It's implemented it didn't do anything intelligent really all of the intelligence came from Auto fact passing in the plug-in instances but in order to make it so that we can dynamically load plugins at runtime what we're going to be doing is looking at something called a file system Watcher now a file system Watcher is not unique to plugins or anything like that there are tons of different use cases for it but we're going to look at using a file system Watcher in order to discover plugins and this is the first part where I want to pause and get you to think about what you might want to do in your own application so the example that
we walk through is only going to be discovering plugins as they're added into a directory so that's what this part is right here and we're going to be filtering on any dll that gets added that has plug-in somewhere in the name and then a dll extension and the other important thing to mention is that I said it's when it's created so this will watch for a new file to be created and that actually means that if you go ahead and delete that plugin we're not actually going to remove it in this example so if you want to have some type of functionality where you can add new plugins at runtime and then remove them as well you're going to want to consider how you might want to change the example we're about to walk through and the other part is that if you're thinking about
how your plugins are stored or where they're stored you may want to consider how this Watcher works so for me because they're just dropped into the bin directory where we're running from I can keep it pretty simple and it's a pretty basic example because I'm just looking for anything with plugin in the name but you might want to have a plug-in folder or different plugin folders maybe you have a different naming convention or some other filter that you need to run when you're looking for plug-in dlls all of that is going to be an exercise for you in your application but this code right here that I have from lines 20 to 24 is what we're going to use in our current example just to get the basics going if you're not familiar with events and event handlers in c-sharp line 24 actually shows us
hooking up an event handler to an event called created and you can see that on line 23 we have enable raising events on this file system Watcher so what that's going to let us do is that when we do have the file system Watcher pick up a new file this enable raising events actually allows the created event on The Watcher to get fired and when that happens we're going to call this method Watcher created which is what I've actually created down below and we'll go to that in just a moment but this event handler is hooked up to the created event if we look at the signature this is pretty standard for an event handler where we have an object that's called sender as the first argument and then some event arguments and you can have customized event arguments so we do get file system
event args that are passed in as the type and then the code that I have down below is actually borrowed from other parts in our example application I've just copied and pasted them into here to do what we actually require to load plugins and you'll see that I left some comments your eyes are probably drawn to that and I'll talk about those in just a moment but how this is going to work is that we create a container Builder because I'm using Auto fact for this and then I'm going to load the assembly From the Path that's on the event args so when that file system Watcher fires these event event arcs are going to have the full path to the dll that was added to the directory that met the filter which was plug-ins somewhere in the name and then dot dll at the
end next this code here is what we actually used to go look for all of the types that are inside of an assembly which we just created and these conditions that we have here are the type of T plugin API and if you recall this is a generic class so it's just the type parameter on the class and we need to make sure that the type we're looking at is of this type the other two parameters that we had here I mentioned in the other videos but this is just so that we don't try to register abstract classes because they're not going to resolve properly and we don't want to have interfaces so it does need to be a class but this code that we have here is what we use with auto fact to go dynamically register all of the plug-in types in an
assembly next what we're going to do is on line 56 actually build that dependency container so we get an auto fact container enter and then we begin a lifetime scope and from there we're able to on line 58 actually resolve all the types of plugins that we had in that dll and from there we're just going to add the range of plugins into this collection that's underscore plugins all right so let's look at some of these comments because you probably have some questions as you were scanning through the first part here says can we provide dependencies from the core application and to get you understanding what this actually means we're creating a new container Builder here so after this line 41 has finished running this container Builder doesn't have any dependencies registered to it that means anything from our core application isn't actually able to
be provided into this plugin for your use case you may actually want that to be how things work it will mean that the plugin is isolated from being able to resolve dependencies in the core application or some parent plug-in but that also might mean that you're not able to share things that are registered into the core application or the parent plugin into these other plugins so to give you an example of what I mean maybe in your core application you have a logger that gets registered and that way in Auto fact anyone that wants a logger can go resolve it if we leave line 41 the way it is currently that would mean that the plugins that get discovered dynamically actually never have the opportunity to resolve that logger and that's because this container Builder never gets the dependencies that are in the core application
now I'm not illustrating it in this video but if you wanted to work around that if you had a lifetime scope reference from the core application or the parent plugin so you see on line 57 here something like this if you had that scope from the core app or the parent plug-in you could actually build another container Builder from that scope and that would mean that you can create a child container Builder and from there that container Builder will have all of the dependencies from the original so that logger example I just kind of walk through that would allow you to solve for that problem but again you need to be thinking about how you want your plugins to interact because that's a decision you need to make for your own application the next part that I wanted to call out is this comment that's
on from line 53 to line 55 and it says how do we want to handle the lifetime here so that's with reference to this lifetime scope if we call dispose too early any delayed service resolution will blow up so these are actually disposable so if I put a using in front this actually does compile and what will happen is if I leave it like this when we leave the method the container and the scope will actually be disposed now on the surface if you're looking at this if we resolve the plugins we actually don't need to keep the scope alive anymore if everything the plug-in needed was resolved right at this point however there's ways in Auto fact that you can lazily resolve things because if that type is not yet been required from anything loaded rate at this point so after line 58 executes
if there's some other type that later needs to be resolved this scope and container might already be disposed of if we leave it like this and if that's the case that resolution will blow up later in the application so if you did want to constrain in your own application that you have your plugins getting resolved and that lifetime scope is not allowed to exist after you could absolutely put a using like this if you wanted to make sure that you could do removing of plugins for example that would mean that the plug-in does not exist for the full lifetime of the application and that would mean that you probably want to find a way to dispose of the scope in the container later if that plug is removed in our example that we're looking at we're only ever adding plugins so that's why I left
the code just like this where there were no using statements or disposes called and to reiterate that's because when this thing discovers a plug-in and then resolves it I actually want that plug-in to exist for the whole lifetime of the application so the only way that's going to get cleaned up is if we end up terminating the app at that point everything is going to be disposed and cleaned up of anyway so these are all things that you need to be thinking of and unfortunately in this video I don't want to over complicate it by trying to illustrate all of these things in one video but that's some homework for you to think through and potentially some follow-up videos that I can try to address some of these other patterns all right so up to this point we've created a file system Watcher that can
go load an assembly register all the plug-in types with auto fact and then go resolve those plug-in instances adding them to a collection the API for this class actually just required us to give an I read only list back of the plugins and what we're doing is wrapping a generic plugin provider so that way if you had plugins loaded at startup because they were already there you would get that list and then we're going to combine that list with our underscore plugins distance variable and underscore plugins is only the plugins that are loaded from the file system Watcher so to summarize by the time we leave this method we've combined the list of everything loaded at startup plus everything after now that's going to wrap it up for this class but we have to think about how we're going to use this now in essence
this class is what's going to provide us with the dynamic loading that we want but we have to go figure out the right spots to hook it up the first most obvious spot is going to be in the actual Auto fact module we still need this generic plugin provider and in this plug-in type so that the demo of plugins that we're looking at was ihtml fragment plugin so ihtml fragment plugin we still need a generic plugin provider and if you don't believe me if I go back over here that's what's going to end up getting passed into here and yes this is a generic type argument so if I go back to the plugin module you can see that I'm using the same type so that's going to mean that this right here that I'm highly setting on line 13 is the type that we
need to be passed in to this class late loading plug-in provider with this type parameter so by having this part here with auto fact we'll actually be able to instantiate this one because we'll be able to resolve this dependency from the container automatically so this is the second change that we needed to make to actually have this available but we're not using that anywhere at this point if we just register it nothing's actually consuming it in fact the original code is still just using this so we have to go look at where this generic plug-in provider was used in this plugin itself so that's actually used on the Razer page so I'm jumping over to that on line five you can see that I've changed this plugin provider instead of being the generic plugin provider it's actually requiring that we have a late load plug-in
provider instead and the best part is because we use an interface and it has the exact same API I didn't have to change literally anything else on this page now I know this is a really simple API so I got pretty lucky here but that's just a nice little touch so I'm going to go go ahead and run this and then we're going to see what it looks like when the plug-in's not there I'm going to drop the files in and we'll see how it ends up getting dynamically loaded all right with our application running if we jump over to HTML fragments which is the one plug-in type we have we actually don't have any plugins and that's because I haven't put any in the directory so if you recall that plug-in provider that we created should on Startup load anything that's there this implies
currently there are no dlls meaning that plugin type and then the second part what we're going to demonstrate is that we would add in what the file system Watcher picked up so I'm going to go ahead and drop those files in and we'll see what happened and all right they are dropped in but wait why didn't anything change and of course that's just because of how we implemented things so what we were able to do was actually tell the plugin provider to go load those plugins because the file system Watcher triggered but our Blazer app does not know that it needs to refresh so this brings us to yet another point that you want to consider for your application and that's what type of user experience do you expect in your application when a plug-ins dropped in I could have designed it such that when
you drop the plugin in the page refreshes or one of the components refreshes and in fact that might be a really cool example to follow up with but right now nothing actually changed on the screen that doesn't mean that nothing happened in the application it's just that there was no disruption to the user experience so if I were to go ahead and click on home and then I go back to HTML fragments you can see that it says there are one plugins now and yes this is the third or fourth video now I have not changed this to say plugin with s and parentheses like I said probably never changing it but you can see that we loaded in this new plugin and I didn't edit the video or anything to cut it out or jump but you saw me navigate to home and come
back and it was right there so that should demonstrate that we did dynamically load the plug-in and that's because that file system Watcher was able to get the event letter event handler handle it we loaded the assembly pulled in the plug-in types and then added those to a collection and the only way that we were able to get the UI to refresh was going to home and then when we click back on HTML fragments and it renders this page it actually had to go re-trigger to go ask for all of the plugins in your application that might be a totally acceptable user experience where you don't want to disrupt anything but in other cases you may want to actually consider dynamically changing the content on the screen that would have made for a way more interesting demo I totally get it but I did want
to actually be able to bring up these points to get you thinking alright that's going to wrap up Dynamic plug-in loading in Blazer if you haven't read my article on it I'll link that in the description so you can go ahead and check that out as well you might find that reading through it is a little bit easier to understand you can take your time there's also some code that you can copy directly out this should be what we talked about it actually highlights that case where you have a scope from your parent application it just doesn't give you the code that's going to show you how to get that scope because again that's probably a good exercise for you to figure out and how that should look in your own app if you want to be able to provide plugins with dependencies from your
parent application so as you can see there's a ton that we can explore with plugins inside of asp.net core and Blazer or any other type of application You're Building I should do a follow-up video where we dynamically change the screen when the plugins are dropped in so I'll try to give that a shot but hopefully for now that gets the gears turning in your head and you can think about how you want to implement your plugins thanks for watching and we'll see you next time
Frequently Asked Questions
What is dynamic plugin loading in ASP.NET Core Blazor?
Dynamic plugin loading allows you to load plugins at runtime rather than only at application startup. This means that you can add or modify plugins while the application is running, which can enhance the flexibility and functionality of your application.
How does the file system watcher work in the context of loading plugins?
The file system watcher monitors a specified directory for changes, such as when a new plugin DLL is added. When it detects a new file that matches the criteria, it triggers an event that allows the application to load and register the new plugin dynamically.
Can plugins access dependencies from the core application?
By default, plugins do not have access to the core application's dependencies because they are registered in a separate container. However, you can design your application to share dependencies by creating a child container that inherits from the core application's container.
These FAQs were generated by AI from the video transcript.