Próbuję zbudować aplikację MVC4 / MVC5 z wtykową architekturą, taką jak Orchard CMS. Mam więc aplikację MVC, która będzie projektem startowym i zajmie się uwierzytelnianiem, nawigacją itp. Następnie będzie wiele modułów zbudowanych osobno jako biblioteki klas asp.net lub okrojone projekty mvc i posiadające kontrolery, widoki, repozytoria danych itp.
Spędziłem cały dzień przeglądając samouczki w sieci i pobierając próbki itp. I stwierdziłem, że Kenny ma najlepszy przykład w okolicy - http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html
Jestem w stanie zaimportować kontrolery z modułów (oddzielne biblioteki DLL), jeśli dodam odniesienie do tych bibliotek DLL. Ale powodem używania MEF jest możliwość dodawania modułów w czasie wykonywania. Chcę, aby biblioteki DLL wraz z widokami zostały skopiowane do katalogu ~ / Modules // w projekcie startowym (udało mi się to zrobić), a MEF po prostu je odebrał. Próbując zmusić MEF do załadowania tych bibliotek.
Istnieje również MefContrib, jak wyjaśniono w tej odpowiedzi Kontrolery ASP.NET MVC 4.0 i MEF, jak połączyć te dwa elementy? co jest następną rzeczą, której spróbuję. Ale jestem zaskoczony, że MEF nie działa po wyjęciu z pudełka z MVC.
Czy ktoś ma podobną architekturę działającą (z MefContrib lub bez)? Początkowo myślałem nawet o usunięciu Orchard CMS i wykorzystaniu go jako frameworka, ale jest to zbyt skomplikowane. Przydałoby się również opracowanie aplikacji w MVC5, aby korzystać z WebAPI2.
Odpowiedzi:
Pracowałem nad projektem, który miał podobną architekturę wtykową, jak ta, którą opisałeś i wykorzystywał te same technologie ASP.NET MVC i MEF. Mieliśmy hostową aplikację ASP.NET MVC, która obsługiwała uwierzytelnianie, autoryzację i wszystkie żądania. Nasze wtyczki (moduły) zostały skopiowane do jego podfolderu. Wtyczki były również aplikacjami ASP.NET MVC, które miały własne modele, kontrolery, widoki, pliki css i js. Oto kroki, które wykonaliśmy, aby to zadziałało:
Konfigurowanie MEF
Stworzyliśmy silnik oparty na MEF, który wykrywa wszystkie komponenty komponowalne na starcie aplikacji i tworzy katalog tych części. Jest to zadanie wykonywane tylko raz podczas uruchamiania aplikacji. Silnik musi wykryć wszystkie podłączane części, które w naszym przypadku znajdowały się w
bin
folderze aplikacji hosta lub wModules(Plugins)
folderze.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; } }
To jest przykładowy kod klasy, która wykonuje odnajdywanie wszystkich części MEF.
Compose
Metoda klasy jest wywoływana zApplication_Start
metody wGlobal.asax.cs
pliku. Kod jest zredukowany ze względu na prostotę.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)); } }
Zakłada się, że wszystkie wtyczki są kopiowane do oddzielnego podfolderu
Modules
folderu znajdującego się w katalogu głównym aplikacji hosta. Każdy podfolder wtyczek zawiera podfolderViews
i bibliotekę DLL z każdej wtyczki. WApplication_Start
powyższej metodzie są również zainicjowane niestandardowa fabryka kontrolerów i niestandardowy silnik widoku, który zdefiniuję poniżej.Tworzę fabrykę kontrolerów, która czyta z MEF
Oto kod definiujący fabrykę niestandardowych kontrolerów, który wykryje kontroler, który musi obsłużyć żądanie:
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(); } } }
Dodatkowo każdy kontroler musi być oznaczony
Export
atrybutem:[Export("Plugin1", typeof(IController))] [PartCreationPolicy(CreationPolicy.NonShared)] public class Plugin1Controller : Controller { // // GET: /Plugin1/ public ActionResult Index() { return View(); } }
Pierwszy parametr
Export
konstruktora atrybutów musi być unikatowy, ponieważ określa nazwę kontraktu i jednoznacznie identyfikuje każdy kontroler.PartCreationPolicy
Musi być ustawiony na NonShared bo kontrolerzy nie mogą być ponownie wykorzystane dla wielu żądań.Tworzenie silnika widoku, który wie, jak znajdować widoki z wtyczek
Utworzenie niestandardowego mechanizmu widoku jest potrzebne, ponieważ zgodnie z konwencją silnik widoku szuka widoków tylko w
Views
folderze aplikacji hosta. Ponieważ wtyczki znajdują się w osobnymModules
folderze, musimy powiedzieć silnikowi widoku, aby również tam zajrzał.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(); } }
Rozwiąż problem z silnie wpisanymi widokami we wtyczkach
Używając tylko powyższego kodu, nie mogliśmy używać silnie wpisanych widoków w naszych wtyczkach (modułach), ponieważ modele istniały poza
bin
folderem. Aby rozwiązać ten problem, skorzystaj z następującego łącza .źródło
Należy tylko pamiętać, że kontener MEF ma „fajną funkcję”, która zachowuje odwołania do dowolnego obiektu IDisposable, który tworzy, i prowadzi do ogromnego wycieku pamięci. Podobno wyciek pamięci można rozwiązać za pomocą tego nuget - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed
źródło
Istnieją projekty, które implementują architekturę wtyczek. Możesz użyć jednego z nich lub rzucić okiem na ich kod źródłowy, aby zobaczyć, jak osiągają te rzeczy:
Również 404 na kontrolerach w zewnętrznych zespołach przyjmuje interesujące podejście. Wiele się nauczyłem, po prostu czytając pytanie.
źródło