developer tip

ASP.NET MVC 용 플러그인 아키텍처

copycodes 2020. 11. 2. 08:08
반응형

ASP.NET MVC 용 플러그인 아키텍처


Grouping Controllers 에 대한 Phil Haack의 기사를 보며 시간을 보냈 습니다.

현재 나는 작업중인 프로젝트를위한 플러그인 / 모듈 형 아키텍처를 만드는 데 동일한 아이디어를 사용할 수 있는지 알아 보려고 노력하고 있습니다.

그래서 내 질문은 : Phil의 기사에서 영역을 여러 프로젝트로 나눌 수 있습니까?

이름 공간이 스스로 작동한다는 것을 알 수 있지만 올바른 위치에서 끝나는 견해가 걱정됩니다. 빌드 규칙으로 분류 할 수있는 것이 있습니까?

단일 솔루션의 여러 프로젝트에서 위의 작업이 가능하다고 가정 할 때, 별도의 솔루션과 사전 정의 된 인터페이스 세트로 코딩하여이를 가능하게하는 가장 좋은 방법에 대한 아이디어가 있습니까? Area에서 플러그인으로 이동.

플러그인 아키텍처에 대한 경험이 있지만 대중은 아니므로이 영역에 대한 지침이 유용 할 것입니다.


몇 주 전에 개념 증명을 수행하여 모델 클래스, 컨트롤러 클래스 및 관련 뷰를 DLL로 구성하고 뷰를 검색하는 VirtualPathProvider 클래스 의 예제 중 하나를 추가 / 조정했습니다. DLL에있는 항목을 적절하게 처리합니다.

결국에는 DLL을 적절하게 구성된 MVC 앱에 놓았고 처음부터 MVC 앱의 일부였던 것처럼 작동했습니다. 나는 그것을 조금 더 밀고 5 개의 작은 미니 MVC 플러그인과 잘 작동했습니다. 분명히, 모든 것을 섞을 때 참조 및 구성 종속성을 관찰해야하지만 작동했습니다.

이 연습은 클라이언트를 위해 구축중인 MVC 기반 플랫폼의 플러그인 기능을 목표로했습니다. 사이트의 각 인스턴스에서 더 많은 선택적 요소에 의해 보강되는 핵심 컨트롤러 및보기 세트가 있습니다. 이러한 선택적 비트를 이러한 모듈 식 DLL 플러그인으로 만들 것입니다. 여태까지는 그런대로 잘됐다.

내 사이트에 프로토 타입 개요와 ASP.NET MVC 플러그인 용 샘플 솔루션을 작성했습니다 .

편집 : 4 년 후, 저는 플러그인을 사용하여 ASP.NET MVC 앱을 꽤 많이 해왔으며 위에서 설명한 방법을 더 이상 사용하지 않습니다. 이 시점에서 저는 MEF를 통해 모든 플러그인을 실행하고 컨트롤러를 플러그인에 전혀 넣지 않습니다. 오히려 라우팅 정보를 사용하여 MEF 플러그인을 선택하고 작업을 플러그인 등에 전달하는 일반 컨트롤러를 만듭니다.이 답변이 상당히 만족 스러우므로 추가 할 것이라고 생각했습니다.


실제로 ASP.NET MVC에서 사용할 확장 성 프레임 워크를 개발 중입니다. 내 확장 성 프레임 워크는 유명한 Ioc 컨테이너 인 Structuremap을 기반으로합니다.

내가 수행하려는 사용 사례는 간단합니다. 모든 고객 (= 멀티 테넌시)에 대해 확장 할 수있는 몇 가지 기본 기능이 있어야하는 애플리케이션을 만듭니다. 호스팅되는 애플리케이션의 인스턴스는 하나만 있어야하지만이 인스턴스는 핵심 웹 사이트를 변경하지 않고도 모든 고객에 맞게 조정할 수 있습니다.

Ayende Rahien이 작성한 멀티 테너시 기사에서 영감을 얻었습니다. http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx 또 다른 영감의 원천은 다음과 같습니다. Domain Driven Design에 대한 Eric Evans의 책. My Extensibility 프레임 워크는 저장소 패턴과 루트 집계 개념을 기반으로합니다. 프레임 워크를 사용할 수 있으려면 호스팅 응용 프로그램이 리포지토리 및 도메인 개체를 중심으로 구축되어야합니다. 컨트롤러, 저장소 또는 도메인 개체는 런타임에 ExtensionFactory에 의해 바인딩됩니다.

플러그인은 특정 명명 규칙을 준수하는 컨트롤러 또는 리포지토리 또는 도메인 개체를 포함하는 단순한 집합입니다. 명명 규칙은 간단합니다. 모든 클래스는 고객 ID (예 : AdventureworksHomeController)를 접두사로 지정해야합니다.

애플리케이션을 확장하려면 애플리케이션의 확장 폴더에 플러그인 어셈블리를 복사합니다. 사용자가 고객 루트 폴더 아래의 페이지를 요청하면 (예 : http://multitenant-site.com/[customerID]/[controller]/[action] 프레임 워크는 해당 특정 고객에 대한 플러그인이 있는지 확인하고 인스턴스화) 그렇지 않으면 사용자 정의 플러그인 클래스가 기본값을 한 번로드합니다. 사용자 정의 클래스는 컨트롤러 – 리포지토리 또는 도메인 개체 일 수 있습니다. 이 접근 방식을 사용하면 도메인 모델, 저장소를 통해 데이터베이스에서 UI에 이르는 모든 수준에서 애플리케이션을 확장 할 수 있습니다.

일부 기존 기능을 확장하려는 경우 핵심 애플리케이션의 하위 클래스를 포함하는 어셈블리에 플러그인을 작성합니다. 완전히 새로운 기능을 만들어야 할 때 플러그인 내부에 새로운 컨트롤러를 추가합니다. 이러한 컨트롤러는 해당 URL이 요청 될 때 MVC 프레임 워크에 의해로드됩니다. UI를 확장하려면 확장 폴더 내에 새보기를 만들고 새 컨트롤러 나 하위 클래스가 지정된 컨트롤러에서보기를 참조 할 수 있습니다. 기존 동작을 수정하려면 새 리포지토리 또는 도메인 개체 또는 기존 항목을 하위 클래스로 만들 수 있습니다. 프레임 워크 책임은 특정 고객을 위해로드해야하는 컨트롤러 / 리포지토리 / 도메인 개체를 결정하는 것입니다.
Structuremap ( http://structuremap.sourceforge.net/Default.htm)을 살펴 보는 것이 좋습니다 .) 그리고 특히 레지스트리에서 DSL 기능 http://structuremap.sourceforge.net/RegistryDSL.htm .

이것은 모든 플러그인 컨트롤러 / 저장소 또는 도메인 개체를 등록하기 위해 응용 프로그램을 시작할 때 사용하는 코드입니다.

protected void ScanControllersAndRepositoriesFromPath(string path)
        {
            this.Scan(o =>
            {
                o.AssembliesFromPath(path);
                o.AddAllTypesOf<SaasController>().NameBy(type => type.Name.Replace("Controller", ""));
                o.AddAllTypesOf<IRepository>().NameBy(type => type.Name.Replace("Repository", ""));
                o.AddAllTypesOf<IDomainFactory>().NameBy(type => type.Name.Replace("DomainFactory", ""));
            });
        }

또한 System.Web.MVC에서 상속 된 ExtensionFactory를 사용합니다. DefaultControllerFactory. 이 팩토리는 확장 개체 (컨트롤러 / 레지스트리 또는 도메인 개체)를로드합니다. 시작시 Global.asax 파일에 등록하여 자체 팩토리를 플러그인 할 수 있습니다.

protected void Application_Start()
        {
            ControllerBuilder.Current.SetControllerFactory(
                new ExtensionControllerFactory()
                );
        }

완벽하게 작동하는 샘플 사이트 인이 프레임 워크는 http://code.google.com/p/multimvc / 에서 찾을 수 있습니다.


그래서 저는 위의 J Wynia 의 예를 가지고 약간 놀았습니다 . 그 btw에 대한 많은 감사합니다.

VirtualPathProvider의 확장이 정적 생성자를 사용하여 시스템의 다양한 dll에서 .aspx로 끝나는 사용 가능한 모든 리소스 목록을 만들도록 변경했습니다. 힘들지만 우리는 한 번만하고 있습니다.

아마도 VirtualFiles도 사용되어야하는 방식에 대한 완전한 남용 일 것입니다 ;-)

당신은 끝납니다 :

개인 정적 IDictionary resourceVirtualFile;

문자열은 가상 경로입니다.

아래 코드는 .aspx 파일의 네임 스페이스에 대해 몇 가지 가정을하지만 간단한 경우에 작동합니다. 이 좋은 점은 리소스 이름에서 생성 된 복잡한 뷰 경로를 만들 필요가 없다는 것입니다.

class ResourceVirtualFile : VirtualFile
{
    string path;
    string assemblyName;
    string resourceName;

    public ResourceVirtualFile(
        string virtualPath,
        string AssemblyName,
        string ResourceName)
        : base(virtualPath)
    {
        path = VirtualPathUtility.ToAppRelative(virtualPath);
        assemblyName = AssemblyName;
        resourceName = ResourceName;
    }

    public override Stream Open()
    {
        assemblyName = Path.Combine(HttpRuntime.BinDirectory, assemblyName + ".dll");

        Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyName);
        if (assembly != null)
        {
            Stream resourceStream = assembly.GetManifestResourceStream(resourceName);
            if (resourceStream == null)
                throw new ArgumentException("Cannot find resource: " + resourceName);
            return resourceStream;
        }
        throw new ArgumentException("Cannot find assembly: " + assemblyName);
    }

    //todo: Neaten this up
    private static string CreateVirtualPath(string AssemblyName, string ResourceName)
    {
        string path = ResourceName.Substring(AssemblyName.Length);
        path = path.Replace(".aspx", "").Replace(".", "/");
        return string.Format("~{0}.aspx", path);
    }

    public static IDictionary<string, VirtualFile> FindAllResources()
    {
        Dictionary<string, VirtualFile> files = new Dictionary<string, VirtualFile>();

        //list all of the bin files
        string[] assemblyFilePaths = Directory.GetFiles(HttpRuntime.BinDirectory, "*.dll");
        foreach (string assemblyFilePath in assemblyFilePaths)
        {
            string assemblyName = Path.GetFileNameWithoutExtension(assemblyFilePath);
            Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilePath);  

            //go through each one and get all of the resources that end in aspx
            string[] resourceNames = assembly.GetManifestResourceNames();

            foreach (string resourceName in resourceNames)
            {
                if (resourceName.EndsWith(".aspx"))
                {
                    string virtualPath = CreateVirtualPath(assemblyName, resourceName);
                    files.Add(virtualPath, new ResourceVirtualFile(virtualPath, assemblyName, resourceName));
                }
            }
        }

        return files;
    }
}

그런 다음 확장 VirtualPathProvider에서 다음과 같이 할 수 있습니다.

    private bool IsExtended(string virtualPath)
    {
        String checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
        return resourceVirtualFile.ContainsKey(checkPath);
    }

    public override bool FileExists(string virtualPath)
    {
        return (IsExtended(virtualPath) || base.FileExists(virtualPath));
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        string withTilda = string.Format("~{0}", virtualPath);

        if (resourceVirtualFile.ContainsKey(withTilda))
            return resourceVirtualFile[withTilda];

        return base.GetFile(virtualPath);
    }

플러그인 프로젝트에 귀하의 견해를 남길 수 있다고 생각합니다.

That's my idea: you need a ViewEngine that would call the plugin (probably through an interface) and request the view (IView). The plugin would then instantiate the view not through its url (as an ordinary ViewEngine does - /Views/Shared/View.asp) but through its name of the view )for example via reflection or DI/IoC container).

The returning of the view in the plugin might me even hardcoded (simple example follows):

public IView GetView(string viewName)
{
    switch (viewName)
    {
        case "Namespace.View1":
            return new View1();
        case "Namespace.View2":
            return new View2();
        ...
    }
}

...this was just an idea but I hope it could work or just be a good inspiration.


This post may be a little late but I've been playing with ASP.NET MVC2 and have come up with a prototype using the "Areas" feature.

Here's the link for anyone who is interested: http://www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/


[posting as an answer because I can't comment]

Great solution - I used the approach by J Wynia and got it to render a view from a separate assembly. However, this approach appears to only render the view. Controllers within the plugin do not appear to be supported, correct? For instance, if a view from a plugin did a post back, that views' controller within the plugin will not be called. Instead, it will be routed to a controller within the root MVC application. Am I understanding this correctly or is there a workaround for this problem?

참고URL : https://stackoverflow.com/questions/340183/plug-in-architecture-for-asp-net-mvc

반응형