BrandGhost

One Trick To Make ASP.NET Core Blazor Plugin Loading EVEN BETTER

You have post-startup ASP.NET Core Blazor plugin loading working like a charm... But how can you make it even better? Follow along in this tutorial to make your UIs even snappier! 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 your inbox, so you can start your weekend learning off strong: https://www.de...
View Transcript
all right you've started enhancing your Blazer applications by using a plug-in architecture great first step from there you had a use case in your application where you decided I need to have plugins load after startup and not only at startup time and this has worked okay-ish because you watched the last video I put out on this and you had it working in a similar way but the side effect of that was that when a plug-in was dropped in it didn't automatically refresh your UI now depending where you are in your application development this type of behavior might be totally acceptable but we can probably do better can't we in this video I'm going to build on the previous plug-in architecture video for Blazer where we were dynamically loading plugins and this time we're going to automatically refresh the user interface as soon as a plugin is loaded if you haven't watched the previous videos in the series I highly recommend you check them out first so I'll put a link to them right up here so you can watch that come right back and continue on because the context I think will help a lot otherwise enough blabbing let's get over to visual studio okay okay so on my screen I have the class from the previous video that we created that's for late loading plugins so basically using a file system Watcher discovering any changes and then being able to load those plugins in and then change the collection where our plugins are actually available so line 20 to 24 is that file system Watcher we have that all connected in this Watcher created event handler that's from line 38 and onward and then that get plugins method like I mentioned is going to combine the base plugins loaded at startup this part and then adding in the late loaded plugins right here now in order to make this work in Blazer what we're going to do is leverage events and event handlers just like we did for the file system Watcher where we had this Watcher created event handler instead we're going to actually create our own event and have another event handler as well specifically to manage when that plug-in list is changed and right now the only time this plugin list can change is technically two spots one of them is going to be OnStar it up and the second time is when we have a file created that matches the plugin pattern and we load that plugin which is really inside of this method now on Startup for our application I'm not actually interested in looking for an event change because there could be a race condition technically depending on how dependencies are resolved so if by the time our UI goes to populate if the plugins that we were interested and actually already loaded and our event that we're about to go create has already fired for these initial plugins technically we might miss that so instead of having to worry about the initial plugins on Startup our UI is basically just going to ask for a refresh once as soon as it loads and then afterwards it will subscribe to that event so when I jump over to the UI I'll re-explain that but that's the pattern that we're going to be looking for so as you can see on line 27 I've added an event and it uses the syntax if you're not familiar with it it's actually not super common anymore unless you're doing a lot of UI work this used to be pretty big and and win forms and WPF so depending on how much UI work you're doing this is still kind of common but otherwise it's not used a whole lot so the syntax is you have this event keyword and then this is the delegate syntax so it's going to follow an event handler which is uh like we can see actually on line 38 here through 40. an event handler takes in this object to sender and that's going to be the thing that's raising the event and then a type of event args and in this case I'm just saying we don't have any special event args I'm just using the base event Arch class so nothing totally fancy here but this actually allows us to have the same syntax that we see on line 24 where we can use a plus equals to provide an event handler and these event handlers every time you call plus equals you can actually keep chaining handlers together so if we were to do something like this and copy and paste this line I know we're talking about a different event and event handler here than this one but just to illustrate it you would hook up one Handler here and then you'd actually hook up the same name Handler and if that event fired it would execute that Handler twice so it gives you a chaining syntax if you want to use it you can use minus equals to take away an event handler but that's the idea behind this syntax if you're not totally familiar with it so now that we've declared an event and the type of handlers that we're allowed to have hooked up to it where we want to actually invoke this is inside of this Watcher created method so when this Handler fires and we've actually modified our collection of plugins that's where we're going to do this invocation right here so this is going to raise the event and any handlers that are registered will be triggered a quick note you'll notice I do have a null check here before the invoke and that's because if there are no handlers this will actually be null and that means if no one subscribed to this and you didn't have that question mark to check for null this could actually throw an exception it's usually not the case that you want to have that happen you want to be able to kind of support no one listening to your event or many people so we put the null check in place before we invoke okay so that's mostly it for this class not really that exciting except for this new event that we've wired up here but I'm going to explain what I wanted to do next for the UI so to explain how that's going to work I was taking into consideration that I wanted to start doing something kind of like MVC where I have the view and I wanted to have a model as well and in order to make that work instead of just wiring up directly to this late load plug-in provider to me this kind of seemed like a more of a back end not really ui-centric class I know this might be like a premature kind of refactor optimization but I wanted to make it a little bit more clean in terms of uh where my head was at so I created this plug-in list model and using like a model naming convention this is going to be something that our UI control is actually able to have knowledge about so it's kind of like a view model if you're familiar with that kind of pattern so our view model what it's going to do is have the API or the the syntax we want to work with from inside of our Blazer control so what does that mean well we're going to actually take in that late load plug-in provider and essentially we're going to wrap it in an API that we want to work with so I figured when I was getting started with this I wanted to do some type of binding so I figured I would use the I notify property changed interface this is something if you've built user interfaces before things that are supporting binding actually have uh this interface and this event on them as well and that way things that are interested in understanding when a value on a property has changed they can listen to this event so they can give their own event handler check for the properties that they're interested in changing and then do some UI updates a lot of this happens behind the scenes so generally we're not thinking about the nitty-gritty of this but if you're making a model and you want to have bindable properties on it this is something that you would create and then anytime the property that you're exposing changes you would just invoke property changed and to show you where that's happening basically on line 20 I've hooked up to the plugin providers plug-in changed event so that's the event that we just walk through in the other class and I was explaining the purpose of it and we're hooking up to that and we're going to raise yet another event but this one is more specific to like this idea of a model and view because property changed events are actually used a lot in user interfaces we didn't have to do this by any means I was actually hoping that I was going to get the binding syntax working how I wanted but I think I need a little bit more experience with blazer to really uh polish that off so I apologize but this um ended up being a bit of extra work for not a lot of good reason because I was hoping that this property change would just while you're up to The Binding very cleanly it doesn't necessarily but I can still walk through how it's used what the intentions were and in a sort of perfect world what the expectation was for this working so when this event handler fires were basically just raising another event but we're calling it property changed and it has property changed events and it's going to indicate that anyone is listening to this will know that plugins so the name of this property has changed this model only has one property on it which is the plugins list but in other view models that you might create in your user interface you might have other properties that could be bound to and they're changing as well so for example maybe you want to have like a plugins list like the one we have here on line 17 maybe you want to have a bindable property for the count of plugins so that's another property changed event that you could raise when the plugin providers list of plugins changes so you have a bunch of options but we're just going with this for now something else to call out is that on line 12 we're registering this event handler that I was just explaining and you'll notice that we don't have any code with a minus equals to get rid of it that's actually the same if I jump back to this late load plugin provider you'll notice that even on the Watcher we only hooked up the event handler and we've never actually unhooked it now in theory you want to think about being safe when you're hooking up event handlers so if the lifetime of this object is such that it's going to end before the application you want to be able to unhook the events so that you don't have these lingering handlers because they will cause memory leaks if you're not unhooking them properly and you have object lifetimes that don't really match up in our case this class that we're looking at and the plug-in list model that I was just explaining these are going to exist for the lifetime of the application currently this is how I'm designing the application your application and needs in your application May may be very different so I just wanted to point out that if you're looking at this copying the same pattern you may want to think about unsubscribing from that event when I jump over to the user interface we will see an example of this and I'll touch on it again one more thing before jumping over to the Razer page it's just that in the plugin module we now have to register this plugin list model again nothing very fancy here it's just like the other plug-in providers technically this is just like a plug-in provider itself but we're giving it a different API that is more view Centric again this was a little bit of extra work not totally needed because I was hoping The Binding was going to work but it didn't so we could have got away with just making this class here late load plugin provider use that new event that we were talking about right in the start okay over to the razor page so the first thing I'll call out is that on the Razer page we are now passing in this plug-in list model it's still taking the same type of plugins and what we're going to do is act actually look at the code that's at the bottom of this and to call out no changes here first I think that's important we didn't have to change anything in the actual rendering of the control that's all the same what I was hoping to do and someone watching this that has more experience in Blazer might think it's pretty obvious I've just missed it so far but I was hoping I could have done something like binding the actual list of plugins so if I scroll back up this plugin provider has that dot plugins property I was hoping that I could have a binding somewhere on one of these elements in the UI and then when that plug-in list updates because of the I notify property changed I could have just had the UI refresh right from that and it would know how to pull out the fragments from that I know how to do this in WPF but I don't think I could get it working in Blazer right away so I'll have to revisit that so this was going to be the code that changed changed but in reality that's not what I ended up with so the code down here is actually what changed you'll notice I have a dispose method and like I was just talking about we do have this unhooking of the event handler so in order to make this work I did have to sorry I'm scrolling to the top I did have to have this implements eye disposable here so just calling that out because I skipped over it now we have this dispose method where we unhook and on initialize has changed a little bit so not only are we hooking up to that event handler on on initialize but what we're doing is actually calling a refresh and that's the same code we had before I've just pulled it out into this method so all of this code from line 58 to 75 used to be right here on line 54 but the reason I've pulled that out is because we actually want to call it when we have the plug-in collection changed and if we look at the event handler that's on line 77 there's a whole bunch of stuff going on here that I'm going to I have to explain so if your eyes are jumping to specific things and you're going holy crap why is he doing that just give me a moment and I'll touch on it a little bit more so this event handler is for the property changed event and this is going to be something that we're only interested in when that plug-ins property is changing this kind of pattern I'm not a huge fan of because you end up having like this situation where the more properties you have on your view model you have like else if else if else if like it's really nasty we can clean this up with a lookup or something as well to use a dictionary here but in general I'm not a huge fan of doing custom checks for property changed events and that's why binding just works really nice you don't have to look and Implement custom things like this so just wanted to call that out where to start on this so high level what we're doing is looking specifically for this Pro property that's changed and when it happens this method here refresh plugins is going to actually try to get our UI to have the new elements okay so what's going on with the rest of it what's all this extra code why is there a fix me let's start from the top so on line 77 you'll notice there's a special word here for async and async and event handlers do not mix well together some of the first articles I was putting out this year on my website were about async void and it's a pretty dangerous pattern I made YouTube videos on this as well I'll link that above and the idea is that when you have async void what happens is that the tasks if they throw exceptions there's no you're kind of at a boundary here so the calling code into this method is not able to properly handle exceptions that bubble up so if an exception were to be thrown anywhere in this method it's going to have some nasty side effects now I'm not sure if it's still official Microsoft recommendation or not but you know I've seen at some point over the years like async void the only time that people say it's acceptable is on event handlers and that's because the Syntax for hooking up an event handler actually requires that you have a void there again in some of the Articles I've written I've shown that you can have task-based event handlers but really all of the sort of workarounds for this are pretty nasty in the end the most simple thing is kind of silly or ridiculous as it looks is really to wrap it in a try catch and no you probably don't just want an empty catch like this this isn't really helpful but you would do your error handling here and the the point is that you want to prevent any type of exception from bubbling out of this async event handler because it has an async void that's not totally related to this plug-in loading I just want to explain what you're seeing on screen so that if you go to copy this code and you didn't understand that and you didn't want to copy it just be aware that some funny stuff can happen and then the next thing I want to call out because there's two more things here one is that we're using this invoke async which um I mean based on everything I was saying maybe sounds a little bit confusing but this is because the file system Watcher that's going to pick up when there's a plug-in change on disk is actually not running on the UI thread and you might say okay that's fine that's not what I would have expected anyway and you're right I wouldn't expect that either but when we want to go change the user interface we have to make sure that we're working on the UI thread so at some point when we're going from that file system Watchers event being raised we have to make sure we get to the UI thread to do the refresh now depending on how you structure your code you may want to move where this is so this invoke async maybe you want to be able to do that from you know not around each property that's being changed maybe you want that at the top of the method I'll leave that up for you to make decisions about I think you could argue right or wrong many different ways but the point is on line 86 we need to run some code specifically on the UI thread that's going to be these two lines here 88 and 89 and that brings us to line 89 which is the last one that I haven't talked about so line 89 is going to be the thing that actually tells our Razer page to update it's going to tell it that there's been some State that's changed if I don't include that all of the code runs does what you might expect it actually refreshes the plug-in list behind the scenes so in the code so I can follow with with break points but the user interface itself doesn't change and that's because the Razer page isn't aware that that state has actually changed so we're forcefully telling it that all of this code that I just walk through from line 77 down to 97 that I'm highlighting here all of this was because I couldn't actually make binding work how I expected and if I can figure out how to make binding work the way that I think it is supposed to work then all of this code can go away so if you're watching this you know how to do that obviously mention it in the comments otherwise I got some homework to do and more videos to create okay you're probably sick of me blabbing about this and you just want to see it work so let's jump over to the Blazer app and I'll show you all of the awesomeness all right I have the Blazer app launched if I go to HTML fragments we will see there are zero plugins now in order to make this feel magical what I'm going to do is try to keep talking so that you know or can hopefully believe that I'm not going to edit this video and at the same time off screen I'm going to drag and drop the plugins into the right folder and you should see this update so all I'm doing right now is navigating to that folder I've copied them to my clipboard I'm about to paste them but I'm going to keep talking and paste them and there you go they've just updated in the user interface so unless I have really good editing skills and um I don't unfortunately I hope you believe that that just updated on the fly without any fancy editing now what would be really cool and another follow-up video to come is that if we could remove these plugins what would happen now that we have the UI updating on the fly it would be really cool to see these come and go if we were adding or removing files so in one of the last videos I actually had a great comment so if you've gone and watched that and scroll through the comments someone left a a pretty great comment about some thoughts about you know what does it mean to have plugins loading after startup like how does that complicate the life cycle how does how do you have to factor in removing things like what are the concerns and risks of that and they're spot on like there's a lot to consider if you want to be able to have plugins exist for not the same lifetime as your rent entire application it's one thing if you add them in after the fact and they live until the end of the app but if you want to remove them before the end how do you clean that up how do you make sure that they didn't register event handlers and you know there's going to be lingering code um how do we safely unload them because right now those plugins the dlls are actually being used by the process so I can't even delete them off disk if I wanted to right now I'd have to do some steps beforehand so I want to explore that and show you in a follow-up video but hopefully that was pretty cool that you could see that update okay so we just walked through some Blazer code that shows you the UI updating on the Fly no fancy editing I promise and all we had to do was drop the plugins into the bin directory and so we're building on the last video to use the file system Watcher and what I demonstrated in this video is with a couple of events we were actually able to raise an event and add a Handler later on so that our user interface our Razer page can actually take some action when that happens now I mentioned that I couldn't get binding to work the way I wanted to but that was the point of that I notify property changed model that we had that was the whole purpose of it and when we went through the Razer page I tried to show you where I wanted to put the binding instead we went to the code section of the Razer page and actually implemented The Binding herself so it's a little bit gnarly but we hook up to the I notify property change and event ourselves then we actually convert the plugins to pull out their HTML fragments that's what that plug-in was originally for and then we had to make sure that we were running on the UI thread and we also had to make sure that we were telling the UI that the state had changed so all of those things were necessary to sort of get the same effect as binding so I hope you found that useful so going through this video I definitely have a few more ideas for Content that I should put together to try and round out all of uh you know the different topics we've been going over including plug-in unloading so stay tuned for that if you enjoyed this please subscribe to the channel I definitely appreciate it love hearing requests and different topics and comments if you want to talk through different implications of what you're seeing that would be great and yeah I hope you enjoyed this and we'll see you next time thank you

Frequently Asked Questions

What is the main purpose of this video?

In this video, I'm demonstrating how to enhance the Blazor application by implementing a plugin architecture that allows plugins to be loaded dynamically after startup and automatically refresh the user interface when a new plugin is added.

Why do I need to watch the previous videos before this one?

I highly recommend watching the previous videos in the series because they provide essential context and foundational knowledge that will help you understand the concepts and code presented in this video.

What should I do if I encounter issues with the UI not updating after loading plugins?

If you're having trouble with the UI not updating, make sure that you're correctly invoking the UI refresh method after loading the plugins and that you're handling events properly to notify the UI of any changes.

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