Avoid This Plugin Mistake: Reflection & Dependency Injection in C#
March 11, 2024
• 1,250 views
When it comes to plugins and building a C# application with a plugin architecture, it's common for reflection to intersect with dependency injection. We often have to do things like scan for assemblies, filter the plugin types that we need to use, or create instances of things to have them available.
However, one common challenge is when C# developers try to mix in Activator.CreateInstance alongside the dependency injection frameworks. In this video, I explain where some of that challenge comes from and how to work around it.
View Transcript
when we're building plug-in based applications a lot of the time we're dealing with things like reflection and dependency injection put together if this is something that you're relatively new to this can lead to a lot of confusion and troubleshooting and trying to figure out why things aren't working my name's Nick centino and I'm a principal software engineering manager at Microsoft in this video I want to walk you through a common challenge that people run into when they're trying to mix reflection with dependency injection for trying to load their plugins I'm going to walk you through a common setup that happens where the struggle ends up occurring and then how go about fixing this in this video I'll be using autofac but if you're using a different dependency injection framework a lot of the concepts will be similar a quick reminder to check that pin comment
for a link to my free Weekly Newsletter and my courses on dome train now with that said let's jump over to visual studio so generally when we're building plug-in based applications one of the first things that we start doing especially when we're working with dependency injection is we try configuring our dependency injection container and in this case because I'm using autofac it is called the container Builder class and then from there we can start to register things onto it now in this particular case what I'm going to be looking at is a setup where our plugins are meeting this IR repository interface I just have this I repository interface pulled out into a different project and technically if we had other projects for our different plugins they could go reference this as well in our particular case though we're not going to have to load
from different assemblies I'm just going to keep it all in one to keep this more simple before I dive into how we end up using reflection and the container Builder together what I'm going to do is look at the implementation of our plugin so from line 45 to 50 I have the definition of our plugin it's very simple the I repository interface just has this sort of contrived method on it that's an I inumerable that returns these my object instances the method is just called get all objects now in this particular case the challenge that we're going to be working through is when our plugin has dependencies so what I have on our class here is dependency for our plug-in if we look a little bit above here on line 37 I've just created this really artificial dependency it also is going to have a
similar method that can get all objects and we're just going to return this dummy object back this part in particular is not really important I just wanted to show that when we finally get this working we'd be able to pull all of that information through from our dependency the critical part that we want to look at is right here from line 45 to 46 we have a plug-in it does meet our plugin interface that's called I repository and it does have a dependency these are the important pieces that we're trying to work with here so if I scroll back up to where we start using reflection with the container Builder generally what I see people trying to do and this is pretty common right I do this a lot with my own plug-in loading is we do some type of scanning to be able to
find which assemblies we want to load from in this particular case like I said we're only going to be working with the current assembly so I do have some code we'll get to in a little bit later in this video that walks through stepping through files and trying to load in different assemblies but if we look from line 14 to 15 just the current assembly from there what we're going to do is ask for the types in that assembly and only pick the ones that meet our IR repository interface because that's going to be our plug-in interface that we're working with now where the issue ends up arising I think for most people is this line 19 here what people want to be able to do is use activator create instance in order to create an instance of their plugin from there what they're able
to do is take that instance that was created through reflection and then register that instance onto the container Builder if we go to run this we'll see that we run into a little challenge so let's go look at what the rest of this code does first then we'll try it out after we've done all of our registration we build the container get a lifetime scope and then all that we're doing is asking for all of the plugins that we found and we're just going to print out the type names it's very simple and once this program runs it should exit and ideally what we see if this works is we just have our single plugin the name of that get printed to the console if I press F5 to run this though we'll see that it's not going to go exact as planned and we
can see that right away I get an exception and it says there's a Missing Method exception that it can't dynamically create an instance of the type our repository plugin and the reason for that is that there's no parameterless Constructor to find and that makes sense when we tried to call create instance we're not passing in any dependencies so you have a couple of options at this point if you want to continue using Create instance this way off of the activator class what you could do is make a decision to standardize all of the dependencies that need to go into your plugin you could say that when I call create instance and pass in the plugin type you need to have your plugin defin such that it takes in whatever dependencies that you Mark is required in my personal opinion this is pretty limiting it's going
to mean that anyone who goes to create a plugin can only work with your dependencies I feel like that's not very extensible because you'll probably have plugins that want to have their own dependencies passed in maybe not everyone's going to need the same set of things and that's kind of hard to enforce in a plug-in system although that is one option you could work with I just don't think it's ideal the other thing that you could do if you're insistent on using activator create instance is think about having a parameterless Constructor so if we check out our plug-in implementation here we did require one parameter passed in on the Constructor this is a default Constructor in C it just might look a little odd if you're not used to the syntax what we could do is remove it we don't have any Constructor and we
need to provide maybe one that has the parameter and one that doesn't and then we would have to come up with a creative way to set it that way we could call the parameter list Constructor using activator create instance up here and then come up with a different mechanism to set that dependency on our instance if we assume that we're going down that path then when would we be able to set the dependencies properly where are those dependencies coming from if they're coming off of the container Builder itself would we need to wait until we built the container first to be able to start resolving the dependencies and then maybe use something like property or method injection on our plugin I think that's a potential option but again to me it doesn't really feel right and ultimately I think this all boils down to the
fact that activator create instance may not be the thing that you want to use here this is especially true if the plugins that you're trying to create instances of have dependencies that are going to be part of your container already so to explain that a little bit further the challenge here is that we're using two different systems to create instances of things if we're using a dependency injection framework like autofac it's going to seemingly magically inject those dependencies for us through the Constructor if we're using activator create instance there is no magic it's going to pick the Constructor that matches the parameters that you provide if I don't provide any it's going to look for a parameterless Constructor and if I do provide parameters it's going to pick the Constructor that matches those but how do I get those instances of the dependencies that have
to go in suddenly I have to kind of finagle my own dependency injection to pass into the plugins so in my opinion if we completely ditch this line we can start start to come up with something that's a little bit better and really it's pretty simple because autofac and there's lots of other dependency injection Frameworks that support this kind of thing has a method that we can use instead that's not register instance it's just register type if we pass in the type plug-in type in this case this will now hook up the type of that plugin into our dependency injection container because that's in there autofac will now be able to go resolve dependencies that it needs as soon as we go ask for an instance of an IR repository and that means that it's able to look at the Constructor to say what does
this class need and it will be able to support that across all of the different plugins that you provide into here as long as it has dependencies registered for each of those plugins we need to make sure that dependency for our plugin is registered and we can see from line 10 to 12 that we do have it there so we have our dependency this is going to allow us to register the type of our plug-in and if you notice register type here is taking in an explicit type parameter and register type here is not using the generic method it's using the overload that's taking in an instance of a type object but that means that we have both pieces registered on the container so we should be able to go resolve this let's try running this now and there we go we were able to
get rid of activator create instance alt together simplifying our code because now everything's just being registered onto the container and because our dependency is also registered there with our plugin it will get automatically resolved through dependency injection for us so now that you've seen how we can move away from activator create instance and register things properly on the dependency injection container we could look into doing things like scanning for the other assemblies in the bin directories I have plug-in one and plug-in two also in the bin directory so what you could do and I'm not going to go walk through all the details of this but I'll show you a dependency scanner that I've implemented what it's able to do is ask the current directory that we're running out of for all of the dlls that are there and you could change this to point
at a plug-in folder or Implement a file filter to go look for specifically named plugins you could do anything you'd like then we use assembly load file and you could change this up to be more robust to make sure it's not going to throw exceptions and things like that but from there what we're able to do is ask the container Builder and this is specific to autofac this part but we get asked to register the assembly types passing in all of those assemblies from there we can use assignable to I repository which is kind of like using a link wear Clause where we're saying for all of those just the ones that Implement I repository because that's our plugin interface and then from there we're just telling autofac to make sure that only allow this to be resolved when people ask for the interface and
will'll make them singlet implementation so it's not going to make new instances so you could absolutely use something like this dependency scanner implementation to go load all of the plugins from the bin directory instead of just using something like assembly get executing assembly and hopefully that helps identify where you might be running into some common road blocks using activator create instance so the reflection part of plug-in loading alongside a dependency injection framework in this particular case autofac but the same type of thing could happen with I service collection in your asp.net core apps if you're interested in seeing more about plug-in based applications you can check out this video next for a Blazer build series thanks and I'll see you next time
Frequently Asked Questions
What is the main issue when using reflection with dependency injection in C# plugins?
The main issue arises when trying to use 'Activator.CreateInstance' to create an instance of a plugin that has dependencies. If the plugin does not have a parameterless constructor, you'll encounter a 'Missing Method' exception because the method cannot find a way to instantiate the plugin without passing in the required dependencies.
How can I properly register plugins with dependencies in a dependency injection container?
Instead of using 'Activator.CreateInstance', I recommend using the 'RegisterType' method of your dependency injection container, like Autofac. This allows the container to automatically resolve the dependencies required by your plugins when you request an instance of the plugin interface.
Can I scan for plugins in different assemblies using Autofac?
Yes, you can implement a dependency scanner that looks for DLLs in a specified directory. By using 'Assembly.LoadFile' and then registering the types with the container, you can dynamically load all the plugins that implement your desired interface, such as 'IRepository'.
These FAQs were generated by AI from the video transcript.