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

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                 

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

3-5-2012 5-07-19 PM

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

3-6-2012 10-28-51 AM

其中红色的区域都是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:         }
    
 

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值