This is the third part of a series about using Roslyn with dependency injection to create a flexible and powerful plug-in framework. Here I review the parts of the solution that deal with dependency injection. Check out the working example on GitHub.
The first trick is to use dependency injection to create any instance of the HelloWorldGenerator class. Then if we need to add a new dependency to the class, we can just add it to the constructor without breaking anything.
HelloWorldGenerator.cs
123456789
publicclassHelloWorldGenerator:IGenerator{publicHelloWorldGenerator(ISomeDependencydependency,IAnotherDependencyanother,INewDependencynew// a new dependency!!!){...}
We’ll use Ninject here, but you ought to be able to achieve the same with any dependency injection framework.
So normally, we’d have a binding something like:
1
Bind<IGenerator>().To<HelloWorldGenerator>();
Instead we’ll replace this with a binding to a factory method instead.
The CreatePluginInstance(context) method will try to find an IGenerator class within any available plug-ins. If it finds one, it will ask the Ninject framework to create an instance of the plug-in class. Otherwise it falls back to the default type (the original implementation of the generator). The PluginLocator it is responsible for searching any runtime-compiled assemblies for candidate plug-ins. We’ll look at it in more detail later.
1234567891011
privateIGeneratorCreatePluginInstance(IContextcontext){varpluginLocator=context.Kernel.Get<PluginLocator>();TyperoslynPluginType=pluginLocator.Locate<IGenerator>();/// if we found a plug-in, create an instance of itif(roslynPluginType!=null)return(IGenerator)context.Kernel.Get(roslynPluginType);else///otherwise create an instance of the original implementationreturncontext.Kernel.Get<HelloWorldGenerator>();}
By convention
Of course, you might have dozens of IGenerator descendants, in which case you can use Ninject’s convention-based binding module. (Don’t forget to add it with NuGet). My version looks something like the following.
1234567891011
/// If you have a lot of IGenerator subclasses, you can use Ninject's/// convention based module./// /// For each Generator, bind to IGenerator. /// For example, Bind<IGenerator>.To<SomeGenerator>();/// Kernel.Bind(scanner=>scanner.FromThisAssembly().SelectAllClasses().InheritedFrom(typeof(IGenerator)).BindToPluginOtherwiseDefaultInterfaces());//This is a custom extension method (see below)
12345678910111213141516171819202122232425
publicstaticclassConventionSyntaxExtensions{publicstaticIConfigureSyntaxBindToPluginOtherwiseDefaultInterfaces(thisIJoinFilterWhereExcludeIncludeBindSyntaxsyntax){returnsyntax.BindWith(newDefaultInterfacesBindingGenerator(newBindableTypeSelector(),newPluginOtherwiseDefaultBindingCreator()));}}/// <summary>/// Returns a Ninject binding to a method which returns the plug-in type if one exists, otherwise returns the default type./// </summary>publicclassPluginOtherwiseDefaultBindingCreator:IBindingCreator{publicIEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>>CreateBindings(IBindingRootbindingRoot,IEnumerable<Type>serviceTypes,TypeimplementationType){if(bindingRoot==null){thrownewArgumentNullException("bindingRoot");}return!serviceTypes.Any()?Enumerable.Empty<IBindingWhenInNamedWithOrOnSyntax<object>>():new[]{bindingRoot.Bind(serviceTypes.ToArray()).ToMethod(context=>context.Kernel.Get(context.Kernel.Get<PluginLocator>().Locate(serviceTypes)??implementationType))};}}