I'd like to share the way I built an ASP.NET MVC plugin framework, it mainly contains the below features,
- Each plugin can be deployed in a seperate folder; No need to copy plugin assembly to
bin folder, the plugin files keep same structure as an ordinary website;
- Dynamically install/uninstall plugin after website gets running;
- Plugins share same master/layout;
There are many discussions about building ASP.NET MVC plugin framework, I saw a good article here:
http://www.codeproject.com/Articles/358360/NET-4-0-ASP-NET-MVC-3-plug-in-architecture-with-e, but it requires all views be embedded into the assembly, which is a little bit unacceptable to developers in real life.
Another way is, when installing a plugin, copy its views to Views folder, this seems a solution but it's very diffcult to maintain individual plugin, especially when the size of plugin grows large.
The backend plugin framework is based on
OSGi.NET, technically, you can replace it with anyother frameworks like MEF, Sharpdevelop with some wrapping.
Using the code
Download source code for ASP.NET MVC 3 and ASP.NET MVC4 plugin framework at
// Load plugins in HttpApplication.
protected void Application_Start()
var bootstapper = new Bootstrapper();
//Register Razor view engine for bundle.
ViewEngines.Engines.Add(new BundleRuntimeViewEngine(new BundleRazorViewEngineFactory()));
//Register WebForm view engine.
ViewEngines.Engines.Add(new BundleRuntimeViewEngine(new BundleWebFormViewEngineFactory()));
The code above is pretty neat and clear, but I still have some brief comments here.
- The first two lines is to start OSGi.NET, which will load plugins and resolve their dependency, after this, all plugins should be active or ready to use;
- The next two lines is to register view engines for plugins, this is crucial of the plugin framework. In this framework, each plugin and its views, assemblies and all private items can be deployed in a separate folder, by default, ASP.NET can't find the
views when user try to access them, so I need to customize a new one to help ASP.NET can locate the resource from correct folder.
- The last one MonitorExtension is used to hook plugin changing, like install new plugin or uninstall, in this case we should update the master pages correspondingly.
Points of Interest
How does ASP.NET locates plugin assembly since its assembly doesn't copy to bin?
This is pretty tricky here, ASP.NET resolves assembly from class BuildManager, so you should
register plugin assembly into there after the plugin is active, or remove it when deactivated.
How to display views in plugin to master page?
Each plugin has a manifest file, which describes all resources in it. Take the
BlogPlugin in the source code for example, its manifest is this,
<?xml version="1.0" encoding="utf-8"?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="BlogPlugin"
SymbolicName="BlogPlugin" Version="18.104.22.168" InitializedState="Active">
<Activator Type="BlogPlugin.Activator" Policy="Immediate" />
<Assembly Path="bin\BlogPlugin.dll" Share="false" />
<Item url="/BlogPlugin/Blog/Index" text="Blog" order="4"/>
<Item url="/BlogPlugin/Support/Index" text="Support" order="2"/>
The extension node indicates this plugin will add Blog and Support link to Main menu in layout page. Let's go back to
MoniteExtension, it's used to monitor any changes of plugin extension, in this case, it will load extension info into
ApplicationViewModel, then render layout page/Master page dynamically. The
MoniterExtension method is as follows:
private void MonitorExtension()
ViewModel = new ApplicationViewModel();
//Register pages in Shell project.
Text = "Home",
URL = "/"
_extensionHooker = new ExtensionHooker(BundleRuntime.Instance.GetFirstOrDefaultService<IExtensionManager>());
_extensionHooker.HookExtension("MainMenu", new MainMenuExtensionHandler(ViewModel));
_extensionHooker.HookExtension("SidebarMenu", new SidebarExtensionHandler(ViewModel));
And below is how we render main menu in layout page,
<small>This page is built by <b>Razor Engine</b></small></h1>
var viewModel = UIShell.OSGi.BundleRuntime.Instance.GetFirstOrDefaultService<ApplicationViewModel>();
if (viewModel != null)
foreach (var mainMenuItem in viewModel.MainMenuItems.OrderBy(item => item.Order))
<li itemid="@mainMenuItem.Order"><a href="@mainMenuItem.URL">@mainMenuItem.Text</a> </li>
And here is the running mode,
Here is how it happens,
How to support dynamically plugin installation?
By default, after ASP.NET application get started, you are not allowed to register assembly to
BuildManager anymore, I did a risky try here, which is get the assembly container from
BuildManager by reflection, then register newly installed plugin assembly.