Tôi đã làm việc trên một dự án có kiến trúc có thể cắm được tương tự như bạn đã mô tả và nó sử dụng các công nghệ tương tự ASP.NET MVC và MEF. Chúng tôi đã có một ứng dụng ASP.NET MVC lưu trữ xử lý xác thực, ủy quyền và tất cả các yêu cầu. Các plugin (mô-đun) của chúng tôi đã được sao chép vào một thư mục con của nó. Các plugin cũng là các ứng dụng ASP.NET MVC có các mô hình, bộ điều khiển, chế độ xem, tệp css và js riêng. Đây là các bước mà chúng tôi đã làm theo để làm cho nó hoạt động:
Thiết lập MEF
Chúng tôi đã tạo công cụ dựa trên MEF để khám phá tất cả các bộ phận có thể ghép khi khởi động ứng dụng và tạo danh mục các bộ phận có thể ghép được. Đây là tác vụ chỉ được thực hiện một lần khi khởi động ứng dụng. Công cụ cần phải khám phá tất cả các bộ phận có thể cắm được, trong trường hợp của chúng tôi, chúng nằm trong bin
thư mục của ứng dụng máy chủ hoặc trong Modules(Plugins)
thư mục.
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;
}
}
Đây là mã mẫu của lớp thực hiện khám phá tất cả các phần của MEF. Các Compose
phương pháp của lớp được gọi từ các Application_Start
phương pháp trong các Global.asax.cs
tập tin. Mã được giảm vì lợi ích của đơn giản.
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));
}
}
Giả định rằng tất cả các plugin được sao chép trong một thư mục con riêng biệt của Modules
thư mục nằm trong thư mục gốc của ứng dụng chủ. Mỗi thư mục con plugin chứa thư mục con Views
và DLL từ mỗi plugin. Trong Application_Start
phương thức trên cũng được khởi tạo nhà máy sản xuất bộ điều khiển tùy chỉnh và công cụ chế độ xem tùy chỉnh mà tôi sẽ xác định bên dưới.
Tạo nhà máy sản xuất bộ điều khiển đọc từ MEF
Đây là mã để xác định nhà máy sản xuất bộ điều khiển tùy chỉnh sẽ khám phá bộ điều khiển cần xử lý yêu cầu:
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();
}
}
}
Ngoài ra, mỗi bộ điều khiển phải được đánh dấu bằng Export
thuộc tính:
[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
public ActionResult Index()
{
return View();
}
}
Tham số đầu tiên của phương thức Export
khởi tạo thuộc tính phải là duy nhất vì nó chỉ định tên hợp đồng và xác định duy nhất từng bộ điều khiển. Các PartCreationPolicy
phải được thiết lập để NonShared vì bộ điều khiển không thể được tái sử dụng cho nhiều yêu cầu.
Tạo View Engine biết cách tìm các khung nhìn từ các plugin
Việc tạo công cụ chế độ xem tùy chỉnh là cần thiết vì công cụ chế độ xem theo quy ước chỉ tìm kiếm các chế độ xem trong Views
thư mục của ứng dụng lưu trữ. Vì các plugin được đặt trong Modules
thư mục riêng biệt , chúng tôi cần yêu cầu công cụ xem cũng xem ở đó.
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();
}
}
Giải quyết vấn đề với các chế độ xem được nhập mạnh trong plugin
Bằng cách chỉ sử dụng mã trên, chúng tôi không thể sử dụng các chế độ xem được nhập mạnh trong các plugin (mô-đun) của chúng tôi, vì các mô hình tồn tại bên ngoài bin
thư mục. Để giải quyết vấn đề này, hãy làm theo liên kết sau .