Introduction to Plugin Architecture in C#

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome to the Rock coding YouTube channel my name is Anton and today we're going to be learning about plug-in architecture in c-sharp plugins and plug-in architecture is essentially being able to extend your application while at runtime so for example if you have Visual Studio vs code or writer to edit code you can have your extensions or plugins to add more functionality to it if you've ever used WordPress you know the power of plugins and perhaps you can take it as far as saying Cloud functions because the cloud is just one big computer you're taking your code and you're extending the cloud as always don't forget if you're enjoying the video leave a like And subscribe that helps out the channel massively if you have any questions leave them in the comments section don't forget to check out the description I have a course that is out if you want to know c-sharp as I do it I'm sure it will be useful to you with that let's go ahead and get started here we have three projects the server this is the application that we're going to be extending we then have an endpoint pdk so usually you would have an SDK a software development kit here because we're developing plugins it's a plug-in development kit hence pdk let's quickly take a look at this and we really have super simple two components I plug in endpoint this is the thing which is going to essentially Mark a entry point to a plugin and then we have a path attribute because we essentially want to add endpoints to our server by the way that is not the only way that you can extend you if you can write a plugin you can extend your server in any way that you design here the design is specifically we want to be able to add endpoints to our asp.net core application so hence the plugin endpoint and the path attribute these are the only two things that we have here the endpoint pdk also has a reference to the framework so we can actually have HTTP context in here okay we then have the server and the server references the plugin development kit nothing else in in here and it is pretty much an empty template now to skip some time of me writing out code here is an example endpoint that we essentially want to add to our server at runtime the test endpoint has a project reference to endpoint pdk and if you're developing a plug-in this setting is important because basically when you're going to build your plugin it is only going to Output the dll for the individual assembly it is not going to include the endpoint pdk dll if you remove private it's going to be included in the output artifact and may collide with the one that is included in the server with all of that let's go ahead and go to our program Cs and we're going to think about how can we possibly add more endpoints to our application perhaps after we start our application we hold a reference to the app and we do something like map get and long story short that is not going to work as soon as you run your application you cannot add more endpoints that way so what we're going to do is we're going to write custom middleware so use middleware we will say plugin middleware create this type semicolon over here and because I can't remember all the types that are needed for the middleware we're going to go to use Authentication go inside of here the authentication middleware we're going to copy the request delegate that we need in the Constructor so ctor place that over here this is going to be the next function that is going to be executing and then we just need to match the signature of this method so a method that returns a task and accepts an HTTP context so we grab this function place it over here give it a body and now we have to fill it out let's get rid of this use authentication over here before we dive straight into loading the plugin Etc we can play about with this and get a feel for how this is going to work first of all let's say that we're going to have some kind of context that's going to have a request and it's going to have a path if this path is equal to something like plugin slash test we want to go ahead and well write something to the response so write async this will be test we are going to await here and then whether this has triggered or not we can basically catch all the scenarios if we go to context a response and then there is a has started flag if the response hasn't started then we actually want to fall through to the next so we're gonna take next invoke it pass the context down in there and oh wait so if we hit next we're gonna hit hello world let's go ahead and start this up so opening this dot watch here we see hello world if we go to plug and test and that actually says plug test we want plug-in test so let me amend this a little bit plugin and there we can see test now essentially what we want to do is instead of hard coding this here we want to be able to load up this class over here so what do we do we're going to open test endpoint over here we're gonna delete the assemblies just to be sure and then we're gonna build this so.net build this is going to Reaper produce the artifacts with the latest information and all we need to do is just have a path to this assembly the process of installation of a plugin is essentially just taking artifacts and putting them in the correct place in order for it to be load and then perhaps telling the system go over here and load this stuff up so let's go ahead and grab the full path we're going to drop down the terminal we're going to come back to program Cs and we're gonna say here is the path to the full assembly we can then use assembly load from and this is going to accept a path and now we have an assembly object and from here on out if you know reflection you know what you're doing you can go to assembly you can get type let's say that we're looking after the test endpoint and whatever class I gave it so an end point in the same namespace this is going to be end point we're then going to have the path information from this endpoint get custom attribute this is going to be path attribute and by the way just for Clarity if I open a path attribute this is the same path attribute that is coming from this endpoint BDK okay to check for Noble we can place a question mark over here if path info doesn't equal null or we can just say method and path equals to that of the context request so let me scroll down and quickly skipped past this part and there we have it if path info is a null and the method in the path are basically equal and we ignore the cases we want to go ahead take this endpoint we want to activate it so we're going to go to the activator we're going to create an instance of this type and this is going to be a plug-in endpoint so for actual endpoint this we can call it type so endpoint type replace it here and here then on the individual endpoint we can execute it and pass the HTTP context into there wait and that should be good so the application at this point should restart if I come back over here and actually let me remind my self of the route it was plug test so coming back over here we're gonna remove the i n and go to plug test and we get test if we duplicate this and I'm just going to go to the root route here we're still getting hello world so we've managed to successfully plug in our additional functionality now if we come back and we're at this plugable endpoint we change it we go to rebuild it we're still pointing to the same dll if we come back and we well refresh essentially it's gonna break if we come back over here and we say let's reload the app the app restarts so it's working again and again if we hit the plug test endpoint we can see the change so the behavior that we're seeing over here is once we have managed to load an assembly and its types into our application once it is very hard to load it a second time because there is something around basically being able able to identify that that dll is that unique thing and we cannot load it more than once so that's basically the new ones how do we walk around this what can we do well first of all we have a special type called an assembly below the context and this type specifically is giving us a scope in which assemblies are loaded so instead of just having your whole application This Global namespace This Global application essentially your assembly is like a drop added to this pool and essentially diluted through it you're putting a little space in there that's saying this is where I'm going to be adding my new assembly and then you can go ahead and remove it as well and that is what the assembly load context is so let's go ahead and create a new assembly load context and here in the Constructor you're gonna see that you can provide a name to it and then you can mark it as is collectible or not if you mark it as is collectible that gives you the ability to actually unload the assemblies that you have loaded for the name we're just going to give it the path and then for is collectible we're gonna say that it is true let's say that this is the load context and now instead of using assembly load from and by the way if you go into the load from method you're gonna see that it's actually using an assembly load context internally it's just not using a separate one it's using the default one coming back to program CS we can go to the load context use it and say load from assembly path this is still going to give us an assembly and finally let's say that we're gonna wrap this whole thing and try catch or more of a try finally and by the end this whole thing is done we are going to call unload and unload behave slightly weirdly although also logically and we're going to talk about this in just a second let's remove this space over here and go over the code once more so we understand what it does we create this scope for where we are going to load up an assembly so we have this context and we're maintaining a reference to this place where we are going to be loading assemblies we go ahead load the assembly we do exactly the same thing as we were doing before and then after we've managed to execute it or not because we already did the loading we're just going to unload and by the way we're doing this for every request this is not a production ready solution we are just trying to understand what the heck is actually going on here with what we currently have let's go ahead open up the terminal it's going to restart we're going to come back to this endpoint over here I'm going to refresh and we're still going to see the same extended endpoint coming back over here we're going to go to test endpoint we're gonna change it back to test we're gonna rebuild we're gonna come back and we're gonna refresh and we see the signature is incorrect error the logic behind why it displays the error of the signature is incorrect I won't be able to explain to you however I will be able to tell you why this is happening the reason this is happening is because in program CS when we're calling unload over here it doesn't actually instantly unload anything imagine this so you are loading assemblies from a file you are essentially having this compiled c-sharp code you are bringing it into your own application and then at some point you're saying unload what that is going to do is just remove references and if you're somehow still referencing any of that code the unloading is not going to happen and it's actually only going to occur when the garbage collection runs so unload over here even though it's a synchronous method it's more of a like remove markers the actual unloading is going to happen when the garbage collection is going to run so if I open a memory profiler and I reattach to the server process over here and I'm going to force garbage collection and then I'm going to come back over here and refresh the endpoint we now see the change so when you want to actually unload a plugin and reload it you actually need to force garbage collection and collect the old plugin from memory if we come back to the code and we take a look at this we're loading a type from this assembly if we take this type and we stick it somewhere on the plug-in middleware which outlives the unloading of assembly the assembly is never going to unload this is why if you are uninstalling an extension or you're removing a plug-in it's going to ask you please restart because perhaps for performance reasons the main application needs to take the things inside the plugin and dilute it in its essentially Global space so furthermore what can we do about this let's go ahead and first of all create a private static task where we're going to process us the HTTP context so CTX we're going to take this whole Malarkey place it over here inside this body so we're working directly with this everywhere where we have CTX or context let's go ahead and replace it make the function asynchronous and we're going to call process right over here on the context and await on it give it a little bit of space the first reason for taking out the logic into its own method is because of the stack we have this endpoint type variable and it's essentially capturing this type so until we actually exit the method even though we have unloaded so if we would try to force garbage collection over here nothing would happen because the stack is still keeping a reference to this type over here so before we can actually start garbage collection we need to do it outside we need to exit this method to clear all the references from the stack something that you need to be aware of also in this situation is you can supply and an attribute Mark that is basically called method implementation attribute and then here you can specify things like aggressive inline aggressive optimizations and think about it like this if the dotnet runtime deems it necessary or just I don't know feels like or basically decides to it's going to say there is no method it's going to take the code that is over here and it's just going to execute it as part of this method that basically means even if we're going to put the garbage collection logic over here if we're not going to Define this boundary of a method explicitly we may be holding on to this reference a little bit longer than we intended to so let's say that method implementation no inlining again inlining means taking all of the instructions over here and essentially inlining them into this invoke method another way of giving it this guarantee essentially we can go to the garbage collector we can collect and another thing that we can call is finalizer's weight for pending finalizers if you're wondering what a finalizer is essentially on classes you will have things like destructors so something that looks like this without any parameters and when the object is actually being garbage collected this is what is going to run this finalizer is essentially going to get called sometime after the actual garbage collection runs and it's going to be placed on the finalizer queue where one after another the objects which are being garbage collected will get their finalizers called one by one so here we're basically just saying everything that we have marked as garbage collected or that is being garbage collected wait for all of those finalizers to be called okay so we're doing this we're still flying a little bit blind because we don't have any insurance whether anything has been cleared from memory one way that we can double check that stuff has actually been unloaded is using a week not week week reference to the load context so a strong reference is going to prevent garbage collection week reference doesn't and it just says is there still an object on the endless let's take the weak reference return it from this process we will then have it over here so assembly load context reference we can take this and let's say We'll place a for each Loop we'll retry 10 times while I is less than 10 and the reference is alive we want to go ahead and try garbage collecting by the end of this we can go to the console right line and say unloading successful always forget that s over there take the LC ref and say is alive and we'll say not so if it's not alive it's successful if it is alive then it's unsuccessful with what we have over here let's come back wait for the application to restart let's double check that everything is working so we're just going to refresh the endpoint come back over here and the unloading is successful we can take a quick step over here and say what kind of unloading attempt is it to just to satisfy our curiosity how many times are we forcing garbage collection to run before the stuff actually gets unloaded there we have it let's go ahead and refresh and looks like two times again refresh and another two times why does it take two times I don't know which generation is it trying to collect I don't know as well I don't really care all that much again the loading and unloading of the plugin is going to be slow it's really the experience of once the plugin is loaded how well can you use it and can you actually update the plugin to well update the plugin because updating stuff actually matters so we're capable of unloading the plugin by the end of it let's drop down the terminal over here go to the test endpoint add some more changes we are going to rebuild the application come back over here refresh and the plugin automatically updates one last thing that I wanted to highlight over here is the point about references again so we've seen how if we are essentially keeping references to something the unloading doesn't happen and we actually need to run a garbage collector so how can you essentially leak a reference so you can prevent your assembly from being unloaded and the points about this can be very subtle for example if we go to the test endpoint and we're going to say look we're gonna have some kind of Json string over here and we're going to use the Json serializer to serialize some the object where we're gonna have a message and say yo okay we're gonna take the Json string that is what we're going to write over here and if we're smart you know we're going to use write as Json async we're going to take this object and we're going to put it here okay and Json serializer you don't even see it it's not even there right this is what you're using all fine and dandy let's go ahead over here re-run the build come back to the application refresh and everything is working fine no problem we come back we add all of our changes we rebuilt we come back we refresh and the signature is incorrect if we come back to the application over here we're gonna see that we've went through all of our attempts and the unloading was unsuccessful if you didn't know now you're basically going to know the Json serializer from system.text.json I don't know about the Newton soft one essentially the type that you're trying to serialize that is going to get cached so effectively the Json serializer coming from this Global namespace down into your plugin and if you're utilizing it in your plugin through your plugin your leaking references into the global space so this is how easy it is to mess this stuff up my recommendations for plugins even though it's sometimes not going to be possible you want those to be pure functions a pure function is a function without side effects although this being essentially an internal solution for Microsoft I think I haven't looked far enough but I think through Json serialization options you can actually go ahead and disable it however this is going to be the end of this video thank you very much for watching hopefully this gives you a good starting point on how to write a plugin and what to watch out for or rather than writing a plugin essentially utilizing plugin architecture in your c-sharp application please note it's not fully production ready we've left the loading and unloading of the plugin in the Middle where you want to take it out there is a class like system file Watcher you want to watch the plugins folder for changes if there is a change emits an event try to unload disable usage of the plugin you know you basically have to do all that crazy ceremony as you usually do with edge cases Etc what if you can't unload the plugin what do you do in that scenario you know voice your scenarios and test them as always if you enjoyed the video don't forget to leave a like subscribe if you have any questions leave them in the comment section if you would like the code for this video as well as my other videos please come support me on patreon I will really appreciate it a very very big and special thank you to all my current patreon supporters your help is very appreciated don't forget to check the description for extra documentation that you can use around this topic as always thank you for watching and have a good day
Info
Channel: Raw Coding
Views: 19,050
Rating: undefined out of 5
Keywords: csharp, c#, plugin, architecture, design pattern, plugin architecture, dotnet, .net, asp.net core, dynamic, assembly, AssemblyLoadContext, unload
Id: g4idDjBICO8
Channel Id: undefined
Length: 21min 25sec (1285 seconds)
Published: Sun Apr 09 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.