Introduction to Plugin Architecture in C#

Raw Coding
9 Apr 202321:25

Summary

TLDRIn this Rock Coding YouTube tutorial, Anton teaches viewers about plugin architecture in C#, focusing on extending applications at runtime. He explains how plugins work using examples like Visual Studio and WordPress, then dives into creating a plugin system for an ASP.NET Core application. Anton demonstrates building a server, a plugin development kit (PDK), and a custom middleware to load and unload plugins dynamically. He also addresses challenges like unloading plugins and avoiding memory leaks, providing practical insights into plugin development in C#.

Takeaways

  • 📘 Plugin architecture in C# allows for extending applications at runtime, similar to how extensions work in Visual Studio, VS Code, or WordPress plugins.
  • 🔌 The presenter introduces a 'Plugin Development Kit (PDK)' which is a specialized SDK for creating plugins, including components like 'IPluginEndpoint' and 'PathAttribute'.
  • 🏗️ The server project is designed to be extended with plugins, and it references the PDK to incorporate HTTP context functionalities.
  • 🛠️ Custom middleware is created to handle plugin loading and execution, enabling dynamic addition of endpoints to an ASP.NET Core application.
  • 🔄 The process of loading and unloading plugins involves using reflection to instantiate types and execute methods based on the plugin's assembly.
  • 🧩 The concept of 'Assembly Load Context' is highlighted as a way to manage the scope of assembly loading, allowing for the possibility of unloading assemblies.
  • 🗑️ Unloading plugins is not immediate and requires the use of garbage collection to clear references and finalize the unloading process.
  • 🔄 The presenter demonstrates the use of a weak reference to an assembly load context to check if an assembly has been successfully unloaded.
  • 🛑 The importance of managing references carefully is emphasized to avoid preventing assemblies from being unloaded due to lingering references.
  • 🚫 The script mentions that certain operations, like serialization with the JSON serializer, can inadvertently leak references and affect the unloading process.
  • 🔧 The video concludes with recommendations for plugin development, such as aiming for pure functions and being cautious of side effects that could impact the plugin lifecycle.

Q & A

  • What is plugin architecture in the context of C#?

    -Plugin architecture in C# refers to the ability to extend an application at runtime, similar to how extensions or plugins add functionality to applications like Visual Studio or WordPress.

  • Why is plugin architecture useful in application development?

    -Plugin architecture is useful because it allows developers to add new features and functionality to an application without modifying the core application code, providing flexibility and extensibility.

  • What is the role of a Plugin Development Kit (PDK) in creating plugins?

    -A Plugin Development Kit (PDK) provides the necessary components and interfaces for developers to create plugins. It includes elements like 'IPluginEndpoint' and 'PathAttribute' to define entry points and routing for plugins.

  • How does the 'PathAttribute' in the PDK contribute to plugin development?

    -'PathAttribute' in the PDK is used to specify the endpoint path for a plugin, allowing the server to recognize and route requests to the appropriate plugin functionality.

  • What is the purpose of the 'EndpointPDK' in the server project?

    -The 'EndpointPDK' in the server project references the framework and provides the ability to include HTTP context within the plugin, enabling the plugin to interact with the server's HTTP requests and responses.

  • Can you explain the custom middleware mentioned in the script for handling plugins?

    -The custom middleware, referred to as 'Plugin Middleware', is used to dynamically load and execute plugin code during runtime. It matches incoming requests to plugin endpoints and activates the corresponding plugin functionality.

  • What is the significance of unloading assemblies in a plugin architecture?

    -Unloading assemblies is important for updating or removing plugins without restarting the entire application. It allows for dynamic changes to the application's functionality.

  • Why is it challenging to unload a plugin in a C# application?

    -Unloading a plugin can be challenging due to reference leaks where parts of the plugin may still be referenced by the application, preventing the garbage collector from unloading the assembly.

  • What is the role of 'AssemblyLoadContext' in managing plugin assemblies?

    -'AssemblyLoadContext' provides a way to load and unload assemblies independently from the main application domain. It allows for creating a separate scope for plugin assemblies, facilitating their dynamic loading and unloading.

  • How can you ensure that a plugin is properly unloaded from an application?

    -To ensure a plugin is properly unloaded, you can use techniques like forcing garbage collection, waiting for finalizers to run, and using weak references to check if the assembly has been collected.

  • What are some potential issues with using the 'System.Text.Json' serializer in plugins?

    -The 'System.Text.Json' serializer can cause reference leaks because it caches the types it serializes, which can prevent the plugin assembly from being unloaded if the serializer holds references to plugin types.

Outlines

00:00

😀 Introduction to Plugin Architecture in C#

Anton, the host of the Rock Coding YouTube channel, introduces the topic of plugin architecture in C#. He explains that plugins allow for extending applications at runtime, using examples like Visual Studio, VS Code, and WordPress. He also touches on cloud functions as a form of plugin architecture. Anton invites viewers to like, subscribe, and ask questions in the comments. He promotes a C# course and outlines the structure of the video, which includes three projects: a server, an endpoint PDK (Plugin Development Kit), and an example endpoint. The server is the extendable application, while the PDK provides the necessary components for plugin development, such as an entry point and a path attribute for adding endpoints to an ASP.NET Core application.

05:00

🛠️ Building a Custom Middleware for Plugins

The video script delves into the technical process of creating custom middleware to dynamically add endpoints to a server application. Anton discusses the limitations of ASP.NET Core's default behavior, which does not allow for adding endpoints post-startup. To overcome this, he demonstrates how to write a middleware that can load and execute plugin endpoints at runtime. This involves creating a 'Plugin Middleware' class, defining a request delegate, and implementing a method to match HTTP requests with plugin endpoints. The middleware is designed to intercept requests to specific paths and execute the corresponding plugin logic, showcasing a practical example with a 'test endpoint'.

10:02

🔄 Dynamically Loading and Unloading Assemblies

Anton explains the process of dynamically loading and unloading plugin assemblies in a C# application. He details the use of reflection to load assemblies from a specified path and to instantiate types that have been marked with a 'Path Attribute'. The script covers the technical steps to activate a plugin by creating an instance of the plugin type and executing it with the HTTP context. It also touches on the challenges of unloading assemblies, explaining that the 'AssemblyLoadContext' can be used to manage the lifecycle of dynamically loaded assemblies and enable their unloading by marking them as collectible.

15:02

🚫 Challenges with Unloading Plugins

The script discusses the difficulties associated with unloading plugins in C#. It explains that unloading is not immediate and is subject to garbage collection. Anton demonstrates how to force garbage collection to ensure that unloaded assemblies are removed from memory. He also highlights potential issues with references that can prevent assemblies from being unloaded, such as the use of the 'JsonSerializer' which caches types and can inadvertently hold references to plugin code. The video emphasizes the importance of managing references carefully to ensure successful unloading of plugins.

20:03

🔧 Best Practices for Plugin Development

In the final paragraph, Anton provides recommendations for developing plugins in C#. He suggests aiming for pure functions with no side effects to minimize the risk of leaking references and to ensure that plugins can be safely unloaded. The script wraps up with a reminder that the demonstrated solution is not production-ready and outlines further considerations for a complete plugin system, such as using a file watcher to automatically detect changes in the plugins folder and handle the loading and unloading of plugins. Anton concludes by thanking viewers for their support and inviting them to engage with the content through likes, subscriptions, and Patreon support.

Mindmap

Keywords

💡Plugin Architecture

Plugin architecture refers to a software design pattern that allows applications to be extended at runtime by adding modules or plugins. In the context of the video, it is the core concept being discussed, with the focus on how to implement this in C#. The script explains that plugins can extend functionality, similar to how extensions work in Visual Studio or VS Code, and how plugins can add new features to an ASP.NET Core application.

💡C#

C# is a programming language developed by Microsoft as part of its .NET initiative. The video script uses C# as the primary language for demonstrating how to create a plugin architecture. It is the language in which the server, plugin development kit (PDK), and the plugins themselves are written.

💡Visual Studio

Visual Studio is an integrated development environment (IDE) from Microsoft used for developing applications in various programming languages, including C#. In the script, it is mentioned as an example of an application that supports extensions or plugins to add more functionality.

💡ASP.NET Core

ASP.NET Core is an open-source, cross-platform framework for building modern, cloud-based, Internet-connected applications. The script discusses extending an ASP.NET Core application by adding endpoints through a plugin architecture, which is a key part of the video's tutorial.

💡Endpoint

In the context of web development, an endpoint refers to a single and specific destination in a network that is identified by an address. The script talks about creating a 'plugin endpoint' as a way to add new endpoints to a server dynamically, which is a central part of the plugin architecture being discussed.

💡Path Attribute

The Path Attribute in the script refers to a custom attribute used to mark the entry point for a plugin with a specific path. It is used in conjunction with the plugin endpoint to add new routes to the server, which is a key concept in the plugin architecture.

💡Middleware

Middleware in ASP.NET Core is software that is positioned between the server and the client, and is used to handle requests and responses. The script describes creating custom middleware to facilitate the loading and execution of plugins at runtime.

💡Assembly

In .NET, an assembly is a unit of code that is built, versioned, and deployed as a single implementation artifact. The video script discusses loading and unloading assemblies as part of the plugin architecture, which is essential for dynamically adding and updating functionality.

💡Reflection

Reflection is a feature in .NET that allows a program to examine and modify the structure and behavior of itself and other loaded assemblies at runtime. The script mentions using reflection to discover and instantiate types from loaded assemblies, which is a key technique in implementing the plugin architecture.

💡Garbage Collection

Garbage collection is the process of automatically freeing memory that is occupied by objects that are no longer in use. The script discusses the importance of garbage collection in unloading plugins, as it is necessary to clear references to allow the unloading process to occur.

💡Finalizer

A finalizer, or destructor, is a special method that runs when an object is being collected by the garbage collector. The script mentions finalizers in the context of ensuring that all resources are released when a plugin is unloaded.

💡System.Text.Json

System.Text.Json is a high-performance JSON library included in .NET Core and .NET 5+. The script warns about potential issues with serialization and caching when using this library, which can inadvertently hold references and prevent the unloading of plugins.

Highlights

Introduction to plugin architecture in C#, allowing applications to be extended at runtime.

Explanation of how plugins enhance functionality in applications like Visual Studio, VS Code, and WordPress.

The concept of cloud functions as a form of plugin architecture, extending cloud capabilities.

The importance of leaving likes and subscribing to the channel for support and updates.

Overview of the three projects involved in the plugin architecture: server, endpoint PDK, and test endpoint.

Description of the Plugin Development Kit (PDK) and its components for marking plugin entry points.

Details on the server project, which serves as the base application to be extended by plugins.

The process of creating custom middleware for dynamically adding endpoints to an ASP.NET Core application.

Demonstration of a test endpoint project reference to the endpoint PDK for plugin development.

Instructions on avoiding conflicts by correctly setting the 'Private' property in plugin assembly output.

The use of reflection for loading and executing plugin types at runtime.

The challenge of unloading plugins and the role of the AssemblyLoadContext in managing assembly loading and unloading.

Techniques for forcing garbage collection to unload plugins and the limitations of this approach.

The impact of method implementation attributes on the inlining of code and its effect on garbage collection.

The use of weak references to check if an assembly has been successfully unloaded.

The subtle ways references can leak and prevent plugin unloading, such as through JSON serialization.

Recommendations for creating pure functions within plugins to avoid side effects and reference leakage.

The need for a robust system to handle plugin updates and edge cases in a production environment.

Closing remarks, encouraging viewers to support the channel, ask questions, and check for additional resources.

Transcripts

play00:00

welcome to the Rock coding YouTube

play00:01

channel my name is Anton and today we're

play00:03

going to be learning about plug-in

play00:04

architecture in c-sharp plugins and

play00:06

plug-in architecture is essentially

play00:08

being able to extend your application

play00:10

while at runtime so for example if you

play00:13

have Visual Studio vs code or writer to

play00:16

edit code you can have your extensions

play00:19

or plugins to add more functionality to

play00:22

it if you've ever used WordPress you

play00:24

know the power of plugins and perhaps

play00:26

you can take it as far as saying Cloud

play00:28

functions because the cloud is just one

play00:31

big computer you're taking your code and

play00:33

you're extending the cloud as always

play00:35

don't forget if you're enjoying the

play00:37

video leave a like And subscribe that

play00:38

helps out the channel massively if you

play00:40

have any questions leave them in the

play00:41

comments section don't forget to check

play00:43

out the description I have a course that

play00:44

is out if you want to know c-sharp as I

play00:46

do it I'm sure it will be useful to you

play00:48

with that let's go ahead and get started

play00:50

here we have three projects the server

play00:54

this is the application that we're going

play00:55

to be extending we then have an endpoint

play00:58

pdk so usually you would have an SDK a

play01:02

software development kit here because

play01:04

we're developing plugins it's a plug-in

play01:07

development kit hence pdk let's quickly

play01:10

take a look at this and we really have

play01:12

super simple two components I plug in

play01:15

endpoint this is the thing which is

play01:17

going to essentially Mark a entry point

play01:20

to a plugin and then we have a path

play01:22

attribute because we essentially want to

play01:24

add endpoints to our server by the way

play01:27

that is not the only way that you can

play01:29

extend you if you can write a plugin you

play01:32

can extend your server in any way that

play01:34

you design here the design is

play01:36

specifically we want to be able to add

play01:38

endpoints to our asp.net core

play01:40

application so hence the plugin endpoint

play01:43

and the path attribute these are the

play01:44

only two things that we have here the

play01:47

endpoint pdk also has a reference to the

play01:49

framework so we can actually have HTTP

play01:52

context in here okay we then have the

play01:55

server and the server references the

play01:57

plugin development kit nothing else in

play01:59

in here and it is pretty much an empty

play02:02

template now to skip some time of me

play02:05

writing out code here is an example

play02:07

endpoint that we essentially want to add

play02:09

to our server at runtime the test

play02:12

endpoint has a project reference to

play02:14

endpoint pdk and if you're developing a

play02:17

plug-in this setting is important

play02:19

because basically when you're going to

play02:22

build your plugin it is only going to

play02:24

Output the dll for the individual

play02:26

assembly it is not going to include the

play02:28

endpoint pdk dll if you remove private

play02:31

it's going to be included in the output

play02:34

artifact and may collide with the one

play02:36

that is included in the server with all

play02:38

of that let's go ahead and go to our

play02:41

program Cs and we're going to think

play02:43

about how can we possibly add more

play02:45

endpoints to our application perhaps

play02:48

after we start our application we hold a

play02:51

reference to the app and we do something

play02:53

like map get and long story short that

play02:56

is not going to work as soon as you run

play02:58

your application you cannot add more

play03:00

endpoints that way so what we're going

play03:02

to do is we're going to write custom

play03:04

middleware so use middleware we will say

play03:08

plugin middleware create this type

play03:12

semicolon over here and because I can't

play03:14

remember all the types that are needed

play03:16

for the middleware we're going to go to

play03:17

use Authentication

play03:19

go inside of here the authentication

play03:21

middleware we're going to copy the

play03:23

request delegate that we need in the

play03:25

Constructor so ctor place that over here

play03:28

this is going to be the next function

play03:30

that is going to be executing and then

play03:32

we just need to match the signature of

play03:35

this method so a method that returns a

play03:37

task and accepts an HTTP context so we

play03:40

grab this function place it over here

play03:42

give it a body and now we have to fill

play03:43

it out let's get rid of this use

play03:45

authentication over here before we dive

play03:47

straight into loading the plugin Etc we

play03:50

can play about with this and get a feel

play03:52

for how this is going to work first of

play03:54

all let's say that we're going to have

play03:55

some kind of context that's going to

play03:57

have a request and it's going to have a

play03:59

path if this path is equal to something

play04:01

like plugin slash test we want to go

play04:04

ahead and well write something to the

play04:07

response so write async this will be

play04:10

test we are going to await here and then

play04:12

whether this has triggered or not we can

play04:15

basically catch all the scenarios if we

play04:17

go to context a response and then there

play04:20

is a has started flag if the response

play04:23

hasn't started then we actually want to

play04:25

fall through to the next so we're gonna

play04:27

take next invoke it pass the context

play04:30

down in there and oh wait so if we hit

play04:33

next we're gonna hit hello world let's

play04:35

go ahead and start this up so opening

play04:37

this dot watch here we see hello world

play04:40

if we go to plug and test and that

play04:43

actually says plug test we want plug-in

play04:45

test so let me amend this a little bit

play04:48

plugin and there we can see test now

play04:50

essentially what we want to do is

play04:52

instead of hard coding this here we want

play04:55

to be able to load up this class over

play04:58

here so what do we do we're going to

play05:00

open test endpoint over here we're gonna

play05:02

delete the assemblies just to be sure

play05:04

and then we're gonna build this so.net

play05:06

build this is going to Reaper produce

play05:08

the artifacts with the latest

play05:09

information and all we need to do is

play05:12

just have a path to this assembly the

play05:14

process of installation of a plugin is

play05:16

essentially just taking artifacts and

play05:18

putting them in the correct place in

play05:20

order for it to be load and then perhaps

play05:22

telling the system go over here and load

play05:24

this stuff up so let's go ahead and grab

play05:27

the full path we're going to drop down

play05:29

the terminal we're going to come back to

play05:30

program Cs and we're gonna say here is

play05:33

the path to the full assembly we can

play05:35

then use assembly load from and this is

play05:39

going to accept a path and now we have

play05:42

an assembly object and from here on out

play05:44

if you know reflection you know what

play05:46

you're doing you can go to assembly you

play05:48

can get type let's say that we're

play05:50

looking after the test endpoint and

play05:53

whatever class I gave it so an end point

play05:55

in the same namespace this is going to

play05:57

be end point we're then going to have

play06:00

the path information from this endpoint

play06:03

get custom attribute this is going to be

play06:05

path attribute and by the way just for

play06:08

Clarity if I open a path attribute this

play06:10

is the same path attribute that is

play06:12

coming from this endpoint BDK okay to

play06:15

check for Noble we can place a question

play06:16

mark over here if path info doesn't

play06:19

equal null or we can just say method and

play06:22

path equals to that of the context

play06:24

request so let me scroll down and

play06:27

quickly skipped past this part and there

play06:29

we have it if path info is a null and

play06:31

the method in the path are basically

play06:33

equal and we ignore the cases we want to

play06:35

go ahead take this endpoint we want to

play06:37

activate it so we're going to go to the

play06:40

activator we're going to create an

play06:42

instance of this type and this is going

play06:44

to be a plug-in endpoint so for actual

play06:48

endpoint this we can call it type so

play06:50

endpoint type replace it here and here

play06:53

then on the individual endpoint we can

play06:56

execute it and pass the HTTP context

play06:59

into there wait and that should be good

play07:01

so the application at this point should

play07:04

restart if I come back over here and

play07:06

actually let me remind my self of the

play07:09

route it was plug test so coming back

play07:11

over here we're gonna remove the i n and

play07:14

go to plug test and we get test if we

play07:17

duplicate this and I'm just going to go

play07:18

to the root route here we're still

play07:21

getting hello world so we've managed to

play07:24

successfully plug in our additional

play07:27

functionality now if we come back and

play07:29

we're at this plugable endpoint we

play07:32

change it we go to rebuild it we're

play07:35

still pointing to the same dll if we

play07:38

come back and we well refresh

play07:40

essentially it's gonna break if we come

play07:43

back over here and we say let's reload

play07:46

the app the app restarts so it's working

play07:48

again and again if we hit the plug test

play07:51

endpoint we can see the change so the

play07:53

behavior that we're seeing over here is

play07:56

once we have managed to load an assembly

play07:59

and its types into our application once

play08:02

it is very hard to load it a second time

play08:05

because there is something around

play08:06

basically being able able to identify

play08:09

that that dll is that unique thing and

play08:12

we cannot load it more than once so

play08:14

that's basically the new ones how do we

play08:17

walk around this what can we do well

play08:19

first of all we have a special type

play08:22

called an assembly below the context and

play08:25

this type specifically is giving us a

play08:29

scope in which assemblies are loaded so

play08:32

instead of just having your whole

play08:34

application This Global namespace This

play08:37

Global application essentially your

play08:40

assembly is like a drop added to this

play08:42

pool and essentially diluted through it

play08:44

you're putting a little space in there

play08:47

that's saying this is where I'm going to

play08:49

be adding my new assembly and then you

play08:51

can go ahead and remove it as well and

play08:53

that is what the assembly load context

play08:55

is so let's go ahead and create a new

play08:57

assembly load context and here in the

play09:00

Constructor you're gonna see that you

play09:02

can provide a name to it and then you

play09:03

can mark it as is collectible or not if

play09:07

you mark it as is collectible that gives

play09:09

you the ability to actually unload the

play09:12

assemblies that you have loaded for the

play09:14

name we're just going to give it the

play09:15

path and then for is collectible we're

play09:17

gonna say that it is true let's say that

play09:20

this is the load context and now instead

play09:22

of using assembly load from and by the

play09:25

way if you go into the load from method

play09:27

you're gonna see that it's actually

play09:29

using an assembly load context

play09:31

internally it's just not using a

play09:33

separate one it's using the default one

play09:35

coming back to program CS we can go to

play09:38

the load context use it and say load

play09:41

from assembly path this is still going

play09:44

to give us an assembly and finally let's

play09:47

say that we're gonna wrap this whole

play09:49

thing and try catch or more of a try

play09:52

finally and by the end this whole thing

play09:54

is done we are going to call unload and

play09:58

unload behave slightly weirdly although

play10:01

also logically and we're going to talk

play10:03

about this in just a second let's remove

play10:05

this space over here and go over the

play10:07

code once more so we understand what it

play10:09

does we create this scope for where we

play10:12

are going to load up an assembly so we

play10:14

have this context and we're maintaining

play10:16

a reference to this place where we are

play10:18

going to be loading assemblies we go

play10:20

ahead load the assembly we do exactly

play10:22

the same thing as we were doing before

play10:24

and then after we've managed to execute

play10:26

it or not because we already did the

play10:28

loading we're just going to unload and

play10:31

by the way we're doing this for every

play10:33

request this is not a production ready

play10:34

solution we are just trying to

play10:36

understand what the heck is actually

play10:38

going on here with what we currently

play10:40

have let's go ahead open up the terminal

play10:42

it's going to restart we're going to

play10:44

come back to this endpoint over here I'm

play10:46

going to refresh and we're still going

play10:48

to see the same extended endpoint coming

play10:50

back over here we're going to go to test

play10:52

endpoint we're gonna change it back to

play10:54

test we're gonna rebuild

play10:56

we're gonna come back and we're gonna

play10:58

refresh and we see the signature is

play11:01

incorrect error the logic behind why it

play11:04

displays the error of the signature is

play11:06

incorrect I won't be able to explain to

play11:07

you however I will be able to tell you

play11:10

why this is happening the reason this is

play11:12

happening is because in program CS when

play11:15

we're calling unload over here it

play11:17

doesn't actually instantly unload

play11:19

anything imagine this so you are loading

play11:23

assemblies from a file you are

play11:25

essentially having this compiled c-sharp

play11:27

code you are bringing it into your own

play11:29

application and then at some point

play11:31

you're saying unload what that is going

play11:33

to do is just remove references and if

play11:36

you're somehow still referencing any of

play11:38

that code the unloading is not going to

play11:41

happen and it's actually only going to

play11:43

occur when the garbage collection runs

play11:46

so unload over here even though it's a

play11:48

synchronous method it's more of a like

play11:50

remove markers the actual unloading is

play11:53

going to happen when the garbage

play11:54

collection is going to run so if I open

play11:57

a memory profiler and I reattach to the

play12:01

server process over here and I'm going

play12:03

to force garbage collection and then I'm

play12:05

going to come back over here and refresh

play12:07

the endpoint we now see the change so

play12:10

when you want to actually unload a

play12:13

plugin and reload it you actually need

play12:15

to force garbage collection and collect

play12:17

the old plugin from memory if we come

play12:19

back to the code and we take a look at

play12:21

this we're loading a type from this

play12:23

assembly if we take this type and we

play12:25

stick it somewhere on the plug-in

play12:26

middleware which outlives the unloading

play12:29

of assembly the assembly is never going

play12:31

to unload this is why if you are

play12:34

uninstalling an extension or you're

play12:36

removing a plug-in it's going to ask you

play12:38

please restart because perhaps for

play12:40

performance reasons the main application

play12:42

needs to take the things inside the

play12:44

plugin and dilute it in its essentially

play12:46

Global space so furthermore what can we

play12:49

do about this let's go ahead and first

play12:52

of all create a private static task

play12:55

where we're going to process us the HTTP

play12:58

context so CTX we're going to take this

play13:01

whole Malarkey place it over here inside

play13:03

this body so we're working directly with

play13:06

this everywhere where we have CTX or

play13:09

context let's go ahead and replace it

play13:10

make the function asynchronous and we're

play13:13

going to call process right over here on

play13:15

the context and await on it give it a

play13:18

little bit of space the first reason for

play13:20

taking out the logic into its own method

play13:22

is because of the stack we have this

play13:24

endpoint type variable and it's

play13:26

essentially capturing this type so until

play13:29

we actually exit the method even though

play13:33

we have unloaded so if we would try to

play13:35

force garbage collection over here

play13:37

nothing would happen because the stack

play13:39

is still keeping a reference to this

play13:41

type over here so before we can actually

play13:44

start garbage collection we need to do

play13:46

it outside we need to exit this method

play13:48

to clear all the references from the

play13:51

stack something that you need to be

play13:52

aware of also in this situation is you

play13:55

can supply and an attribute Mark that is

play13:58

basically called method implementation

play14:00

attribute and then here you can specify

play14:03

things like aggressive inline aggressive

play14:05

optimizations and think about it like

play14:08

this if the dotnet runtime deems it

play14:12

necessary or just I don't know feels

play14:15

like or basically decides to it's going

play14:17

to say there is no method it's going to

play14:20

take the code that is over here and it's

play14:22

just going to execute it as part of this

play14:23

method that basically means even if

play14:26

we're going to put the garbage

play14:27

collection logic over here if we're not

play14:30

going to Define this boundary of a

play14:32

method explicitly we may be holding on

play14:35

to this reference a little bit longer

play14:38

than we intended to so let's say that

play14:40

method implementation no inlining again

play14:43

inlining means taking all of the

play14:45

instructions over here and essentially

play14:46

inlining them into this invoke method

play14:49

another way of giving it this guarantee

play14:51

essentially we can go to the garbage

play14:54

collector we can collect and another

play14:56

thing that we can call is finalizer's

play15:00

weight for pending finalizers if you're

play15:02

wondering what a finalizer is

play15:03

essentially on classes you will have

play15:05

things like destructors so something

play15:08

that looks like this without any

play15:10

parameters and when the object is

play15:13

actually being garbage collected this is

play15:15

what is going to run this finalizer is

play15:17

essentially going to get called sometime

play15:20

after the actual garbage collection runs

play15:22

and it's going to be placed on the

play15:24

finalizer queue where one after another

play15:26

the objects which are being garbage

play15:28

collected will get their finalizers

play15:30

called one by one so here we're

play15:32

basically just saying everything that we

play15:33

have marked as garbage collected or that

play15:35

is being garbage collected wait for all

play15:37

of those finalizers to be called okay so

play15:40

we're doing this we're still flying a

play15:43

little bit blind because we don't have

play15:46

any insurance whether anything has been

play15:49

cleared from memory one way that we can

play15:52

double check that stuff has actually

play15:54

been unloaded is using a week not week

play15:58

week reference to the load context so a

play16:01

strong reference is going to prevent

play16:03

garbage collection week reference

play16:05

doesn't and it just says is there still

play16:07

an object on the endless let's take the

play16:09

weak reference return it from this

play16:12

process we will then have it over here

play16:14

so assembly load context reference we

play16:18

can take this and let's say We'll place

play16:20

a for each Loop we'll retry 10 times

play16:23

while I is less than 10 and the

play16:26

reference is alive we want to go ahead

play16:29

and try garbage collecting by the end of

play16:32

this we can go to the console right line

play16:35

and say unloading successful always

play16:39

forget that s over there take the LC ref

play16:43

and say is alive and we'll say not so if

play16:47

it's not alive it's successful if it is

play16:49

alive then it's unsuccessful with what

play16:52

we have over here let's come back wait

play16:54

for the application to restart let's

play16:56

double check that everything is working

play16:57

so we're just going to refresh the

play16:59

endpoint come back over here and the

play17:01

unloading is successful we can take a

play17:04

quick step over here and say what kind

play17:07

of unloading attempt is it to just to

play17:10

satisfy our curiosity how many times are

play17:13

we forcing garbage collection to run

play17:15

before the stuff actually gets unloaded

play17:17

there we have it let's go ahead and

play17:19

refresh and looks like two times again

play17:22

refresh and another two times why does

play17:25

it take two times I don't know which

play17:27

generation is it trying to collect I

play17:29

don't know as well I don't really care

play17:31

all that much again the loading and

play17:33

unloading of the plugin is going to be

play17:36

slow it's really the experience of once

play17:39

the plugin is loaded how well can you

play17:41

use it and can you actually update the

play17:43

plugin to well update the plugin because

play17:45

updating stuff actually matters so we're

play17:48

capable of unloading the plugin by the

play17:50

end of it let's drop down the terminal

play17:52

over here go to the test endpoint add

play17:54

some more changes we are going to

play17:57

rebuild the application come back over

play17:59

here refresh and the plugin

play18:01

automatically updates one last thing

play18:04

that I wanted to highlight over here is

play18:07

the point about references again so

play18:09

we've seen how if we are essentially

play18:12

keeping references to something the

play18:14

unloading doesn't happen and we actually

play18:16

need to run a garbage collector so how

play18:19

can you essentially leak a reference so

play18:23

you can prevent your assembly from being

play18:25

unloaded and the points about this can

play18:28

be very subtle for example if we go to

play18:30

the test endpoint and we're going to say

play18:32

look we're gonna have some kind of Json

play18:35

string over here and we're going to use

play18:37

the Json serializer to serialize some

play18:40

the object where we're gonna have a

play18:42

message and say yo okay we're gonna take

play18:46

the Json string that is what we're going

play18:47

to write over here and if we're smart

play18:50

you know we're going to use write as

play18:51

Json async we're going to take this

play18:53

object and we're going to put it here

play18:54

okay and Json serializer you don't even

play18:57

see it it's not even there right this is

play18:59

what you're using all fine and dandy

play19:01

let's go ahead over here re-run the

play19:04

build come back to the application

play19:05

refresh and everything is working fine

play19:08

no problem we come back we add all of

play19:11

our changes we rebuilt we come back we

play19:15

refresh and the signature is incorrect

play19:17

if we come back to the application over

play19:20

here we're gonna see that we've went

play19:22

through all of our attempts and the

play19:24

unloading was unsuccessful if you didn't

play19:27

know now you're basically going to know

play19:30

the Json serializer from

play19:33

system.text.json I don't know about the

play19:35

Newton soft one essentially the type

play19:38

that you're trying to serialize that is

play19:40

going to get cached so effectively the

play19:42

Json serializer coming from this Global

play19:45

namespace down into your plugin and if

play19:47

you're utilizing it in your plugin

play19:49

through your plugin your leaking

play19:51

references into the global space so this

play19:54

is how easy it is to mess this stuff up

play19:56

my recommendations for plugins even

play19:58

though it's sometimes not going to be

play20:00

possible you want those to be pure

play20:02

functions a pure function is a function

play20:05

without side effects although this being

play20:07

essentially an internal solution for

play20:09

Microsoft I think I haven't looked far

play20:11

enough but I think through Json

play20:13

serialization options you can actually

play20:15

go ahead and disable it however this is

play20:17

going to be the end of this video thank

play20:19

you very much for watching hopefully

play20:21

this gives you a good starting point on

play20:23

how to write a plugin and what to watch

play20:25

out for or rather than writing a plugin

play20:27

essentially utilizing plugin

play20:29

architecture in your c-sharp application

play20:31

please note it's not fully production

play20:33

ready we've left the loading and

play20:35

unloading of the plugin in the Middle

play20:37

where you want to take it out there is a

play20:39

class like system file Watcher you want

play20:41

to watch the plugins folder for changes

play20:43

if there is a change emits an event try

play20:46

to unload disable usage of the plugin

play20:49

you know you basically have to do all

play20:51

that crazy ceremony as you usually do

play20:53

with edge cases Etc what if you can't

play20:56

unload the plugin what do you do in that

play20:58

scenario you know voice your scenarios

play21:00

and test them as always if you enjoyed

play21:02

the video don't forget to leave a like

play21:04

subscribe if you have any questions

play21:05

leave them in the comment section if you

play21:07

would like the code for this video as

play21:09

well as my other videos please come

play21:10

support me on patreon I will really

play21:12

appreciate it a very very big and

play21:14

special thank you to all my current

play21:15

patreon supporters your help is very

play21:18

appreciated don't forget to check the

play21:19

description for extra documentation that

play21:21

you can use around this topic as always

play21:23

thank you for watching and have a good

play21:25

day

Rate This

5.0 / 5 (0 votes)

الوسوم ذات الصلة
C# PluginsDynamic ExtensionsRuntime LoadingASP.NET CoreMiddlewareAssembly LoadingGarbage CollectionPlugin DevelopmentSoftware ArchitectureDevOps
هل تحتاج إلى تلخيص باللغة الإنجليزية؟