65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET MVC 插件框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (17投票s)

2013 年 3 月 22 日

CPOL

5分钟阅读

viewsIcon

118965

介绍如何创建一个ASP.NET MVC插件框架,每个插件可以放在一个单独的文件夹中,并且可以动态地安装/卸载插件。

引言

在本文中,我想分享我的ASP.NET MVC插件框架,它主要包含以下功能:

  1. 每个插件可以部署在单独的文件夹中;无需将插件程序集复制到bin文件夹,插件文件保持与普通网站相同的结构;
  2. 在网站运行后动态地安装/卸载插件;
  3. 插件共享相同的母版页/布局页;
  4. 插件可以具有相同的控制器名称;
  5. 支持Web Forms引擎和Razor引擎;

背景

有很多关于构建ASP.NET MVC插件框架的讨论,但大多数都使用了以下技巧或类似的方法:

  1. 所有视图都嵌入到程序集中,或者
  2. 将插件程序集复制到bin文件夹,或者
  3. 使用私有路径指示插件程序集的位置,或者
  4. 安装插件后,将其视图复制到Views文件夹,

所有这些方法看起来都很吸引人,但维护单个插件非常困难,尤其是当插件的规模变得很大时。

在本文中,您将看到创建一个插件几乎与创建常规ASP.NET MVC Web应用程序完全相同,只需要为每个插件创建一个插件清单文件。

ASP.NET MVC插件实际上是基于另一个插件框架OSGi.NET的扩展,从技术上讲,您可以将其替换为任何其他框架,如MEF、Sharp-develop,只需进行一些包装。

使用代码创建插件

现在让我们从头开始创建一个新的插件,基于该插件框架。我将创建一个媒体管理插件,它可以显示最受欢迎的电视节目。关键步骤如下:

  1. 下载最新的ASP.NET MVC插件框架源代码(您可以选择MVC3或MVC4,取决于您的Visual Studio版本),并将其解压到一个单独的文件夹中,然后用Visual Studio打开MvcOSGi.sln。解决方案的骨架如下所示:
  2. “Core”下的项目是插件框架,Plugins文件夹是插件容器,启动项目“MvcOSGi.Shell”是一个标准的ASP.NET MVC Web应用程序,它所做的就是,在Application_Start()中启动OSGi.Net框架,代码如下所示。

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();
}

上面的代码非常整洁清晰,但我仍然有一些简要的注释。

  • 前两行用于启动OSGi.NET,它将加载插件并解析它们的依赖关系,之后,所有插件都应该处于活动状态或准备好使用状态;
  • 接下来的两行用于注册插件的视图引擎,这对于插件框架至关重要。在此框架中,每个插件及其视图、程序集和所有私有项都可以部署在一个单独的文件夹中,默认情况下,ASP.NET在用户尝试访问视图时找不到它们,因此我需要自定义一个新的视图引擎来帮助ASP.NET从正确的文件夹定位资源。
  • 最后一个MonitorExtension用于挂钩插件更改,例如安装新插件或卸载插件,在这种情况下,我们应该相应地更新母版页。
  1. 右键单击Plugins文件夹,选择“添加”-“新项目”-“ASP.NET MVC 4 Web应用程序”,输入项目名称为MediaPlugin,然后单击“确定”完成插件项目。
  2. packages\iOpenworks\UIShell.OSGi.dll添加到MediaPlugin的引用中,这是后端插件框架库。
  3. 创建一个名为PopularTVShowController的控制器及其视图;
  4. 向MediaPlugin项目添加一个名为Manifest.xml的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>  

这是插件清单文件;它指定要向最终用户显示的插件程序集和页面。我稍后会讨论它。

最后,重新生成整个解决方案,然后按F5运行。

您将在主页上看到您的插件页面的链接列表,这是如何发生的:

我稍后将解释如何将插件中的视图显示到母版页。让我们点击“热门电影”链接来查看插件中的页面:

您会发现插件通过查询字符串指定插件名称。这就是框架处理多个插件中相同控制器名称问题的方式。

关注点

如何将插件中的视图显示到母版页?

每个插件都有一个清单文件,其中描述了它所有的资源。以源代码中的BlogPlugin为例,其清单如下:

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

extension节点表示此插件将在布局页面的主菜单中添加Blog和Support链接。让我们回到MoniteExtension,它用于监视插件扩展的任何更改,在这种情况下,它将把扩展信息加载到ApplicationViewModel中,然后动态地渲染布局页/母版页。MoniterExtension方法如下所示:

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));
}

下面是我们在布局页中渲染主菜单的方式:

<div class="header">
    <div class="header_resize">
        <div class="logo">
            <h1><a href="https://codeproject.org.cn/">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>

这是运行模式:

这是如何发生的:

ASP.NET如何定位插件程序集,因为它的程序集没有复制到bin文件夹?

这有点棘手,ASP.NET从类BuildManager解析程序集,所以您应该在插件激活后将其注册到那里,或在停用时将其移除。

如何支持动态插件安装?

我正在使用一个免费的插件框架OSGi.NET来管理插件,它原生支持动态安装和卸载插件。请参考OSGi.NET模块化框架
这显示了如何在远程控制台应用程序中安装或停止插件。我基于它包装了MVC插件框架,当安装新插件时,我可以收到通知,然后我只需要将插件中的程序集添加到BuildManager

默认情况下,ASP.NET应用程序启动后,您就不能再向BuildManager注册程序集了,我在这里做了一个冒险的尝试,就是通过反射获取BuildManager的程序集容器,然后注册新安装的插件程序集。

为什么在动态安装期间不需要重新启动**w3wp**进程?

默认情况下,w3wp会加载Bin文件夹中的所有程序集并锁定它们,没有办法在不重置的情况下释放锁定。但您不必这样做。在OSGi.net中,当插件被移除时,它里面的所有资源都会被释放,所以程序集对其他插件来说是不可见的,就像它从未存在过一样。关键在于插件程序集放置在其私有文件夹中,而不是**bin文件夹**,所以w3wp不会加载它,但osgi.net会加载它,这样,移除插件程序集就不会回收**w3wp**。

历史

  1. 详细说明动态安装/卸载,以及为什么w3wp不会回收。
© . All rights reserved.