好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于ASP.NET MVC3 Razor的模块化/插件式架构实现

基于ASP.NET MVC3 Razor的模块化/插件式架构实现


我们把各个模块编译出来的assembly和各个模块的配置文件自动放到一个bin平级的plugin目录,然后web应用启动的时候自动扫描这个plugin目录并加载各个模块plugin,这个怎么做到的?大家也许知道,ASP.NET只允许读取Bin目录下的assbmely,不可以读取其他路径,包括Bin\abc等,即使在web.config这样 配置probing 也不行:(不信你可以试一下)

    1:   <  configuration  >  Element
    2:     <  runtime  >  Element
    3:       <  assemblyBinding   xmlns  ="urn:schemas-microsoft-com:asm.v1"  > 
    4:         <  probing   privatePath  ="bin;bin\abc;plugin;"  /> 
    5:       </  assemblyBinding  > 
    6:      </  runtime  > 
    7:   </  configuration  > 

这个和 TrustLevel 有关,在 Full Trust 的情况下,可以这样读取非Bin目录下的assembly:

首先在和Bib平级的地方建一个目录Plugin,然后在模块class library project的属性里面加一个postBuildEvent,就是说在编译完成以后把模块的assbmely自动拷贝到主web项目的plugin目录:

    1:  copy /Y "$(TargetDir)$(ProjectName).dll" "$(SolutionDir)ModularWebApplication\Plugin\"
    2:  copy /Y "$(TargetDir)$(ProjectName).config" "$(SolutionDir)ModularWebApplication\Plugin\"
    3:   

然后用下面的代码加载Plugin目录下的assembly:(只看LoadAssembly那一段)

    1:  using System;
    2:  using System.Collections.Generic;
    3:  using System.IO;
    4:  using System.Linq;
    5:  using System.Reflection;
    6:  using System.Text;
    7:  using System.Threading;
    8:  using System.Web;
    9:  using System.Web.Compilation;
   10:  using System.Web.Hosting;
   11:  using Common.Framework;
   12:  using Common.PrecompiledViews;
   13:   
   14:  //[assembly: PreApplicationStartMethod(typeof(PluginLoader), "Initialize")]
   15:   
   16:  namespace Common.PrecompiledViews
   17:  {
   18:      public class PluginLoader
   19:      {
   20:          public static void Initialize(string folder = "~/Plugin")
   21:          {
   22:              LoadAssemblies(folder);
   23:              LoadConfig(folder);
   24:          }
   25:   
   26:          private static void LoadConfig(string folder, string defaultConfigName="*.config")
   27:          {
   28:              var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));
   29:              var configFiles = directory.GetFiles(defaultConfigName, SearchOption.AllDirectories).ToList();
   30:              if (configFiles.Count == 0) return;
   31:   
   32:              foreach (var configFile in configFiles.OrderBy(s = >  s.Name))
   33:              {
   34:                  ModuleConfigContainer.Register(new ModuleConfiguration(configFile.FullName));
   35:              }
   36:          }
   37:   
   38:          private static void LoadAssemblies(string folder)
   39:          {
   40:              var directory = new DirectoryInfo(HostingEnvironment.MapPath(folder));
   41:              var binFiles = directory.GetFiles("*.dll", SearchOption.AllDirectories).ToList();
   42:              if (binFiles.Count == 0) return;
   43:   
   44:              foreach (var plug in binFiles)
   45:              {
   46:                  //running in full trust
   47:                  //************
   48:                  //if (GetCurrentTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)
   49:                  //set in web.config, probing to plugin\temp and copy all to that folder
   50:                  //************************
   51:                  var shadowCopyPlugFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
   52:                  var shadowCopiedPlug = new FileInfo(Path.Combine(shadowCopyPlugFolder.FullName, plug.Name));
   53:                  File.Copy(plug.FullName, shadowCopiedPlug.FullName, true); //TODO: Exception handling here...
   54:                  var shadowCopiedAssembly = Assembly.Load(AssemblyName.GetAssemblyName(shadowCopiedPlug.FullName));
   55:   
   56:                  //add the reference to the build manager
   57:                  BuildManager.AddReferencedAssembly(shadowCopiedAssembly);
   58:              }
   59:          }
   60:   
   61:          //private static AspNetHostingPermissionLevel GetCurrentTrustLevel()
   62:          //{
   63:          //    foreach (AspNetHostingPermissionLevel trustLevel in
   64:          //        new AspNetHostingPermissionLevel[]
   65:          //            {
   66:          //                AspNetHostingPermissionLevel.Unrestricted,
   67:          //                AspNetHostingPermissionLevel.High,
   68:          //                AspNetHostingPermissionLevel.Medium,
   69:          //                AspNetHostingPermissionLevel.Low,
   70:          //                AspNetHostingPermissionLevel.Minimal
   71:          //            })
   72:          //    {
   73:          //        try
   74:          //        {
   75:          //            new AspNetHostingPermission(trustLevel).Demand();
   76:          //        }
   77:          //        catch (System.Security.SecurityException)
   78:          //        {
   79:          //            continue;
   80:          //        }
   81:   
   82:          //        return trustLevel;
   83:          //    }
   84:   
   85:          //    return AspNetHostingPermissionLevel.None;
   86:          //}
   87:   
   88:      }
   89:  }

如果不是 Full Trust ,例如 Medium Trust 的情况下参考这个帖子《 Developing-a-plugin-framework-in-ASPNET-with-medium-trust 》。

如何在_layout.cshtml的主菜单注入plugin的菜单

在母版页_layout.cshtml有个主菜单,一般是这样写的:

    1:   <  ul  > 
    2:      <  li  > @Html.ActionLink("Home", "Index", "Home") </  li  > 
    3:      <  li  > @Html.ActionLink("About", "About", "Home") </  li  > 
    4:      <  li  > @Html.ActionLink("Team", "Index", "Team") </  li  > 
    5:   </  ul  > 

现在我们如何实现从模块插入plugin到这个主菜单呢?这个有点难。因为大家知道,_layout.cshml母版没有controller。怎么实现呢?方法是用controller基类,让所有controller继承自这个基类。然后在基类里面,读取plugin目录里面的配置文件,获取所有模块需要插入的主菜单项,然后放入viewBag,这样在_Layout.cshtml就可以获取viewBag,类似这样:

    1:   <  ul  > 
    2:     @foreach (MainMenuItemModel entry in ViewBag.MainMenuItems)
    3:      {
    4:           <  li  > @Html.ActionLink(entry.Text, 
    5:                 entry.ActionName, 
    6:                  entry.ControllerName) </  li  > 
    7:      }
    8:   </  ul  > 

代码:基类Controller,读取plugin目录里面的配置文件,获取所有模块需要插入的主菜单项,然后放入viewBag

    1:   using  System;
    2:   using  System.Collections;
    3:   using  System.Collections.Generic;
    4:   using  System.ComponentModel;
    5:   using  System.Linq;
    6:   using  System.Net.Mime;
    7:   using  System.Text;
    8:   using  System.Web.Mvc;
    9:   
   10:   namespace  Common.Framework
   11:  {
   12:       public   class  BaseController : Controller
   13:      {
   14:           protected   override   void  Initialize(System.Web.Routing.RequestContext requestContext)
   15:          {
   16:               base .Initialize(requestContext);
   17:   
   18:               // retireve data from plugins 
   19:              IEnumerable<ModuleConfiguration> ret = ModuleConfigContainer.GetConfig();
   20:   
   21:              var data = (from c  in  ret
   22:                          from menu  in  c.MainMenuItems
   23:                          select  new  MainMenuItemModel
   24:                                     {
   25:                                         Id = menu.Id, ActionName = menu.ActionName, ControllerName = menu.ControllerName, Text = menu.Text
   26:                                     }).ToList();
   27:              
   28:              ViewBag.MainMenuItems = data.AsEnumerable();
   29:          }
   30:   
   31:      }
   32:  }

代码:ModuleConfigContainer,用到单例模式,只读取一次

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.Linq;
    4:   using  System.Text;
    5:   
    6:   namespace  Common.Framework
    7:  {
    8:       public   static   class  ModuleConfigContainer
    9:      {
   10:           static  ModuleConfigContainer()
   11:          {
   12:              Instance =  new  ModuleConfigDictionary();
   13:          }
   14:   
   15:           internal   static  IModuleConfigDictionary Instance { get; set; }
   16:   
   17:           public   static   void  Register(ModuleConfiguration item)
   18:          {
   19:              Instance.Register(item);
   20:          }
   21:   
   22:           public   static  IEnumerable<ModuleConfiguration> GetConfig()
   23:          {
   24:               return  Instance.GetConfigs();
   25:          }
   26:      }
   27:  }

代码:ModuleConfigDictionary

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.Linq;
    4:   using  System.Text;
    5:   
    6:   namespace  Common.Framework
    7:  {
    8:       public   class  ModuleConfigDictionary : IModuleConfigDictionary
    9:      {
   10:           private   readonly  Dictionary< string , ModuleConfiguration>  _configurations =  new  Dictionary< string , ModuleConfiguration>();
   11:   
   12:           public  IEnumerable<ModuleConfiguration> GetConfigs()
   13:          {
   14:               return  _configurations.Values.AsEnumerable();
   15:          }
   16:   
   17:           public   void  Register(ModuleConfiguration item)
   18:          {
   19:               if (_configurations.ContainsKey(item.ModuleName))
   20:              {
   21:                  _configurations[item.ModuleName] = item;
   22:              }
   23:               else 
   24:              {
   25:                  _configurations.Add(item.ModuleName, item);
   26:              }
   27:          }
   28:      }
   29:  }

代码:ModuleConfiguration,读取模块的配置文件

    1:   using  System;
    2:   using  System.Collections.Generic;
    3:   using  System.IO;
    4:   using  System.Linq;
    5:   using  System.Text;
    6:   using  System.Xml;
    7:   using  System.Xml.Linq;
    8:   
    9:   namespace  Common.Framework
   10:  {
   11:       public   class  ModuleConfiguration
   12:      {
   13:           public  ModuleConfiguration( string  filePath)
   14:          {
   15:               try 
   16:              {
   17:                  var doc = XDocument.Load(filePath);
   18:                  var root = XElement.Parse(doc.ToString());
   19:   
   20:                   if  (!root.HasElements)  return ;
   21:   
   22:                  var module = from e  in  root.Descendants( "module" )
   23:                                //where e.Attribute("name").Value == "xxxx" 
   24:                               select e;
   25:   
   26:                   if  (!module.Any())  return ;
   27:   
   28:                  ModuleName = module.FirstOrDefault().Attribute( "name" ).Value;
   29:   
   30:                  var menus = from e  in  module.FirstOrDefault().Descendants( "menu" )
   31:                              select e;
   32:   
   33:                   if  (!menus.Any())  return ;
   34:   
   35:                  var menuitems = menus.Select(xElement =>  new  MainMenuItemModel
   36:                                                               {
   37:                                                                   Id = xElement.Attribute( "id" ).Value,
   38:                                                                   Text = xElement.Attribute( "text" ).Value,
   39:                                                                   ActionName = xElement.Attribute( "action" ).Value,
   40:                                                                   ControllerName = xElement.Attribute( "controller" ).Value
   41:                                                               }).ToList();
   42:   
   43:                  MainMenuItems = menuitems;
   44:              }
   45:               catch 
   46:              {
   47:                   //TODO: logging 
   48:              }
   49:          }
   50:           public   string  ModuleName { get; set; }
   51:           public  IEnumerable<MainMenuItemModel> MainMenuItems { get; set; }
   52:      }
   53:  }

每个模块的配置文件为{projectName}.config,格式如下:

    1:   <?  xml   version  ="1.0"   encoding  ="utf-8"  ? > 
    2:   <  configuration  > 
    3:     <  module   name  ="Module2"  > 
    4:       <  mainmenu  > 
    5:         <  menu   id  ="modul2"   text  ="Team"   action  ="Index"   controller  ="Team"  /> 
    6:       </  mainmenu  > 
    7:     </  module  > 
    8:   </  configuration  > 

为了简单起见,只保留了注入主菜单的部分,为了让读者简单易懂。明白了以后你自己可以任意扩展…

代码:IModuleConfigDictionary,接口

模块配置文件{projectName}.config的位置:

为什么每个模块的Class library project都需要一个web.config呢?因为如果没有这个,那就没有Razor智能提示,大家可以参考这篇文章《 How to get Razor intellisense for @model in a class library project 》。

闲话几句插件式架构(Plugin Architecture)或者模块化(Modular)架构

插件式架构(Plugin Architecture)或者模块化(Modular)架构是大型应用必须的架构,关于什么是Plugin,什么是模块化模式,这种架构的优缺点等我就不说了,自己百谷歌度。关于 .NET下面的插件式架构 和模块化开发实现方法,基本上用 AppDomain 实现,当检测到一个新的插件Plugin时,实例化一个新的 AppDomain 并加载Assembly反射类等,由于 AppDomain 很好的隔离各个Plugin,所以跨域通信要用 MarshalByRefObject类 ,具体做法可以参考这篇文章《 基于AppDomain的"插件式"开发 》。另外,有很多框架提供了模块化/插件开发的框架,例如 Prism 、 MEF(Managed Extensibility Framework ,.NET 4.0 内置)等。

客户端插件架构

还有一种插件架构是客户端插件架构(Javascript 模块化),如 jQuery UI Widget Factory , Silk Project 就是很好的例子。

本文源码下载

源码下载请 点击此处 。运行源码之前务必阅读 此文 和本文。注意:本文抛砖引玉,力求简单,易懂,并非完整的架构实现,更多丰富的功能实现一切皆有可能,只要在理解的基础上。

 

分类:  C#

标签:  架构 ,  ASP.NET

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于基于ASP.NET MVC3 Razor的模块化/插件式架构实现的详细内容...

  阅读:47次

上一篇: userdata)

下一篇:JetBrains.com Products