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


80

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에서 앱을 개발하는 것도 좋을 것입니다.


1
MVC5에서 작동하도록이 설정을 모두 갖추고 있습니까? MVC 5에서도 같은 것을 설정하려고합니다. 도움에 감사드립니다
Junior

1
다음은 EF와 해협 ASP.net을 모두 구현하는 버전이있는 경쟁 예제입니다. codeproject.com/Articles/1109475/…
BrownPony 2011

더 많은 애플리케이션에서 MEF를 사용하지 않는 이유는 무엇입니까? 모두가 이것에 대해 스스로 굴리는 것 같습니다.
johnny

답변:


105

설명하신 것과 유사한 플러그 형 아키텍처가있는 프로젝트에서 작업했으며 동일한 기술인 ASP.NET MVC 및 MEF를 사용했습니다. 인증, 권한 부여 및 모든 요청을 처리하는 호스트 ASP.NET MVC 응용 프로그램이 있습니다. 플러그인 (모듈)은 하위 폴더에 복사되었습니다. 플러그인은 자체 모델, 컨트롤러, 뷰, css 및 js 파일이있는 ASP.NET MVC 응용 프로그램이기도합니다. 다음은 작동하도록하기 위해 수행 한 단계입니다.

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

Export특성 생성자 의 첫 번째 매개 변수 는 계약 이름을 지정하고 각 컨트롤러를 고유하게 식별하므로 고유해야합니다. 는 PartCreationPolicy컨트롤러가 여러 요청에 재사용 할 수 없기 때문에 공유 안로 설정해야합니다.

플러그인에서보기를 찾는 것을 알고있는보기 엔진 만들기

관례 상보기 엔진은 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폴더 외부에 존재하기 때문에 플러그인 (모듈)에서 강력한 형식의 뷰를 사용할 수 없었습니다 . 이 문제를 해결하려면 다음 링크를 따르십시오 .


1
각 개별 모듈에 대한 사용자 지정 경로는 어떻습니까? 나는 각 모듈이 routetable과 global asax의 ref를 가져와야한다고 생각합니다. route 인터페이스는 모듈 폴더와 코어 모두에서 보일 것입니다.
sharif y

3
각 플러그인에 대해 별도의 영역을 정의하여이를 해결했습니다. 각 플러그인에서 AreaRegistration에서 상속하는 클래스를 만들고 RegisterArea 메서드를 재정 의하여 플러그인에서 사용하려는 경로를 정의 할 수있었습니다.
Ilija Dimov 2014 년

10
이 솔루션에 대한 샘플 프로젝트가 있습니까?
cpoDesign 2014 년

2
cpoDesign에 동의합니다. 샘플 프로젝트는 좋을 것입니다
chris vietor 2014-08-08

2
나는 또한 GitHub의 샘플 프로젝트를 다운로드하는 것이 좋을 것이라는 데 동의합니다 :)
Kbdavis07 2014 년


3

플러그인 아키텍처를 구현하는 프로젝트가 있습니다. 다음 중 하나를 사용하거나 소스 코드를 살펴보고 이러한 작업을 수행하는 방법을 확인할 수 있습니다.

또한 404 on Controllers in External Assemblies 는 흥미로운 접근 방식을 취하고 있습니다. 나는 그저 질문을 읽음으로써 많은 것을 배웠다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.