your programing

MVC 4 또는 5를 사용하는 MEF-플러그 형 아키텍처 (2014)

lovepro 2020. 10. 10. 10:44
반응형

MVC 4 또는 5를 사용하는 MEF-플러그 형 아키텍처 (2014)


Orchard CMS와 같은 플러그 형 아키텍처로 MVC4 / MVC5 애플리케이션을 빌드하려고합니다. 그래서 시작 프로젝트가 될 MVC 응용 프로그램이 있고 인증, 탐색 등을 처리합니다. 그런 다음 asp.net 클래스 라이브러리로 별도로 빌드되거나 mvc 프로젝트를 제거하고 컨트롤러, 뷰, 데이터 저장소 등을 포함하는 여러 모듈이 있습니다.

나는 하루 종일 웹에서 튜토리얼을 진행하고 샘플을 다운로드하는 데 보냈고 Kenny가 http://kennytordeur.blogspot.in/2012/08/mef-in-aspnet-mvc-4-and -webapi.html

해당 DLL에 대한 참조를 추가하면 모듈 (별도의 DLL)에서 컨트롤러를 가져올 수 있습니다. 그러나 MEF를 사용하는 이유는 런타임에 모듈을 추가 할 수 있기 때문입니다. 보기와 함께 DLL을 시작 프로젝트의 ~ / Modules // 디렉터리에 복사하고 싶습니다 (이 작업을 수행했습니다). MEF가이를 선택합니다. MEF가 이러한 라이브러리를로드하도록 고군분투합니다.

이 답변 ASP.NET MVC 4.0 컨트롤러 및 MEF 에서 설명한대로 MefContrib도 있습니다. 이 두 가지를 함께 가져 오는 방법은 무엇입니까? 다음으로 시도 할 것입니다. 그러나 MEF가 MVC에서 기본적으로 작동하지 않는다는 것이 놀랍습니다.

MefContrib의 유무에 관계없이 유사한 아키텍처가 작동하는 사람이 있습니까? 처음에는 Orchard CMS를 제거하고 프레임 워크로 사용할 생각도했지만 너무 복잡합니다. WebAPI2를 활용하기 위해 MVC5에서 앱을 개발하는 것도 좋을 것입니다.


설명하신 것과 유사한 플러그 형 아키텍처를 가진 프로젝트에서 작업했으며 동일한 기술 ASP.NET MVCMEF. 인증, 권한 부여 및 모든 요청을 처리하는 호스트 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
{
    //
    // GET: /Plugin1/
    public ActionResult Index()
    {
        return View();
    }
}

The first parameter of the Export attribute constructor must be unique because it specifies the contract name and uniquely identifies each controller. The PartCreationPolicy must be set to NonShared because controllers cannot be reused for multiple requests.

Creating View Engine that knows to find the views from the plugins

Creation of custom view engine is needed because the view engine by convention looks for views only in the Views folder of the host application. Since the plugins are located in separate Modules folder, we need to tell to the view engine to look there also.

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();
    }
}

Solve the problem with strongly typed views in the plugins

By using only the above code, we couldn't use strongly typed views in our plugins(modules), because models existed outside of the bin folder. To solve this problem follow the following link.


Just be aware that MEF's container has a "nice feature" that keeps references to any IDisposable object it creates, and will lead to huge memory leak. Allegedly the memory leak can be addressed with this nuget - http://nuget.org/packages/NCode.Composition.DisposableParts.Signed


There are projects out there that implement a plugin architecture. You might want to use one of these or to have a look at their source code to see how they accomplish these things:

Also, 404 on Controllers in External Assemblies is taking an interesting approach. I learned a lot by just reading the question.

참고URL : https://stackoverflow.com/questions/21017036/mef-with-mvc-4-or-5-pluggable-architecture-2014

반응형