Click here to Skip to main content
15,867,568 members
Articles / Web Development / ASP.NET

ASP.NET MVC plugin framework

Rate me:
Please Sign up or sign in to vote.
4.80/5 (19 votes)
15 May 2013CPOL5 min read 114.1K   71   32
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. Image 1

    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.

C#
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
<?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,

Image 2

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,

Image 3

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
<?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:

C#
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,

HTML
<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,

Image 4

Here is how it happens:

Image 5

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)


Written By
Architect
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: Plugin's View Does not open under Main Layout. Pin
JoeyZhao28-Nov-13 14:59
JoeyZhao28-Nov-13 14:59 
QuestionAnother iisExpress process start Pin
dchen1317-Oct-13 11:46
dchen1317-Oct-13 11:46 
AnswerRe: Another iisExpress process start Pin
JoeyZhao28-Nov-13 15:02
JoeyZhao28-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? Pin
tinybear7612-Sep-13 9:47
tinybear7612-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? Pin
JoeyZhao12-Sep-13 13:12
JoeyZhao12-Sep-13 13:12 
QuestionNice article! where can I find source code for UIShell.OSGi? Pin
tinybear7612-Sep-13 4:41
tinybear7612-Sep-13 4:41 
AnswerRe: Nice article! where can I find source code for UIShell.OSGi? Pin
JoeyZhao12-Sep-13 4:43
JoeyZhao12-Sep-13 4:43 
GeneralRe: Nice article! where can I find source code for UIShell.OSGi? Pin
tinybear7612-Sep-13 4:59
tinybear7612-Sep-13 4:59 
QuestionWhat is the library of Bootstrapper Pin
deepika.arava12-Jun-13 19:26
deepika.arava12-Jun-13 19:26 
AnswerRe: What is the library of Bootstrapper Pin
JoeyZhao12-Jun-13 19:48
JoeyZhao12-Jun-13 19:48 
QuestionWeb Plugin Management Pin
MatijaSestak6-Jun-13 23:18
MatijaSestak6-Jun-13 23:18 
AnswerRe: Web Plugin Management Pin
JoeyZhao6-Jun-13 23:38
JoeyZhao6-Jun-13 23:38 
AnswerRe: Web Plugin Management Pin
JoeyZhao22-Jun-13 22:55
JoeyZhao22-Jun-13 22:55 
GeneralMy vote of 5 Pin
Prasad Khandekar15-May-13 0:56
professionalPrasad Khandekar15-May-13 0:56 
QuestionCan't download the source code Pin
Lie Tjie Pouw16-Apr-13 23:58
Lie Tjie Pouw16-Apr-13 23:58 
AnswerRe: Can't download the source code Pin
Lie Tjie Pouw17-Apr-13 18:00
Lie Tjie Pouw17-Apr-13 18:00 
GeneralMy 5 Pin
SiteBuilder9-Apr-13 2:00
professionalSiteBuilder9-Apr-13 2:00 
GeneralMy vote of 5 Pin
Prasad Khandekar1-Apr-13 8:26
professionalPrasad Khandekar1-Apr-13 8:26 
Questiondynamic install/uninstall plugin Pin
pietro7725-Mar-13 12:24
pietro7725-Mar-13 12:24 
AnswerRe: dynamic install/uninstall plugin Pin
JoeyZhao31-Mar-13 14:45
JoeyZhao31-Mar-13 14:45 
GeneralRe: dynamic install/uninstall plugin Pin
Member 111410569-Feb-16 0:14
Member 111410569-Feb-16 0:14 
GeneralMy vote of 5 Pin
Josen.L.Zhang22-Mar-13 14:39
Josen.L.Zhang22-Mar-13 14:39 

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

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