您的位置:首页 > 编程语言 > ASP

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

2012-06-24 21:33 417 查看
本文主要探讨了一种基于ASP.NET MVC3 Razor的模块化(Plugin)/插件(plugin)式架构的实现方法。本文借鉴了《Compile
your asp.net mvc Razor views into a seperate dll》作者提供的方法。敬请注意。其实ASP.NET MVC的模块化(Plugin)/插件(plugin)式架构讨论的很多,但基于Razor视图引擎的很少(如:MVC2插件架构例子都是基于WebForm的,MVCContrib

Portable Areas也是,还有这个Plugin架构)。要么就是非常复杂非常重量级的框架,例如Orchard CMS的模块化做的很好,可惜太重量级了,也没独立的模块可以剥离出来。所以我们追寻的是简单的基于ASP.NET
MVC3 Razor的模块化(Plugin)/插件(plugin)式架构的实现方法。本文最后实现的项目结构如下图:(插件都放到~/Plugin目录下,按功能划分模块,每个模块都有M,V,C)





其中,业务模块(class library project)包含其所有的视图、控制器等,模型可以放在里面也可以单独放一个project。主web项目没有引用业务模块,业务模块会编译到主web项目的~/plugin目录下面(注意:不是bin目录),然后当web应用启动的时候自动加载plugin目录下面的模块。最后运行起来的效果如下图:





其中红色的区域都是plugin进去的,那个tab的标题plugin到母版页的主菜单,tab内容也来自plugin。下面说说如何实现这样的ASP.NET MVC插件式plugin架构(模块化架构)。

实现的难点在动态加载UI视图(*.cshtml, _layout.cshtml, _viewStart.cshtml)

废话少说,直入要害。基于ASP.NET MVC3 Razor的编译发生在两个层面:

控制器(Controller), 模型(Models),和其它所有的C#代码等有msbuild(或者VisualStudio)编译到bin目录下的程序集(assembly)

视图(*.aspx, *.cshtml)由ASP.NET在运行时动态编译。当一个Razor视图(*.cshtml)显示前,Razor视图引擎调用BuildManager把视图(*.cshtml)编译到动态程序集assembly,然后使用Activator.CreateInstance来实例化新编译出来的对象,最后显示出来。如果视图(*.cshtml)用到@model绑定model,那么还会自动加载bin或者GAC里面的Model。

所以如果我们要动态加载插件(plugin),用反射bin目录下的程序集(assembly)的方法很容易搞定上面的第一部分(C#代码的部分),但UI视图的部分(上面第二部分)(特别是*.cshtml, 母版_layout.cshtml, 基视图_viewStart.cshtml)就比较难搞定。而且每次报错都是一样的,那就是Controller找不到相应的视图View,基本不知所云而且根本不是要点:view ….
or its master was not found or no view engine supports the searched locations. The following locations were searched: …,因此要搞定UI视图的部分(上面第二部分)(特别是*.cshtml, 母版_layout.cshtml, 基视图_viewStart.cshtml),就需要自己动手了,基本原理是:

重载RazorBuildProvider,用来动态编译视图

实现一个自定义VirtualPathProvider,从虚拟路径自定义判断读取资源(从插件中加载资源),如果要使用编译的视图就返回编译的VirtualFile

实现一个容器Dictionary保存已编译的视图和虚拟路径,例如path <~/views/team/index.cshtml> type <Area.Module2.Views.Team._Page_Views_Team_Index_cshtml>,或者path <~/views/_viewstart.cshtml> type <Area.Module1.Views._Page_Views__ViewStart_cshtml>

代码:自定义VirtualPathProvider,从虚拟路径自定义判断读取资源(从插件中加载资源),如果要使用编译的视图就返回编译的VirtualFile

1: using System;


2: using System.Collections.Generic;


3: using System.Linq;


4: using System.Reflection;


5: using System.Web.Caching;


6: using System.Web.Hosting;


7: using System.Web.WebPages;


8:


9: namespace Common.Framework


10: {


11:     public class CompiledVirtualPathProvider: VirtualPathProvider


12:     {


13:         /// <summary>


14:         /// Gets a value that indicates whether a file exists in the virtual file system.


15:         /// </summary>


16:         /// <returns>


17:         /// true if the file exists in the virtual file system; otherwise, false.


18:         /// </returns>


19:         /// <param name="virtualPath">The path to the virtual file.</param>


20:         public override bool FileExists(string virtualPath)


21:         {


22:             return


23:                 GetCompiledType(virtualPath) != null


24:                 || Previous.FileExists(virtualPath);


25:         }


26:


27:         public Type GetCompiledType(string virtualPath)


28:         {


29:             return ApplicationPartRegistry.Instance.GetCompiledType(virtualPath);


30:         }


31:


32:         /// <summary>


33:         /// Gets a virtual file from the virtual file system.


34:         /// </summary>


35:         /// <returns>


36:         /// A descendent of the <see cref="T:System.Web.Hosting.VirtualFile"/> class that represents a file in the virtual file system.


37:         /// </returns>


38:         /// <param name="virtualPath">The path to the virtual file.</param>


39:         public override VirtualFile GetFile(string virtualPath)


40:         {


41:             if (Previous.FileExists(virtualPath))


42:             {


43:                 return Previous.GetFile(virtualPath);


44:             }


45:             var compiledType = GetCompiledType(virtualPath);


46:             if (compiledType != null)


47:             {


48:                 return new CompiledVirtualFile(virtualPath, compiledType);


49:             }


50:             return null;


51:         }


52:


53:         public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)


54:         {


55:             if (virtualPathDependencies == null)


56:                 return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);


57:


58:             return Previous.GetCacheDependency(virtualPath,


59:                     from vp in virtualPathDependencies.Cast<string>()


60:                     where GetCompiledType(vp) == null


61:                     select vp


62:                   , utcStart);


63:         }


64:


65:     }


66: }


代码:容器Dictionary保存已编译的视图和虚拟路径,例如path <~/views/team/index.cshtml> type <Area.Module2.Views.Team._Page_Views_Team_Index_cshtml>,路径注册以后,会从容器库全局搜索所有注册过的视图,也就是说即使你视图引用的_layout.cshtml和_viewStart.cshtml在其他的Class library project照样可以找到。

1: using System;


2: using System.Collections.Generic;


3: using System.Diagnostics;


4: using System.Linq;


5: using System.Reflection;


6: using System.Web;


7: using System.Web.WebPages;


8:


9: namespace Common.Framework


10: {


11:     public class DictionaryBasedApplicationPartRegistry : IApplicationPartRegistry


12:     {


13:         private static readonly Type webPageType = typeof(WebPageRenderingBase);


14:         private readonly Dictionary<string, Type> registeredPaths = new Dictionary<string, Type>();


15:


16:         /// <summary>


17:         ///


18:         /// </summary>


19:         /// <param name="virtualPath"></param>


20:         /// <returns></returns>


21:         public virtual Type GetCompiledType(string virtualPath)


22:         {


23:             if (virtualPath == null) throw new ArgumentNullException("virtualPath");


24:


25:             //Debug.WriteLine(String.Format("---GetCompiledType : virtualPath <{0}>", virtualPath));


26:


27:             if (virtualPath.StartsWith("/"))


28:                 virtualPath = VirtualPathUtility.ToAppRelative(virtualPath);


29:             if (!virtualPath.StartsWith("~"))


30:                 virtualPath = !virtualPath.StartsWith("/") ? "~/" + virtualPath : "~" + virtualPath;


31:           virtualPath = virtualPath.ToLower();


32:             return registeredPaths.ContainsKey(virtualPath)


33:                        ? registeredPaths[virtualPath]


34:                        : null;


35:         }


36:


37:         public void Register(Assembly applicationPart)


38:         {


39:             ((IApplicationPartRegistry)this).Register(applicationPart, null);


40:         }


41:


42:         public virtual void Register(Assembly applicationPart, string rootVirtualPath)


43:         {


44:             //Debug.WriteLine(String.Format("---Register assembly <{0}>, path <{1}>", applicationPart.FullName, rootVirtualPath));


45:


46:             foreach (var type in applicationPart.GetTypes().Where(type => type.IsSubclassOf(webPageType)))


47:             {


48:                 //Debug.WriteLine(String.Format("-----Register type <{0}>, path <{1}>", type.FullName, rootVirtualPath));


49:


50:                 ((IApplicationPartRegistry)this).RegisterWebPage(type, rootVirtualPath);


51:             }


52:       }


53:


54:         public void RegisterWebPage(Type type)


55:         {


56:             ((IApplicationPartRegistry)this).RegisterWebPage(type, string.Empty);


57:       }


58:


59:         public virtual void RegisterWebPage(Type type, string rootVirtualPath)


60:         {


61:             var attribute = type.GetCustomAttributes(typeof(PageVirtualPathAttribute), false).Cast<PageVirtualPathAttribute>().SingleOrDefault<PageVirtualPathAttribute>();


62:             if (attribute != null)


63:             {


64:               var rootRelativeVirtualPath = GetRootRelativeVirtualPath(rootVirtualPath ?? "", attribute.VirtualPath);


65:


66:                 //Debug.WriteLine(String.Format("---Register path/type : path <{0}> type <{1}>", rootRelativeVirtualPath.ToLower(),


67:                 //                              type.FullName));


68:                 registeredPaths[rootRelativeVirtualPath.ToLower()] = type;


69:             }


70:         }


71:


72:         static string GetRootRelativeVirtualPath(string rootVirtualPath, string pageVirtualPath)


73:         {


74:             string relativePath = pageVirtualPath;


75:             if (relativePath.StartsWith("~/", StringComparison.Ordinal))


76:             {


77:                 relativePath = relativePath.Substring(2);


78:             }


79:             if (!rootVirtualPath.EndsWith("/", StringComparison.OrdinalIgnoreCase))


80:             {


81:                 rootVirtualPath = rootVirtualPath + "/";


82:             }


83:             relativePath = VirtualPathUtility.Combine(rootVirtualPath, relativePath);


84:             if (!relativePath.StartsWith("~"))


85:             {


86:                 return !relativePath.StartsWith("/") ? "~/" + relativePath : "~" + relativePath;


87:             }


88:             return relativePath;


89:         }


90:     }


91: }


下面的代码很关键,用PreApplicationStartMethod关键字(.NET 4.0开始支持)使得代码在Application_Start之前执行。

有关[assembly: PreApplicationStartMethod(typeof(SomeClassLib.Initializer), "Initialize")]详细信息请参考这个页面这个页面

1: using System.Web;


2: using System.Web.Compilation;


3: using System.Web.Hosting;


4: using Common.Framework;


5: using Common.PrecompiledViews;


6:


7: [assembly: PreApplicationStartMethod(typeof(PreApplicationStartCode), "Start")]


8:


9: namespace Common.Framework


10: {


11:     public static class PreApplicationStartCode


12:     {


13:         private static bool _startWasCalled;


14:


15:       public static void Start()


16:         {


17:             if (_startWasCalled)


18:             {


19:                 return;


20:             }


21:             _startWasCalled = true;


22:


23:             //Register virtual paths


24:           HostingEnvironment.RegisterVirtualPathProvider(new CompiledVirtualPathProvider());


25:


26:           //Load Plugin Folder, 


27:             PluginLoader.Initialize();


28:         }


29:     }


30: }


代码:PluginLoader,加载plugin目录里面的东东(assembly和module配置文件)

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


此外,使用SingleFileGenerator的优点是性能提升,缺点是修改了视图就要重新编译。

如何让ASP.NET加载BIN目录之外的路径的Assembly

我们把各个模块编译出来的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 FactorySilk Project就是很好的例子。

本文源码下载

源码下载请点击此处。运行源码之前务必阅读此文和本文。注意:本文抛砖引玉,力求简单,易懂,并非完整的架构实现,更多丰富的功能实现一切皆有可能,只要在理解的基础上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: