ASP.NET MVC的插件体系结构
我已经花了一些时间看菲尔·哈克关于分组控制器的文章非常有趣的东西。
目前,我正试图弄清楚是否可以使用相同的思路为我正在开发的项目创build插件/模块化体系结构。
所以我的问题是:是否有可能在菲尔的文章区域跨多个项目分裂?
我可以看到名字空间会自行解决,但是我关心的是正确的地方。 是否可以通过构build规则进行sorting?
假设以上是可能的在一个单一的解决scheme中的多个项目,没有人有任何想法,使一个单独的解决scheme和编码预定义的一组接口的可能性最好的方法吗? 从一个区域移动到一个插件。
我有一些插件架构的经验,但不是群众,所以在这方面的任何指导将是有益的。
几个星期前,我做了一个概念validation,我把一个完整的组件堆栈:一个模型类,一个控制器类和它们关联的视图放到一个DLL中,添加/调整了一个 VirtualPathProvider类的例子 ,他们会适当地解决在DLL中的那些。
最后,我把这个DLL放到了一个适当configuration的MVC应用程序中,就像它从一开始就是MVC应用程序的一部分一样。 我把它推进了一点,它与这些小迷你MVC插件中的5个就好了。 很显然,你必须在洗牌时注意你的引用和configuration依赖关系,但是它确实有效。
该练习旨在为我为客户构build的基于MVC的平台提供插件function。 有一组核心的控制器和视图,在每个站点的实例中都增加了更多可选的控制器和视图。 我们将要把这些可选位制作成这些模块化的DLL插件。 到现在为止还挺好。
我在我的网站上写了一个关于我的原型和ASP.NET MVC插件示例解决scheme的概述。
编辑:4年来,我一直在做不less插件的ASP.NET MVC应用程序,不再使用我上面描述的方法。 在这一点上,我通过MEF运行所有的插件,而不是把控制器插入到插件中。 相反,我使通用控制器,使用路由信息来selectMEF插件,并把工作交给插件等。只是想我会添加,因为这个答案得到了一个公平的位。
实际上,我正在使用一个可扩展性框架来使用ASP.NET MVC。 我的可扩展性框架基于着名的Ioc容器:Structuremap。
我试图完成的用例很简单:创build一个应该有一些基本function的应用程序,可以为每个客户(=多租户)扩展。 应用程序只能托pipe一个实例,但可以针对每个客户调整此实例,而无需对核心网站进行任何更改。
我受到Ayende Rahien撰写的关于多重性的文章的启发: http ://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy–Approaches-and-Applicability.aspx灵感的另一个来源是埃里克·埃文斯关于领域驱动devise的书。 我的扩展性框架基于存储库模式和根聚合的概念。 为了能够使用框架,托pipe应用程序应该围绕仓库和域对象进行构build。 控制器,存储库或域对象在运行时由ExtensionFactory绑定。
插件只是一个包含控制器或存储库或域对象的组件,它们遵守特定的命名约定。 命名约定很简单,每个类都应该以customerID作为前缀,例如:AdventureworksHomeController。
要扩展应用程序,请在应用程序的扩展文件夹中复制插件程序集。 当用户请求客户根文件夹下的页面时,例如: http : //multitenant-site.com/[ customerID ] /[ controller] /[ action]框架检查是否存在该特定客户的插件并实例化自定义插件类,否则加载默认一次。 自定义类可以是控制器 – 存储库或域对象。 这种方法可以扩展从数据库到用户界面的所有级别的应用程序,通过域模型,存储库。
如果要扩展一些现有的function,可以创build一个包含核心应用程序子类的插件程序集。 当您必须创build全新的function时,您需要在插件中添加新的控制器。 这些控制器将在请求相应的url时由MVC框架加载。 如果要扩展UI,可以在扩展文件夹中创build一个新视图,并通过新的或子类别的控制器引用该视图。要修改现有的行为,可以创build新的存储库或域对象或对现有的子对象进行子类别化。 框架职责是确定为特定客户加载哪个控制器/存储库/域对象。
我build议看看结构图( http://structuremap.sourceforge.net/Default.htm ),特别是在registryDSLfunctionhttp://structuremap.sourceforge.net/RegistryDSL.htm 。
这是我在应用程序启动时用来注册所有插件控制器/存储库或域对象的代码:
protected void ScanControllersAndRepositoriesFromPath(string path) { this.Scan(o => { o.AssembliesFromPath(path); o.AddAllTypesOf<SaasController>().NameBy(type => type.Name.Replace("Controller", "")); o.AddAllTypesOf<IRepository>().NameBy(type => type.Name.Replace("Repository", "")); o.AddAllTypesOf<IDomainFactory>().NameBy(type => type.Name.Replace("DomainFactory", "")); }); }
我也使用从System.Web.MVCinheritance的ExtensionFactory。 DefaultControllerFactory。 该工厂负责加载扩展对象(控制器/registry或域对象)。 您可以通过在Global.asax文件中启动时注册它们来插入自己的工厂:
protected void Application_Start() { ControllerBuilder.Current.SetControllerFactory( new ExtensionControllerFactory() ); }
这个框架作为一个完全可操作的示例网站可以在http://code.google.com/p/multimvc/
所以我和上面的J Wynia的例子有一点关系 。 非常感谢那btw。
我改变了一些东西,以便VirtualPathProvider的扩展使用一个静态构造函数来创build一个以系统中各个dll的.aspx结尾的所有可用资源的列表。 这很辛苦,但只有我们只做一次。
这可能是虚拟文件应该被使用的方式的完全滥用;-)
你最终得到一个:
私有静态IDictionary resourceVirtualFile;
string是虚拟path。
下面的代码对.aspx文件的命名空间做了一些假设,但是它可以在简单的情况下工作。 这个好东西是,你不必创build从资源名称创build复杂的视图path。
class ResourceVirtualFile : VirtualFile { string path; string assemblyName; string resourceName; public ResourceVirtualFile( string virtualPath, string AssemblyName, string ResourceName) : base(virtualPath) { path = VirtualPathUtility.ToAppRelative(virtualPath); assemblyName = AssemblyName; resourceName = ResourceName; } public override Stream Open() { assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName + ".dll"); Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyName); if (assembly != null) { Stream resourceStream = assembly.GetManifestResourceStream(resourceName); if (resourceStream == null) throw new ArgumentException("Cannot find resource: " + resourceName); return resourceStream; } throw new ArgumentException("Cannot find assembly: " + assemblyName); } //todo: Neaten this up private static string CreateVirtualPath(string AssemblyName, string ResourceName) { string path = ResourceName.Substring(AssemblyName.Length); path = path.Replace(".aspx", "").Replace(".", "/"); return string.Format("~{0}.aspx", path); } public static IDictionary<string, VirtualFile> FindAllResources() { Dictionary<string, VirtualFile> files = new Dictionary<string, VirtualFile>(); //list all of the bin files string[] assemblyFilePaths = Directory.GetFiles(HttpRuntime.BinDirectory, "*.dll"); foreach (string assemblyFilePath in assemblyFilePaths) { string assemblyName = Path.GetFileNameWithoutExtension(assemblyFilePath); Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilePath); //go through each one and get all of the resources that end in aspx string[] resourceNames = assembly.GetManifestResourceNames(); foreach (string resourceName in resourceNames) { if (resourceName.EndsWith(".aspx")) { string virtualPath = CreateVirtualPath(assemblyName, resourceName); files.Add(virtualPath, new ResourceVirtualFile(virtualPath, assemblyName, resourceName)); } } } return files; } }
然后你可以在扩展的VirtualPathProvider中做这样的事情:
private bool IsExtended(string virtualPath) { String checkPath = VirtualPathUtility.ToAppRelative(virtualPath); return resourceVirtualFile.ContainsKey(checkPath); } public override bool FileExists(string virtualPath) { return (IsExtended(virtualPath) || base.FileExists(virtualPath)); } public override VirtualFile GetFile(string virtualPath) { string withTilda = string.Format("~{0}", virtualPath); if (resourceVirtualFile.ContainsKey(withTilda)) return resourceVirtualFile[withTilda]; return base.GetFile(virtualPath); }
我想可以在插件项目中留下你的观点。
这是我的想法:你需要一个ViewEngine来调用插件(可能通过一个接口)并请求视图(IView)。 然后,插件将不通过它的url实例化视图引擎(如普通的ViewEngine所做的那样 – /Views/Shared/View.asp),而是通过其视图的名称),例如通过reflection或DI / IoC容器。
插件中的视图的返回可能我甚至硬编码(简单的例子如下):
public IView GetView(string viewName) { switch (viewName) { case "Namespace.View1": return new View1(); case "Namespace.View2": return new View2(); ... } }
…这只是一个想法,但我希望它可以工作,或者只是一个很好的灵感。
这篇文章可能有点晚了,但是我一直在玩ASP.NET MVC2,并且已经使用“Areas”function创build了一个原型。
以下是任何有兴趣的人的链接: http : //www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/
[作为答复张贴,因为我不能评论]
伟大的解决scheme – 我使用J Wynia的方法,并从独立的程序集中获取视图。 但是,这种方法似乎只能呈现视图。 插件中的控制器似乎不被支持,是正确的? 例如,如果一个插件的视图发回了一个post,那么该插件中的视图控制器将不会被调用 。 相反,它将被路由到根MVC应用程序中的控制器。 我正确理解这个问题还是有解决这个问题的方法吗?