Introduction
There are quite a lot good articles about asp.net dependency injection and IoC, most solutions seems attractive but you have to develop everything almost from scratch.
Fortunately, there are some elegant IoC frameworks kill the pain, like Autofac and Spring.net. Autofac steps further to develop some libraries to integrate with Asp.net, which save much time for us.
So what's this article for? I'm not trying to invent new wheel, but to bring Ioc & DI features into Asp.net plugin framework by integrating Autofac, and this solution is also valid for integrating other libraries.
Background
I post an article Asp.net MVC plugin framework to show the solution of building modulized asp.net MVC plugin. Plugin framework oriented design supposed to be able to easily integrate third party libraries, that's what plugin framework for. so I decided to build an IoC plugin based on Autofac.
Using the code
Before we go further, let's get start with developping a demo to use IoC plugin, the completed source code can be download here, please choose
or
either is fine.
Let's take the MediaPlugin for instance, the requirement is load all popular movices from data storage (database or somewhere), then show on page. So I create a movice data access interface, its definition is following,
public interface IMoviceManager
{
List<Movie> GetMovies();
}
Secondly, I create a MediaManagement plugin, which is actually the data access layer, the mockup implementation is below,
public class MovieManager:IMoviceManager
{
private static List<Movie> _movies;
static MovieManager()
{
_movies = new List<Movie>();
_movies.Add(new Movie() { Name = "The Breaking Bad", Rating = 5 });
_movies.Add(new Movie() { Name = "The Avatar", Rating = 5 });
_movies.Add(new Movie() { Name = "The Walking Dead", Rating = 4 });
}
public List<Movie> GetMovies()
{
return _movies;
}
}
In the plugin activator, I register the data access layer into Autofac like this,
public class Activator:IBundleActivator
{
public void Start(IBundleContext context)
{
var builder = context.GetFirstOrDefaultService<ContainerBuilder>();
builder.RegisterType<MovieManager>().AsImplementedInterfaces();
}
public void Stop(IBundleContext context)
{
}
}
The Activator in OSGi.NET is the entry of plugin. When plugin is active, the method Start is invoked, if changed to inactive, Stop is invoked, which enables user has chance to do something like prepare resource and release resource. The activator is optional, Click here to learn a simple demo of activator.
For now, the data access plugin is ready, next step is to create a MediaPlugin to display movices on web page.
The page's controller definition is following,
public class PopularTVShowController : Controller
{
private readonly IMoviceManager _moviceManager;
public PopularTVShowController(IMoviceManager moviceManager)
{
_moviceManager = moviceManager;
}
public ActionResult Index()
{
return View(_moviceManager.GetMovies());
}
}
When accessing its view, the controller is auto constructed. The running mode screen shoot is below,
How it works?
To help us better understand the auto injection mechanism, I post the debugging screenshoot as below,
From the call stack you can see we customized a ControllerFactory in the plugin framework, which responses to create controller instance. Below are the main steps it works,
- Let's say user accesses a plugin page, the URL is http://localhost/MediaPlugin/PopularTVShow/Index;
- Our customized ControllerFactory identify the plugin name is MediaPlugin and controller name is PopularTVShow from the URL ;
- Customized ControllerFactory starts the MediaPlugin if it's inactive, then resolves the Controller type from the plugin assemblies;
- ControllerFactory tries to load ControllerResolver service to construct the controller instance. This is the key to IocPlugin, because the ControllerResolver service is provided by IocPlugin. If the ControllerFactory can't find available ControllerResolver service, it calls System.Activator.CreateInstance instead.
How to create IoC plugin?
There is no trick here, simply create a empty plugin at first. When the plugin is active, construct Autofac ContainerBuilder, then register the instance into you plugin framework. The plugin framework in this project is supported by OSGi.NET, the registeration is below,
public static ContainerBuilder Initialize(this BundleRuntime runtime)
{
var containerBuilder = new ContainerBuilder();
runtime.AddService(typeof(ContainerBuilder), containerBuilder);
return containerBuilder;
}
Then the only thing need to do is monitoring any plugin change, and maintain the assemblies for ContainerBuilder. The piece of registering assembly code is following,
public static void SafeRegisterControllers(this ContainerBuilder containerBuilder, Assembly[] assmblies)
{
lock (containerBuilder)
{
var container = BundleRuntime.Instance.GetFirstOrDefaultService<IContainer>();
if (container == null)
{
containerBuilder.RegisterControllers(assmblies);
}
else
{
ContainerBuilder anotherBuilder = new ContainerBuilder();
anotherBuilder.RegisterControllers(assmblies);
anotherBuilder.Update(container);
}
}
}
Points of Interest
Aotufac does the real work
The IoC plugin I created here actually reuses Autofac integration library to handler controller dependency injection. The Autofac ContainerBuilder is not thread safe, so you have to lock it before updating.
The solution is plugin framework independent
This plugin framework is based on the ASP.NET MVC plugin framework, but not limited to that framework. This solution should work with all plugin frameworks, like MEF, Mono Addin, but you may need to implement the logic of monitoring plugin Start/Stop.
Plugin Dependency & Resolve
A good nature of plugin framework is any plugin can be removed/stop/start anytime. Let's look back the plugins we created, you will find MediaPlugin depends on IocPlugin, so MediaPlugin only works when IoCPlugin is active. In real life, maybe for any reason, the IocPlugin is temporary unavailble, in this case, all views in MediaPlugin should be invisible to end users, otherwise, users will see error page complaining "No parameterless constructor defined for the object". So we should explicitely tell the plugin framework that MediaPlugin depends on IoCPlugin, so once IocPlugin is unavailble, the IocPlugin isn't either, this is known as Dependency & Resolve in plugin framework.
In OSGi.NET, the dependency is defined in plugin manifest file as below,
="1.0" ="utf-8"
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="MediaPlugin" SymbolicName="MediaPlugin" Version="1.0.0.0" InitializedState="Active">
<Activator Type="MediaPlugin.Activator" Policy="Immediate" />
<Runtime>
<Assembly Path="bin\MediaPlugin.dll" Share="false" />
<Dependency BundleSymbolicName="UIShell.IoCPlugin" Resolution="Mandatory" />
</Runtime>
</Bundle>
If you are using other plugin framework, you'd better take this scenario into consideration.
Sequence to Start Plugin
For enterprise application, there are often hundreds of plugins, so the order to start plugin is crucial, some core plugins like DataAccessPlugin, AuthenticationPlugin usually start earier than others. I suggest developer don't make their plugins rely on the start sequence. The IocPlugin isn't essential to most application, so there's no gurantee it always starts prior to others. Consider below secnario,
- Plugin1 get started, its controllers depends on IocPlugin same as MediaPlugin does;
- IocPlugin started, it will monitor any plugin active/inactive;
- MediaPlugin started, so IocPlugin received the plugin active notification, then loads MediaPlugin assembly into the ContainerBuilder;
Do you see what's wrong going here? The assemblies in Plugin1 are ignored by IocPlugin! A good design of IocPlugin should be able to deal with this case, which is load assemblies started prior to it.
Please developer remember the good practice:
Don't make your plugins rely on start sequence as possible as you can.
Migrate more libraries into plugins
Let's summarize the key steps of the migration,
- Create a empty plugin project with Activator class, add reference to third part assemblies;
- Construct your service in plugin Activator, and register it to plugin framework service container. For IocPlugin, we create ContainerBuilder, then put it to bundle service container by invoking
BundleRuntime.AddService(typeof(ContainerBuilder), containerBuilder);
All other plugins can get the ContainerBuilder from the service container, and use it directly.
With this solution, we can migrate libraries into plugins as more as we need. For instance, we can
- Implement a Message Broker plugin by integrating with ActiveMQ or ESB;
- Create Logging plugin by integrating log4net or enterprise library;
- others as you need.
History
None.