基于MVC4 MEF的动态加载插件 - c#

已更新:在这篇文章中阅读以下内容,以寻求最小解决方案

我对带有插件的MVC4解决方案有一些新手问题。我用谷歌搜索了一下,发现
一些好东西,但它不完全符合我的要求,因此我在这里要求一些建议。

在MVC中,类似于小部件的插件的最佳解决方案似乎是可移植区域(在MvcContrib包中)。我在这里找到了基本指导:

http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/

还有一些有用的提示:

http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx

这篇文章中的更多内容:

How to create ASP.NET MVC area as a plugin DLL?

太酷了,但可惜我的要求有些不同:

不幸的是,我需要一个可以动态添加和发现插件的系统,而对于可移植区域,情况并非如此,必须由主MVC站点项目引用。我只想上传一些东西到网站上,让它发现并使用新的组件,所以我将为此使用MEF。
幸运的是,我的插件将不像小部件,它们可能非常复杂且异构。相反,它们是必须遵循通用共享模式的组件。可以将它们像专门的编辑器一样:对于每种数据类型,我将提供一个具有编辑功能的组件:新建,编辑,删除。因此,我想到了实现通用接口并提供诸如New,Edit,Delete之类的操作的插件控制器。
我必须使用MVC4,将来我将不得不添加本地化和移动自定义。
我必须避免来自复杂框架的依赖,并使代码尽可能简单。

因此,每当我想在该网站中添加新的数据类型进行编辑时,我都想在其plugins文件夹中放置一个DLL,以存放逻辑内容(控制器等)以及正确位置的一些视图,以获取该网站发现并使用新的编辑器。

最终,我可以将视图包含在DLL本身中(我发现了这一点:http://razorgenerator.codeplex.com和本教程:http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/,我想我可以将它们与Codeplex razorgenerator一起使用,因为它所指的代码与VS2012不兼容),但最好将它们分开(也是由于本地化和移动感知要求);我当时正在考虑将一种上传机制添加到我的站点管理区域,在该区域中,您可以使用包含控制器和带有视图的文件夹的DLL上传单个zip,然后让服务器在需要的地方解压缩并存储文件。这将使我可以轻松地修改视图,而不必再次部署整个外接程序。

因此,我开始寻找MEF和MVC,但是大多数文章都涉及MVC2,并且不兼容。我对此很幸运,它主要集中在Web API上,但是看起来很有前途并且很简单:

http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-and-webapi.html

这实际上将基于MEF的依赖项解析器和控制器工厂添加到“标准” MVC应用程序中。无论如何,帖子中的示例是指单组装解决方案,而我需要部署几个不同的插件。因此,我稍微修改了代码,以使用指向我的plugins文件夹的MEF DirectoryCatalog(而不是AssemblyCatalog),然后创建了一个测试MVC解决方案,并在类库中使用了一个插件。

无论如何,当我尝试加载插件控制器时,框架使用空类型调用我的工厂GetControllerInstance,因此,MEF当然不能进行合成。可能我缺少明显的东西,但是我是MVC 4的新手,欢迎任何建议或有用的链接(符合MVC4的链接)。谢谢!

这是基本代码:

公共静态类MefConfig
{
公共静态无效RegisterMef()
{
CompositionContainer容器= ConfigureContainer();

ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));

System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver =
新的MefDependencyResolver(container);
}

私有静态CompositionContainer ConfigureContainer()
{
// AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

DirectoryCatalog目录=新的DirectoryCatalog(
HostingEnvironment.MapPath(“〜/ Plugins”));
CompositionContainer容器=新的CompositionContainer(catalog);
返回容器;
}
}

公共类MefDependencyResolver:IDependencyResolver
{
私有只读CompositionContainer _container;

公共MefDependencyResolver(CompositionContainer容器)
{
_container =容器;
}

公共IDependencyScope BeginScope()
{
返回这个
}

公共对象GetService(Type serviceType)
{
var export = _container.GetExports(serviceType,null,null).SingleOrDefault();
return(export!= null?export.Value:null);
}

公共IEnumerable GetServices(类型serviceType)
{
var export = _container.GetExports(serviceType,null,null);
列出createdObjects = new List();

如果(exports.Any())
createdObjects.AddRange(exports.Select(export => export.Value));

返回createdObjects;
}

公共无效Dispose()
{
}
}

公共类MefControllerFactory:DefaultControllerFactory
{
私有只读CompositionContainer _compositionContainer;

公共MefControllerFactory(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}

受保护的重写IController GetControllerInstance(
System.Web.Routing.RequestContext requestContext,类型controllerType)
{
如果(controllerType == null)抛出新的ArgumentNullException(“ controllerType”);
var export = _compositionContainer.GetExports(controllerType,null,null).SingleOrDefault();

IController结果;

如果(null!=导出)结果= export.Value作为IController;
其他
{
结果= base.GetControllerInstance(requestContext,controllerType);
_compositionContainer.ComposeParts(result);
} // eelse

返回结果;
}
}

您可以从此处下载完整的测试解决方案:

http://www.filedropper.com/mvcplugins

编辑:第一个可行的最小解决方案

这是我的发现,希望它们对从此开始的其他新手有用:我没有成功运行上述答复中引用的框架,我想必须为VS2012和MVC4进行一些更新。无论如何,我看了一下代码,然后用谷歌搜索了一下:

1)首先,让我感到困惑的是同一个名称的两个不同接口:IDependencyResolver。如果我了解得很好,则一个(System.Web.Http.Dependencies.IDependencyResolver)用于webapi,另一个(System.Web.Mvc.IDependencyResolver)用于通用DI。这篇文章对我有帮助:http://lucid-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/。

2)另外,第三个组件是DefaultControllerFactory派生的控制器工厂,这对本文很重要,因为它是用于插件托管的控制器的工厂。

这是我针对所有这些的实现,并从几个示例中进行了一些修改:首先是HTTP解析器:

公共密封类MefHttpDependencyResolver:IDependencyResolver
{
私有只读CompositionContainer _container;

公共MefHttpDependencyResolver(CompositionContainer容器)
{
如果(container == null)抛出新的ArgumentNullException(“ container”);
_container =容器;
}

公共对象GetService(Type serviceType)
{
如果(serviceType == null)抛出新的ArgumentNullException(“ serviceType”);

字符串名称= AttributedModelServices.GetContractName(serviceType);

尝试
{
返回_container.GetExportedValue(name);
}
抓住
{
返回null;
}
}

公共IEnumerable GetServices(类型serviceType)
{
如果(serviceType == null)抛出新的ArgumentNullException(“ serviceType”);

字符串名称= AttributedModelServices.GetContractName(serviceType);

尝试
{
返回_container.GetExportedValues(name);
}
抓住
{
返回null;
}
}

公共IDependencyScope BeginScope()
{
返回这个
}

公共无效Dispose()
{
}
}

然后是MVC解析器,它非常相似,即使在这种情况下对于虚拟样本也完全没有必要:

公共类MefDependencyResolver:IDependencyResolver
{
私有只读CompositionContainer _container;

公共MefDependencyResolver(CompositionContainer容器)
{
如果(container == null)抛出新的ArgumentNullException(“ container”);
_container =容器;
}

公共对象GetService(Type type)
{
如果(type == null)抛出新的ArgumentNullException(“ type”);

字符串名称= AttributedModelServices.GetContractName(type);

尝试
{
返回_container.GetExportedValue(name);
}
抓住
{
返回null;
}
}

公共IEnumerable GetServices(类型类型)
{
如果(type == null)抛出新的ArgumentNullException(“ type”);

字符串名称= AttributedModelServices.GetContractName(type);

尝试
{
返回_container.GetExportedValues(name);
}
抓住
{
返回null;
}
}
}

最后是控制器工厂:

[导出(typeof(IControllerFactory))
公共类MefControllerFactory:DefaultControllerFactory
{
私有只读CompositionContainer _container;

[导入构造函数]
公共MefControllerFactory(CompositionContainer容器)
{
如果(container == null)抛出新的ArgumentNullException(“ container”);
_container =容器;
}

公共重写IController CreateController(RequestContext requestContext,字符串controllerName)
{
var controller = _container
.GetExports()
哪里(c => c.Metadata.Name.Equals(controllerName,StringComparison.OrdinalIgnoreCase))
选择(c => c.Value)
.FirstOrDefault();

返回控制器base.CreateController(requestContext,controllerName);
}
}

至于示例控制器,我将其创建到类库项目中:

[导出(typeof(IController))
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata(“ Name”,“ Alpha”)]
公共密封类AlphaController:控制器
{
公共ActionResult Index()
{
ViewBag.Message =“你好,这是PLUGIN控制器!”;

返回View();
}
}

在主项目(MVC站点)中,我有一个Plugins文件夹,可以在其中复制此DLL,并且在其文件夹中还包含该控制器视图的“标准”视图集。

这是最简单的情况,可能还有更多的要查找和完善的地方,但是我首先要很简单。无论如何,任何建议都是值得欢迎的。

参考方案

我目前正在处理同一问题。我找到了这个解决方案:

博客文章:http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/
源代码:https://bitbucket.org/darincreason/mvcapplication8.web

基本上,它在Web应用程序启动时从指定位置以某种名称模式加载程序集:

AssemblyInfo.cs:

[assembly: PreApplicationStartMethod(
  typeof(PluginAreaBootstrapper), "Init")]

PluginAreaBootstrapper.cs:

public class PluginAreaBootstrapper
{
    public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();

    public static List<string> PluginNames()
    {
        return PluginAssemblies.Select(
            pluginAssembly => pluginAssembly.GetName().Name)
            .ToList();
    }

    public static void Init()
    {
        var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");

        foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
            PluginAssemblies.Add(Assembly.LoadFile(file));

        PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);

        // Add assembly handler for strongly-typed view models
        AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
    }

    private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
    {
        var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
        // Check we don't already have the assembly loaded
        foreach (var assembly in currentAssemblies)
        {
            if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
            {
                return assembly;
            }
        }

        return null;
    }
}

但是我相信您可以创建一些目录观察器来动态加载程序集,因此您甚至不需要重新启动Web应用程序。

我认为它可以满足您的1、2和4的需求。它非常简单,不需要任何框架,具有最少的配置,可以动态加载插件,并且可以与MVC 4一起使用。

该解决方案将程序集插入Area目录,但是我相信您可以很容易地对其进行调整以使其像使用路由一样播放。

ASP.NET MVC 5自定义登录,无需脚手架,数据库优先 - c#

我对asp.net和mvc还是很陌生,所以我正在努力学习尽可能多的知识...为此,我从头开始编写博客网站,但是我对身份验证和授权有些困惑。由于我倾向于不真正使用任何脚手架的东西,所以我首先要使用数据库,所以不希望asp.net身份为我创建表。我对散列和加盐密码很酷,并对照数据库检查用户,我遇到的麻烦是将用户设置为登录状态并检查他们应该能够访问什么。我真的很想…

ASP.NET MVC ExecuteResult与ActionResult - c#

我见过它用来向响应添加标头,然后返回文件进行流传输。public override void ExecuteResult(ControllerContext context) { ... response.AddHeader("Accept-Ranges", "bytes"); response.AddHeader(&…

MVC C#TempData - c#

有人可以解释一下MVC中TempData的目的。我了解它的行为类似于ViewBag,但除此之外它还有什么作用。 参考方案 TempData应该是一个短暂的实例,您应该 仅在当前和后续请求中使用它!以来 TempData以这种方式工作,您需要确定下一步 要求,并且重定向到另一个视图是您唯一的时间 可以保证这一点。因此,唯一使用TempData的方案 在重定向时…

在ASP.NET MVC中创建数据库回调的最有效方法 - c#

我有一个ASP.NET MVC网页,该网页基本上通过日期过滤器显示MS SQL数据库中表的行。当新行插入数据库表时,我想用新行列表更新网页视图。实现此目标的最有效方法是什么?基本上,我想从我的JavaScript创建一个到数据库服务器的回调,以用新结果更新UI。假设数据库表中的行数很大。(〜1百万)谢谢,cas 参考方案 如果数据库更新非常频繁,则可以按特定…

VB.NET C#语法的泛型 - c#

您能否告诉我此VB.NET代码的C#等效项:Public Partial Class Index Inherits System.Web.Mvc.Viewpage(Of List(Of Task)) End Class 我不确定在哪里/如何在C#中添加它:public partial class DirList : System.Web.Mvc.ViewPa…