asp.net-mvc ASP.NET MVC 的插件架构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/340183/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-07 23:11:30  来源:igfitidea点击:

Plug-in architecture for ASP.NET MVC

asp.net-mvcplugins

提问by Simon Farrow

I've been spending some time looking at Phil Haack's article on Grouping Controllersvery interesting stuff.

我花了一些时间看 Phil Haack 关于Grouping Controllers的文章,非常有趣。

At the moment I'm trying to figure out if it would be possible to use the same ideas to create a plug-in/modular architecture for a project I'm working on.

目前我正试图弄清楚是否有可能使用相同的想法为我正在处理的项目创建插件/模块化架构。

So my question is: Is it possible to have the Areas in Phil's article split across multiple projects?

所以我的问题是:是否可以将 Phil 文章中的区域拆分到多个项目中?

I can see that the name spaces will work themselves out, but I'm concerned about the views ending up in the right place. Is it something that can be sorted out with build rules?

我可以看到名称空间会自行解决,但我担心视图会出现在正确的位置。是否可以通过构建规则来解决?

Assuming that the above is possible with multiple projects in a single solution, does anyone have any ideas about the best way to make it possible with a separate solution and coding to a predefined set of interfaces? Moving from an Area to a plug-in.

假设在单个解决方案中的多个项目可以实现上述功能,是否有人对使用单独的解决方案和编码到一组预定义的接口使其成为可能的最佳方法有任何想法?从区域移动到插件。

I have some experiences with plug-in architecture but not masses so any guidance in this area would be useful.

我在插件架构方面有一些经验,但没有大量经验,因此这方面的任何指导都会有用。

采纳答案by J Wynia

I did a proof of concept a few weeks ago where I put a complete stack of components: a model class, a controller class and their associated views into a DLL, added/tweaked one of the examplesof the VirtualPathProvider classes that retrieve the views so they'd address those in the DLL appropriately.

几周前我做了一个概念证明,我把一个完整的组件堆栈:一个模型类、一个控制器类和它们关联的视图放到一个 DLL 中,添加/调整了检索视图的 VirtualPathProvider 类的一个示例,以便他们会适当地解决 DLL 中的问题。

In the end, I just dropped the DLL into an appropriately configured MVC app and it worked just like if it had been part of the MVC app from the start. I pushed it a bit further and it worked with 5 of these little mini-MVC plugins just fine. Obviously, you have to watch your references and config dependencies when shuffling it all around, but it did work.

最后,我只是将 DLL 放入一个适当配置的 MVC 应用程序中,它的工作方式就像它从一开始就是 MVC 应用程序的一部分一样。我进一步推动了它,它与 5 个小型 mini-MVC 插件一起工作得很好。显然,你必须注意你的引用和配置依赖项,但它确实有效。

The exercise was aimed at plugin functionality for an MVC-based platform I'm building for a client. There are a core set of controllers and views that are augmented by more optional ones in each instance of the site. We're going to be making those optional bits into these modular DLL plugins. So far so good.

该练习针对我为客户端构建的基于 MVC 的平台的插件功能。在站点的每个实例中有一组核心控制器和视图,这些控制器和视图由更多可选的控制器和视图增强。我们将把这些可选位制作成这些模块化的 DLL 插件。到现在为止还挺好。

I wrote up an overview of my prototype and a sample solution for ASP.NET MVC pluginson my site.

我在我的网站上写了我的原型概述和ASP.NET MVC 插件示例解决方案

EDIT: 4 years on, I've been doing quite a few ASP.NET MVC apps with plugins and no longer use the method I describe above. At this point, I run all of my plugins through MEF and don't put controllers into plugins at all. Rather, I make generic controllers that use the routing information to select MEF plugins and hand the work off to the plugin, etc. Just thought I'd add since this answer gets hit a fair bit.

编辑: 4 年过去了,我一直在做很多带有插件的 ASP.NET MVC 应用程序,不再使用我上面描述的方法。在这一点上,我通过 MEF 运行我所有的插件并且根本不将控制器放入插件中。相反,我制作了使用路由信息来选择 MEF 插件并将工作交给插件等的通用控制器。只是想我会添加,因为这个答案得到了相当多的打击。

回答by Geo

I'm actually working on an extensibility framework to use on top of ASP.NET MVC. My extensibility framework is based on the famous Ioc container: Structuremap .

我实际上正在研究一个在 ASP.NET MVC 之上使用的可扩展性框架。我的可扩展性框架基于著名的 Ioc 容器: Structuremap 。

The use case I'm trying to fulfill is simple: create an application that should have some basic functionality that can be extended for every customer (=multi-tenancy). There should only be one instance of the application hosted but this instance can be adapted for every customer without making any changes to the core website.

我试图实现的用例很简单:创建一个应用程序,该应用程序应该具有一些可以为每个客户扩展的基本功能(=多租户)。应该只托管一个应用程序实例,但该实例可以适用于每个客户,而无需对核心网站进行任何更改。

I was inspired by the article on multi tenacy written by Ayende Rahien: http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspxAnother source of inspiration was the book of Eric Evans on Domain Driven Design. My Extensibility framework is based on the repository pattern and the concept of root aggregates. To be able to use the framework the hosting application should be build around repositories and domain objects. The controllers, repositories or domain objects are bind at runtime by the ExtensionFactory.

我的灵感来自 Ayende Rahien 撰写的关于多租户的文章:http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx 另一个灵感来源是Eric Evans 关于领域驱动设计的书。我的可扩展性框架基于存储库模式和根聚合的概念。为了能够使用该框架,托管应用程序应该围绕存储库和域对象构建。控制器、存储库或域对象在运行时由 ExtensionFactory 绑定。

A plug-in is simply an asselmbly that contains Controllers or Repositories or Domain Objects that respects a specific naming convention. The naming convention is simple, every class should be prefixed by the customerID e.g.: AdventureworksHomeController.

插件只是一个包含遵循特定命名约定的控制器或存储库或域对象的集合。命名约定很简单,每个类都应该以 customerID 为前缀,例如:AdventureworksHomeController。

To extend an application you copy a plug-in assembly in the extension folder of the application. When a user request a page under the customer root folder e.g: http://multitenant-site.com/[customerID]/[controller]/[action]the framework check if there is a plug-in for that particular customer and instantiate the custom plug-in classes otherwise it loads the default once. The custom classes can be Controllers – Repositories or Domain Objects. This approach enables to extend an application at all levels, from the database to the UI, through the domain model, repositories.

要扩展应用程序,请在应用程序的扩展文件夹中复制插件程序集。当用户请求客户根文件夹下的页面时,例如:http: //multitenant-site.com/[customerID ]/[ controller]/[ action]框架检查是否有该特定客户的插件并实例化自定义插件类,否则它会加载默认值一次。自定义类可以是控制器 - 存储库或域对象。这种方法可以在所有级别上扩展应用程序,从数据库到 UI,通过域模型和存储库。

When you want to extend some existing features you create a plug-in an assembly that contains subclasses of the core application. When you've to create totally new functionalities you add new controllers inside the plug-in. These controllers will be loaded by the MVC framework when the corresponding url is requested. If you want to extend the UI you can create a new view inside the extension folder and reference the view by a new or subclassed controller .To modify existing behavior you can create new repositories or domain objects or sub classing exiting ones. The framework responsibility is to determine which controller/ repository / domain object should be loaded for a specific customer.
I advise to have a look at structuremap (http://structuremap.sourceforge.net/Default.htm) and especially at the Registry DSL features http://structuremap.sourceforge.net/RegistryDSL.htm.

当您想要扩展某些现有功能时,您可以创建一个包含核心应用程序子类的程序集的插件。当您必须创建全新的功能时,您可以在插件中添加新的控制器。当请求相应的 url 时,MVC 框架将加载这些控制器。如果您想扩展 UI,您可以在扩展文件夹内创建一个新视图,并通过新的或子类控制器引用该视图。要修改现有行为,您可以创建新的存储库或域对象或子类退出现有的。框架的职责是确定应该为特定客户加载哪个控制器/存储库/域对象。
我建议看看结构图(http://structuremap.sourceforge.net/Default.htm),尤其是在 Registry DSL 功能http://structuremap.sourceforge.net/RegistryDSL.htm

This is the code I use at the startup of the application to register all plug-in controllers/repositories or domain objects:

这是我在应用程序启动时用来注册所有插件控制器/存储库或域对象的代码:

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

I also use an ExtensionFactory inheriting from the System.Web.MVC. DefaultControllerFactory. This factory is responsible to load the extension objects (controllers/registries or domain objects). You can plugin your own factories by registering them at startup in the Global.asax file:

我还使用从 System.Web.MVC 继承的 ExtensionFactory。默认控制器工厂。该工厂负责加载扩展对象(控制器/注册表或域对象)。您可以通过在启动时在 Global.asax 文件中注册它们来插入您自己的工厂:

protected void Application_Start()
        {
            ControllerBuilder.Current.SetControllerFactory(
                new ExtensionControllerFactory()
                );
        }

This framework as a fully operational sample site can be found on: http://code.google.com/p/multimvc/

该框架作为一个完全可操作的示例站点可以在以下位置找到:http: //code.google.com/p/multimvc/

回答by Simon Farrow

So I had a little play around with the example from J Wyniaabove. Many thanks for that btw.

所以我对上面J Wynia的例子进行了一些尝试。顺便说一句,非常感谢。

I changed things so that the extension of the VirtualPathProvider used a static constructor to create a list of all of the available resources ending with .aspx in the various dll's in the system. It's laborious but only we're only doing it once.

我更改了一些内容,以便 VirtualPathProvider 的扩展使用静态构造函数来创建系统中各种 dll 中以 .aspx 结尾的所有可用资源的列表。这很费力,但只有我们只做一次。

It's probably a total abuse of the way that VirtualFiles are supposed to be used as well ;-)

这可能完全滥用了 VirtualFiles 的使用方式;-)

you end up with a:

你最终得到一个:

private static IDictionary resourceVirtualFile;

私有静态 IDictionary 资源虚拟文件;

with the string being virtual paths.

字符串是虚拟路径。

the code below makes some assumptions about the namespace of the .aspx files but it will work in simple cases. This nice thing being that you don't have to create complicated view paths they are created from the resource name.

下面的代码对 .aspx 文件的命名空间做了一些假设,但它可以在简单的情况下工作。一件好事是您不必创建复杂的视图路径,它们是根据资源名称创建的。

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

You can then do something like this in the extended VirtualPathProvider:

然后,您可以在扩展的 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);
    }

回答by gius

I guess it is possible to leave your views in the plug-in projects.

我想可以将您的观点留在插件项目中。

That's my idea: you need a ViewEngine that would call the plugin (probably through an interface) and request the view (IView). The plugin would then instantiate the view not through its url (as an ordinary ViewEngine does - /Views/Shared/View.asp) but through its name of the view )for example via reflection or DI/IoC container).

这就是我的想法:您需要一个 ViewEngine 来调用插件(可能通过接口)并请求视图(IView)。然后,该插件将不通过其 url(如普通 ViewEngine 所做的 - /Views/Shared/View.asp)而是通过其视图名称(例如通过反射或 DI/IoC 容器)实例化视图。

The returning of the view in the plugin might me even hardcoded (simple example follows):

插件中视图的返回甚至可能是硬编码的(简单示例如下):

public IView GetView(string viewName)
{
    switch (viewName)
    {
        case "Namespace.View1":
            return new View1();
        case "Namespace.View2":
            return new View2();
        ...
    }
}

...this was just an idea but I hope it could work or just be a good inspiration.

...这只是一个想法,但我希望它可以奏效,或者只是一个很好的灵感。

回答by Veebs

This post may be a little late but I've been playing with ASP.NET MVC2 and have come up with a prototype using the "Areas" feature.

这篇文章可能有点晚了,但我一直在玩 ASP.NET MVC2,并提出了一个使用“区域”功能的原型。

Here's the link for anyone who is interested: http://www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/

这是任何有兴趣的人的链接:http: //www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/

回答by tbehunin

[posting as an answer because I can't comment]

[发布作为答案,因为我无法发表评论]

Great solution - I used the approach by J Wynia and got it to render a view from a separate assembly. However, this approach appears to onlyrender the view. Controllers within the plugin do not appear to be supported, correct? For instance, if a view from a plugin did a post back, that views' controller within the plugin will notbe called. Instead, it will be routed to a controller within the root MVC application. Am I understanding this correctly or is there a workaround for this problem?

很好的解决方案 - 我使用了 J Wynia 的方法,并让它从单独的程序集中呈现视图。但是,这种方法似乎呈现视图。插件中的控制器似乎不受支持,对吗?例如,如果来自插件的视图进行了回发,不会调用该插件内的视图控制器。相反,它将被路由到根 MVC 应用程序中的控制器。我是否正确理解这一点,或者是否有解决此问题的方法?