updated: read below in this post for a minimal solution
我有一些关于带插件的MVC4解决方案的新手问题 . 我google了一下,发现了一些好东西,但它并不完全符合我的要求,所以我在这里要求一些建议 .
似乎MVC中类似于widget的插件的最佳解决方案是可移植区域(在MvcContrib包中) . 我在这里找到了基本的指导:
http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/
以及一些有用的提示:
这篇文章中有更多内容:
How to create ASP.NET MVC area as a plugin DLL?
这很酷,但遗憾的是我的要求有点不同:
_999_遗憾的是,我需要一个动态添加和发现插件的系统,而不是便携式区域的情况,必须由主MVC站点项目引用 . 我想只是上传一些东西到网站,让它发现并使用新的组件,所以我将使用MEF .
-
幸运的是,我的插件不像小部件,它可能非常复杂和异构;相反,它们是必须遵循共同的共享模式的组件 . 将它们视为专业编辑器:对于每种数据类型,我将提供具有编辑功能的组件:新建,编辑,删除 . 所以我想到了插件控制器,它们实现了一个通用接口,并提供了New,Edit,Delete等操作 .
-
我必须使用MVC4,将来我必须添加本地化和移动定制 .
-
我必须避免复杂框架的依赖,并尽可能简化代码 .
所以,每当我想在这个网站上添加一个新的数据类型进行编辑时,我只想在其插件文件夹中删除一个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应用程序 . 无论如何,帖子中的样本指的是单组装解决方案,而我需要部署几个不同的插件 . 所以我稍微修改了代码,使用指向我的插件文件夹的MEF DirectoryCatalog(而不是AssemblyCatalog),然后创建了一个测试MVC解决方案,在类库中有一个插件 .
无论如何,当我尝试加载插件控制器时,框架使用null类型调用我的工厂GetControllerInstance,因此当然MEF无法进行组合 . 可能我错过了一些明显的东西,但我是MVC 4的新手,欢迎任何建议或有用(符合MVC4标准)的链接 . 谢谢!
这是基本代码:
public static class MefConfig
{
public static void RegisterMef()
{
CompositionContainer container = ConfigureContainer();
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver =
new MefDependencyResolver(container);
}
private static CompositionContainer ConfigureContainer()
{
//AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
DirectoryCatalog catalog = new DirectoryCatalog(
HostingEnvironment.MapPath("~/Plugins"));
CompositionContainer container = new CompositionContainer(catalog);
return container;
}
}
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
_container = container;
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
var export = _container.GetExports(serviceType, null, null).SingleOrDefault();
return (export != null ? export.Value : null);
}
public IEnumerable GetServices(Type serviceType)
{
var exports = _container.GetExports(serviceType, null, null);
List createdObjects = new List();
if (exports.Any())
createdObjects.AddRange(exports.Select(export => export.Value));
return createdObjects;
}
public void Dispose()
{
}
}
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)
{
if (controllerType == null) throw new ArgumentNullException("controllerType");
var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();
IController result;
if (null != export) result = export.Value as IController;
else
{
result = base.GetControllerInstance(requestContext, controllerType);
_compositionContainer.ComposeParts(result);
} //eelse
return result;
}
}
您可以从此处下载完整的测试解决方案:
http://www.filedropper.com/mvcplugins
编辑:第一个工作最小的解决方案
以下是我的发现,希望它们对于其他一些新手开始有用:我没有成功运行上面回复中引用的框架,我想必须要为VS2012和MVC4更新一些内容 . 无论如何,我看了一下代码并用Google搜索了一下:
1)首先,对我来说混淆的原因是具有相同名称的2个不同接口: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解析器:
public sealed class MefHttpDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefHttpDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
public IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{
}
}
然后MVC解析器非常相似,即使在这种情况下对虚拟样本严格不必要:
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
}
最后是控制器工厂:
[Export(typeof(IControllerFactory))]
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _container;
[ImportingConstructor]
public MefControllerFactory(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = _container
.GetExports()
.Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase))
.Select(c => c.Value)
.FirstOrDefault();
return controller ?? base.CreateController(requestContext, controllerName);
}
}
至于样本控制器,我将其创建为一个类库项目:
[Export(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata("Name", "Alpha")]
public sealed class AlphaController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello, this is the PLUGIN controller!";
return View();
}
}
在主项目(MVC站点)中,我有一个Plugins文件夹,我在其中复制此DLL,并在其文件夹中为此控制器的视图添加“标准”视图集 .
这是最简单的可能情况,可能还有更多要找出并改进,但我需要从简单开始 . 无论如何,任何建议都是受欢迎的 .
2 回答
我目前正在研究同样的问题 . 我找到了这个解决方案:
博文:http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/
源代码:https://bitbucket.org/darincreason/mvcapplication8.web
基本上它从指定的位置加载程序集,并在Web应用程序启动时使用一些名称模式:
AssemblyInfo.cs中:
PluginAreaBootstrapper.cs:
但我相信您可以创建一些可以动态加载程序集的目录观察器,因此您甚至不需要重新启动Web应用程序 .
在我看来,它满足您的1,2和4需求 . 它非常简单,不需要任何框架,具有最小配置,允许动态加载插件并与MVC 4一起使用 .
此解决方案将程序集插入Area目录,但我相信您可以非常轻松地将其调整为可以使用路由进行播放 .
您可以找到更完整的解决方案,here,以及更多背景here . 我们的要求也是一个DI所以this也会有所帮助,虽然不需要(第一个链接也为DI提供解决方案 . 祝你好运,这并不难 . 还要注意那些是针对MVC3但很容易转换/迁移到MVC 4