【发布时间】:2012-12-12 09:31:50
【问题描述】:
我正在尝试使用许多插件创建一个 MVC4 Web 应用程序,即本质上是通过 MEF 导出的控制器以及解压到适当位置的内容文件。我找到了很多关于 MVC 插件的资料,主要与领域有关,但我不得不放弃 MvcContrib,这将是最明显的解决方案,因为它似乎没有更多的开发,显示了最新的 MVC 位的一些问题,而且我还想要这个架构的最简单的实现。
我的要求是这样的:
a) 一个基于 MEF 的 MVC 插件解决方案,我只需将一个包放到我的站点中即可使用,理想情况下甚至无需重新启动。这意味着将插件存储在与 Bin 不同的文件夹中,这也提供了更好的隔离。
b) 与 IoC 工具兼容的解决方案比单独由 MEF 完成的解决方案更完整。我倾向于为此使用 Autofac,因为它同时集成了 MEF 和 MVC4(目前为 RC)。
除了视图之类的内容,最重要的任务是让 MVC 在 MEF 插件中定位控制器并实例化它们,所以我需要一个控制器工厂。我在这里找到了一篇关于此的好文章:http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html(我就此联系了 Kenny,感谢他指出一些路由问题)。作者还将他的代码封装到了一个方便的 nuget 包 (MEF.MVC4) 中。无论如何,我发现了一个似乎与路由和命名空间有关的问题:当到达插件控制器的路由时,MEF 控制器工厂 GetControllerInstance 方法得到一个 null controllerType,最终导致 404。我想我可能通过阅读这些帖子找到了罪魁祸首:
http://blog.davebouwman.com/2011/12/08/asp-net-mvc3-and-404s-for-area-controllers/
和
Custom Controller Factory, Dependency Injection / Structuremap problems with ASP.NET MVC
我想(但我可能错了)问题在于路由约定和插件区域控制器命名空间:插件控制器的命名空间不在主机 web 的同一个“根”中。帖子中提出的解决方案只是向 Web 应用程序添加新路由,但这不适合将区域作为插件工作的解决方案,动态添加到主机应用程序。我的主机网络应用程序必须不知道插件,这当然应该是一个相当普遍的要求,但我没有找到明显的解决方案。
重现解决方案
您可以快速创建重现解决方案,以便按照以下步骤查看我的方法的详细信息,或从here下载:
1) 创建一个空白解决方案。
2) 在其中创建一个 MVC4 Web 应用程序 (HostWeb),更新所有 NuGet 预安装包并添加 Mef.MVC4、Autofac MVC 4 (RC) 和 Autofac.Mef。在我的实际应用中,我想使用 Autofac 在构造函数中注入控制器的依赖项。
3) 在 HostWeb 中创建一个 Plugins 文件夹,并在其中创建一个子文件夹 Temp。这将包括使用 Temp 子文件夹作为卷影副本容器的插件,以便从中加载 MEF 目录,而不是直接从插件中加载。这与一些启动代码一起应该让我更新插件而无需重新启动网络应用程序(否则会锁定 DLL)。启动代码是一个名为 PreApplicationInit 的类,您可以在 Infrastructure 文件夹中找到(对 http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust.aspx 稍作修改)。
4) 将部件区域添加到主机 Web 并将视图根文件夹中的 _ViewStart 文件复制到其中(并更改布局视图中的现有链接,以便将空白区域添加到路由值,以便它们不会被打破)。所有插件控制器都将被命名为名为 Parts 的区域。主机 Web 应用程序有一个没有控制器的区域,只是为插件内容文件准备文件夹结构和路由(视图,将从插件安装程序模块解压缩到适当的位置,而二进制文件将放置在插件中)。
5) 在 HostWeb 的 App_Start 中自定义 MefConfig 并添加处理 Autofac 的 IocConfig。然后将调用添加到全局 asax 中:MefConfig.RegisterMef() 和 IocConfig.RegisterDependencies()。
6) 在其中创建另一个 MVC4 Web 应用程序 (AlphaPlugin),更新所有 NuGet 预安装包并添加 Autofac MVC 4 (RC) 和 Autofac.Mef。我选择了一个 Web 应用程序模板(而不是类库),这样我就可以将所有 VS 工具用于 MVC,并最终直接在那里进行一些测试。
7) 将部件区域添加到主机 Web 并将视图根文件夹中的 _ViewStart 文件复制到其中。
8) 在 Parts 区域添加一个可导出的控制器。我的称为 AlphaController,只有一个名为 Hail 的操作方法,它在 ViewBag 中放入一个字符串并返回默认视图。
9) 回到主机,只需在主视图中添加一个指向插件控制器操作的链接,以测试它是否可以通过 MEF 访问。
现在,如果我构建所有并将 AlphaPlugin.dll 二进制文件复制到 HostWeb Plugins 文件夹中,我希望 MVC 可以通过 MEF 找到它,但随后会抛出未找到视图的错误,因为我尚未将任何内容文件复制到主机网络。相反,我得到以下信息:
Value cannot be null.
Parameter name: type
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentNullException: Value cannot be null.
Parameter name: type
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
Stack Trace:
[ArgumentNullException: Value cannot be null.
Parameter name: type]
System.ComponentModel.Composition.Hosting.ExportProvider.GetExportsCore(Type type, Type metadataViewType, String contractName, ImportCardinality cardinality) +263923
System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(Type type, Type metadataViewType, String contractName) +41
MEF.MVC4.MefControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +84
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +226
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +326
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +177
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +88
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +50
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
这是最相关的代码(您可以在 repro 解决方案中找到它):它处理 Plugins 文件夹内容,以便网络应用程序从副本加载它们:
[assembly: PreApplicationStartMethod(typeof(PreApplicationInit), "Initialize")]
static public class PreApplicationInit
{
///
/// The source plugin folder from which to shadow copy from.
///
/// This folder can contain sub folders to organize plugin types.
internal static DirectoryInfo PluginFolder { get; private set; }
///
/// The folder to shadow copy the plugin DLLs to use for running the app.
///
internal static DirectoryInfo ShadowCopyFolder { get; private set; }
static PreApplicationInit()
{
PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins"));
ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins/Temp"));
}
public static void Initialize()
{
if (!Directory.Exists(ShadowCopyFolder.FullName))
Directory.CreateDirectory(ShadowCopyFolder.FullName);
else
{
foreach (FileInfo fi in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories))
{
try
{
fi.Delete();
}
catch (Exception ex)
{
// TODO log
Debug.WriteLine(ex.ToString());
}
}
}
// shadow copy files
foreach (FileInfo fi in PluginFolder.GetFiles("*.dll"))
{
try
{
File.Copy(fi.FullName, Path.Combine(ShadowCopyFolder.FullName, fi.Name), true);
}
catch (Exception ex)
{
// TODO log
Debug.WriteLine(ex.ToString());
}
}
}
}
这是我的工厂,无论如何它都会得到一个空控制器类型,因此它的代码永远不会在第一行之外执行:
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _compositionContainer;
public MefControllerFactory(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
// https://stackoverflow.com/questions/719678/custom-controller-factory-dependency-injection-structuremap-problems-with-asp
if (controllerType == null) return base.GetControllerInstance(requestContext, null);
var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();
IController result;
if (export != null) result = export.Value as IController;
else
{
result = base.GetControllerInstance(requestContext, controllerType);
_compositionContainer.ComposeParts(result);
}
return result;
}
【问题讨论】:
-
伙计,阅读这篇文章需要很长时间。这里有问题吗?
-
抱歉,我想包含详细信息,因为网络上充斥着 MEF+MVC 示例,但它们经常遗漏特定点或引用旧版本。问题是:我想使用 MEF(+ 像 Autofac 这样的依赖注入工具)创建一个带有插件控制器的 MVC Web 应用程序,但是如果我创建我的基于 MEF 的自定义控制器工厂,它总是会得到一个空控制器类型,所以它失败了.这似乎是由于插件和主机中不同的命名空间,以及与此相关的 MVC 路由的性质。如何让我的控制器工厂在这种情况下工作?
-
你有没有得到任何地方?我刚刚开始走同样的路,您能提供的任何见解都会有所帮助。
-
很抱歉我没有这样做,也是因为我计划对我的系统进行重大转变,将我的 movig 部分使用 JS 构建并连接到服务器端的单页应用程序,从而公开一个 RESTful API通过 WebAPI。现在我的插件只提供 WebAPI 和 SPA(=带有 JS 的视图),这在 MVC 中更容易管理。然而,这符合我的要求,但不能解决发布的问题。我真的希望 MVC 为区域和成员资格等提供更多基础设施。
标签: asp.net-mvc-4 mef autofac asp.net-mvc-areas