Ich habe an einem Projekt gearbeitet, das eine ähnliche steckbare Architektur wie die von Ihnen beschriebene hatte und die gleichen Technologien wie ASP.NET MVC und MEF verwendete. Wir hatten eine ASP.NET MVC-Hostanwendung, die die Authentifizierung, Autorisierung und alle Anforderungen abwickelte. Unsere Plugins (Module) wurden in einen Unterordner kopiert. Die Plugins waren auch ASP.NET MVC-Anwendungen mit eigenen Modellen, Controllern, Ansichten, CSS- und JS-Dateien. Dies sind die Schritte, die wir befolgt haben, damit es funktioniert:
MEF einrichten
Wir haben eine auf MEF basierende Engine erstellt, die beim Start der Anwendung alle zusammensetzbaren Teile erkennt und einen Katalog der zusammensetzbaren Teile erstellt. Dies ist eine Aufgabe, die beim Start der Anwendung nur einmal ausgeführt wird. Die Engine muss alle steckbaren Teile erkennen, die sich in unserem Fall entweder im bin
Ordner der Host-Anwendung oder im Modules(Plugins)
Ordner befanden .
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;
}
}
Dies ist der Beispielcode der Klasse, die die Ermittlung aller MEF-Teile durchführt. Die Compose
Methode der Klasse wird von der Application_Start
Methode in der Global.asax.cs
Datei aufgerufen . Der Code wird der Einfachheit halber reduziert.
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));
}
}
Es wird davon ausgegangen, dass alle Plugins in einen separaten Unterordner des Ordners kopiert werden Modules
, der sich im Stammverzeichnis der Hostanwendung befindet. Jeder Plugin-Unterordner enthält den Views
Unterordner und die DLL von jedem Plugin. In der Application_Start
obigen Methode werden auch die benutzerdefinierte Controller-Factory und die benutzerdefinierte Ansichts-Engine initialisiert, die ich unten definieren werde.
Erstellen einer Controller-Factory, die von MEF liest
Hier ist der Code zum Definieren der benutzerdefinierten Controller-Factory, die den Controller erkennt, der die Anforderung verarbeiten muss:
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();
}
}
}
Zusätzlich muss jeder Controller mit folgenden Export
Attributen gekennzeichnet sein:
[Export("Plugin1", typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Plugin1Controller : Controller
{
public ActionResult Index()
{
return View();
}
}
Der erste Parameter des Export
Attributkonstruktors muss eindeutig sein, da er den Vertragsnamen angibt und jeden Controller eindeutig identifiziert. Das PartCreationPolicy
muss auf NonShared gesetzt sein, da Controller nicht für mehrere Anforderungen wiederverwendet werden können.
Erstellen einer View Engine, die die Ansichten der Plugins findet
Die Erstellung einer benutzerdefinierten Ansichts-Engine ist erforderlich, da die Ansichts-Engine gemäß Konvention nur im Views
Ordner der Host-Anwendung nach Ansichten sucht . Da sich die Plugins in einem separaten Modules
Ordner befinden, müssen wir der View Engine mitteilen, dass sie auch dort suchen soll.
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();
}
}
Lösen Sie das Problem mit stark typisierten Ansichten in den Plugins
Wenn wir nur den obigen Code verwenden, können wir in unseren Plugins (Modulen) keine stark typisierten Ansichten verwenden, da Modelle außerhalb des bin
Ordners vorhanden sind. Um dieses Problem zu lösen, folgen Sie dem folgenden Link .