Click here to Skip to main content
Click here to Skip to main content

ASP.NET MVC plugin framework

, 15 May 2013
Rate this:
Please Sign up or sign in to vote.
Explains how to create an ASP.NET MVC plugin framework, each plugin can be placed in a separate folder and can dynamically install/uninstall plugins.

Introduction

In this article, I'd like to share my ASP.NET MVC plugin framework, it mainly contains the below features,

  1. Each plugin can be deployed in a separate folder; No need to copy plugin assembly to bin folder, the plugin files keep same structure as an ordinary website;
  2. Dynamically install/uninstall plugin after website gets running;
  3. Plugins share same master/layout;
  4. Plugins can have same controller name;
  5. Support both web form engine and Razor engine;

Background

There are many discussions about building an ASP.NET MVC plugin framework, but most of them use below tricks or similar,

  1. All views be embedded into the assembly, or
  2. Copy plugin assemblies into bin, or
  3. Use private path to indicate where the plugin's assembly is, or
  4. After installing a plugin, copy its views to Views folder,

All of them seems attractive but it's very difficult to maintain individual plugin, especially when the size of plugin grows large.

In this article, you will see creating a plugin is almost exactly same as create a regular ASP.NET MVC web application, it only need to create a plugin manifest file for each plugin.

The ASP.NET MVC plugins is actually a extension based on another plugin framework OSGi.NET, technically, you can replace it with any other frameworks like MEF, Sharp-develop with some wrapping.

Using the code to Create a Plugin

Now let go through how to create a new plugin from scratch base on the plugin framework. I will create a media management plugin, which can display the most popular TV shows. The key steps are shown below,

  1. Download the latest ASP.NET MVC Plugin Framework source code (you can choose MVC3 or MVC4 depends on what Visual studio version you have) and extract it to a separate folder, then open MvcOSGi.sln with. The solution skeleton is shown below:
  2. The projects under “Core” are the plugin framework, Plugins folder is the plugin container, the start up project “MvcOSGi.Shell” is a standard ASP.NET MVC web application, the only thing it does is to start OSGi.Net framework at Application_Start(), the code is show below.

protected void Application_Start()
{
    //Start OSGi
    var bootstapper = new Bootstrapper();
    bootstapper.StartBundleRuntime();

    //Register Razor view engine for bundle.
    ViewEngines.Engines.Add(new BundleRuntimeViewEngine(new BundleRazorViewEngineFactory()));
    //Register WebForm view engine.
    ViewEngines.Engines.Add(new BundleRuntimeViewEngine(new BundleWebFormViewEngineFactory()));

    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
    MonitorExtension();
}

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.
  1. Right click Plugins folder, Add-New Project-ASP.NET MVC 4 Web Application, input the project name as MediaPlugin, and click OK to finish plugin project.
  2. Add packages\iOpenworks\UIShell.OSGi.dll to MediaPlugin reference, it is the backend plugin framework library.
  3. Create a controller named PopularTVShowController and it’s view;
  4. Add a XML file named Manifest.xml to MediaPlugin project, its content is shown below,
<?xml version="1.0" encoding="utf-8"?>
<Bundle xmlns="urn:uiosp-bundle-manifest-2.0" Name="MediaPlugin" 
    SymbolicName="MediaPlugin" Version="1.0.0.0" InitializedState="Active">
  <Runtime>
    <Assembly Path="bin\MediaPlugin.dll" Share="false" />
  </Runtime>
  <Extension Point="SidebarMenu">
      <Item url="/PopularTVShow/Index?plugin=MediaPlugin" 
              text="Popular Movie" order="0"/>
    <Item url="https://osgi.codeplex.com/" text="OSGi.NET" order="0"/>
    <Item url="#" text="Style" order="0"/>
    <Item url="#" text="Blog" order="0"/>
    <Item url="#" text="Archives" order="0"/>
  </Extension>
</Bundle>  

It is the plugin manifest file; it specifies the plugin assemblies and pages to display to end user. I will talk about it later.

Lastly, rebuild the whole solution and press F5 to run.

You will see the links of your plugin pages list in the home page, here is how it happens,

I will explain how to display views in plugin to master page later. Let's click the "Popular Movie" link to check out the page in plugin,

You will find the plugin specify the plugin name via query string. This is how the framework handle the isue that same controller name in multiple plugins.

Points of Interest

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="1.0.0.0" InitializedState="Active">
  <Activator Type="BlogPlugin.Activator" Policy="Immediate" />
  <Runtime>
    <Assembly Path="bin\BlogPlugin.dll" Share="false" />
  </Runtime>

  <Extension Point="MainMenu">
      <Item url="/Blog/Index?plugin=BlogPlugin" 
            text="Blog" order="4"/>
      <Item url="/Support/Index?plugin=BlogPlugin" 
            text="Support" order="2"/>
  </Extension>
</Bundle> 

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.
    ViewModel.MainMenuItems.Add(new MenuItem
    {
        Text = "Home",
        URL = "/"
    });

    BundleRuntime.Instance.AddService<ApplicationViewModel>(ViewModel);
    _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,

<div class="header">
    <div class="header_resize">
        <div class="logo">
            <h1><a href="http://www.codeproject.com/">OSGi.NET</a> 
                  <small>This page is built by <b>Razor Engine</b></small></h1>
        </div>
        <div class="menu_nav">
            <ul>
                @{
                    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>
                        }
                    }
                }
            </ul>
        </div>
        <div class="clr"></div>
    </div>
</div>

And here is the running mode,

Here is how it happens:

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 support dynamically plugin installation?

I am using a free plugin framework OSGi.NET to manage the plugins, which natively supports dynamicly install and uninstall plugins. Please refer to OSGi.NET Modulization Framework .
Which shows how to install or stop plugin in a remote console app. I wrap the MVC plugin framework base on it, When new plugin is installed, I can receive a notification then I just need to add assemblies in the plugin to BuildManager.

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.

Why don't need to restart w3wp process during dynamical installation?

By default w3wp will load all assemblies in Bin folder and lock them, there is no way to release the lock without reset. But you don't need to. In OSGi.net, when plugin is removed, all resources in it are released, so the assembly is invisible to other plugins like it never exist. The key point is plugin assembly is placed in it's private folder, not bin folder, so w3wp does not load it, but osgi.net does, in this way, removing the plugin assembly won't recycle w3wp.

History

  1. Elaborate dynamic installation/uninstallation, and why w3wp doesn't recycle.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

JoeyZhao
Architect
China China
No Biography provided
Follow on   Google+

Comments and Discussions

 
Generalsomething wrong with the Global.asax file Pinmemberbhsstudio5-Jun-14 19:10 
QuestionRemote tool error PinmemberKonKxy19-Feb-14 9:44 
AnswerRe: Remote tool error PinmemberJoeyZhao19-Feb-14 13:59 
GeneralRe: Remote tool error PinmemberKonKxy20-Feb-14 3:37 
QuestionPlugin's View Does not open under Main Layout. Pinmemberrughimire28-Nov-13 6:14 
AnswerRe: Plugin's View Does not open under Main Layout. PinmemberJoeyZhao28-Nov-13 14:59 
QuestionAnother iisExpress process start Pinmemberdchen1317-Oct-13 11:46 
AnswerRe: Another iisExpress process start PinmemberJoeyZhao28-Nov-13 15:02 
Questioncan Plugins folder be changed to point to other location? or is it a hardcoded path in the black box UIShell.OSGi? Pinmembertinybear7612-Sep-13 9:47 
AnswerRe: can Plugins folder be changed to point to other location? or is it a hardcoded path in the black box UIShell.OSGi? PinmemberJoeyZhao12-Sep-13 13:12 
QuestionNice article! where can I find source code for UIShell.OSGi? Pinmembertinybear7612-Sep-13 4:41 
AnswerRe: Nice article! where can I find source code for UIShell.OSGi? PinmemberJoeyZhao12-Sep-13 4:43 
GeneralRe: Nice article! where can I find source code for UIShell.OSGi? Pinmembertinybear7612-Sep-13 4:59 
QuestionWhat is the library of Bootstrapper Pinmemberdeepika.arava12-Jun-13 19:26 
AnswerRe: What is the library of Bootstrapper PinmemberJoeyZhao12-Jun-13 19:48 
QuestionWeb Plugin Management PinmemberMatijaSestak6-Jun-13 23:18 
AnswerRe: Web Plugin Management PinmemberJoeyZhao6-Jun-13 23:38 
That's really a great question! actually we already implemented app store (plugin repository) long time ago, please check out:http://iopenworks.com/[^]
We step further and implemented below features base on OSGi.NET,
1, Created a set of tool integrated with Visual Studio 2008/2010/2012, developers can create a plugin in 1 minute;
2, Developer can deploy plugin to plugin repository with one click on the project;
3, All plugin can upgrade to new version;
4, Manage plugins on web page;
 
All of our projects are developed based on this, this is totally free, but I didn't have much time to make a English version.
AnswerRe: Web Plugin Management PinmemberJoeyZhao22-Jun-13 22:55 
GeneralMy vote of 5 PinprofessionalPrasad Khandekar15-May-13 0:56 
QuestionCan't download the source code PinmemberLie Tjie Pouw16-Apr-13 23:58 
AnswerRe: Can't download the source code PinmemberLie Tjie Pouw17-Apr-13 18:00 
GeneralMy 5 PinmemberAndre K9-Apr-13 2:00 
GeneralMy vote of 5 PinmemberPrasad Khandekar1-Apr-13 8:26 
Questiondynamic install/uninstall plugin Pinmemberpietro7725-Mar-13 12:24 
AnswerRe: dynamic install/uninstall plugin PinmemberJoeyZhao31-Mar-13 14:45 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 15 May 2013
Article Copyright 2013 by JoeyZhao
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid