我曾从事过一个项目,该项目具有与您所描述的相似的可插拔体系结构,并且使用了相同的技术ASP.NET MVC和MEF。我们有一个宿主ASP.NET MVC应用程序,它处理身份验证,授权和所有请求。我们的插件(模块)已复制到其子文件夹。插件也是ASP.NET MVC应用程序,具有自己的模型,控制器,视图,css和js文件。这些是我们使其工作所遵循的步骤:
设置MEF
我们基于MEF创建了引擎,该引擎在应用程序启动时发现了所有可组合零件,并创建了可组合零件的目录。这是在应用程序启动时仅执行一次的任务。引擎需要发现所有可插拔部件,在我们的案例中,它们位于bin
主机应用程序的Modules(Plugins)
文件夹或文件夹中。
public class Bootstrapper
{
private static CompositionContainer CompositionContainer;
private static bool IsLoaded = false;
public static void Compose(List<string> pluginFolders)
{
if (IsLoaded) return;
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")));
foreach (var plugin in pluginFolders)
{
var directoryCatalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules", plugin));
catalog.Catalogs.Add(directoryCatalog);
}
CompositionContainer = new CompositionContainer(catalog);
CompositionContainer.ComposeParts();
IsLoaded = true;
}
public static T GetInstance<T>(string contractName = null)
{
var type = default(T);
if (CompositionContainer == null) return type;
if (!string.IsNullOrWhiteSpace(contractName))
type = CompositionContainer.GetExportedValue<T>(contractName);
else
type = CompositionContainer.GetExportedValue<T>();
return type;
}
}
这是执行所有MEF部分发现的类的示例代码。在Compose
类的方法是从所谓Application_Start
的方法中的Global.asax.cs
文件。为了简单起见,减少了代码。
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var pluginFolders = new List<string>();
var plugins = Directory.GetDirectories(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Modules")).ToList();
plugins.ForEach(s =>
{
var di = new DirectoryInfo(s);
pluginFolders.Add(di.Name);
});
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
Bootstrapper.Compose(pluginFolders);
ControllerBuilder.Current.SetControllerFactory(new CustomControllerFactory());
ViewEngines.Engines.Add(new CustomViewEngine(pluginFolders));
}
}
假定所有插件都复制到Modules
位于主机应用程序根目录中的文件夹的单独子文件夹中。每个插件子文件夹包含每个子插件的子Views
文件夹和DLL。在上述Application_Start
方法中,还将初始化我将在下面定义的自定义控制器工厂和自定义视图引擎。
创建从MEF读取的控制器工厂
以下是用于定义定制控制器工厂的代码,该代码将发现需要处理请求的控制器:
public class CustomControllerFactory : IControllerFactory
{
private readonly DefaultControllerFactory _defaultControllerFactory;
public CustomControllerFactory()
{
_defaultControllerFactory = new DefaultControllerFactory();
}
public IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = Bootstrapper.GetInstance<IController>(controllerName);
if (controller == null)
throw new Exception("Controller not found!");
return controller;
}
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Default;
}
public void ReleaseController(IController controller)
{
var disposableController = controller as IDisposable;
if (disposableController != null)
{
disposableController.Dispose();
}
}
}
此外,每个控制器都必须标记以下Export
属性:
[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
public ActionResult Index()
{
return View();
}
}
Export
属性构造函数的第一个参数必须唯一,因为它指定合同名称并唯一标识每个控制器。在PartCreationPolicy
必须设置为无共享,因为控制器不能为多个请求重复使用。
创建可以从插件中查找视图的View Engine
需要创建自定义视图引擎,因为按照惯例,视图引擎仅在Views
主机应用程序的文件夹中查找视图。由于插件位于单独的Modules
文件夹中,因此我们需要告诉视图引擎也要在该文件夹中查找。
public class CustomViewEngine : RazorViewEngine
{
private List<string> _plugins = new List<string>();
public CustomViewEngine(List<string> pluginFolders)
{
_plugins = pluginFolders;
ViewLocationFormats = GetViewLocations();
MasterLocationFormats = GetMasterLocations();
PartialViewLocationFormats = GetViewLocations();
}
public string[] GetViewLocations()
{
var views = new List<string>();
views.Add("~/Views/{1}/{0}.cshtml");
_plugins.ForEach(plugin =>
views.Add("~/Modules/" + plugin + "/Views/{1}/{0}.cshtml")
);
return views.ToArray();
}
public string[] GetMasterLocations()
{
var masterPages = new List<string>();
masterPages.Add("~/Views/Shared/{0}.cshtml");
_plugins.ForEach(plugin =>
masterPages.Add("~/Modules/" + plugin + "/Views/Shared/{0}.cshtml")
);
return masterPages.ToArray();
}
}
使用插件中的强类型视图解决问题
通过仅使用上述代码,我们无法在插件(模块)中使用强类型视图,因为模型存在于bin
文件夹之外。要解决此问题,请点击以下链接。