Architektura Plug-in dla ASP.NET MVC

Spędziłem trochę czasu przeglądając artykuł Phila Haacka o grupowaniu kontrolerów bardzo ciekawych rzeczach.

W tej chwili staram się dowiedzieć, czy byłoby możliwe wykorzystanie tych samych pomysłów do stworzenia architektury plug-in/modular dla projektu, nad którym pracuję.

Więc moje pytanie brzmi: czy jest możliwe, aby obszary w artykule Phila podzielić na wiele projektów?

Widzę, że spacje nazw same się ułożą, ale martwię się o widokach kończących się we właściwym miejscu. Czy jest to coś, co można rozwiązać za pomocą reguł budowania?

Zakładając, że powyższe jest możliwe przy wielu projektach w jednym rozwiązaniu, czy ktoś ma jakieś pomysły na najlepszy sposób, aby było to możliwe przy oddzielnym rozwiązaniu i kodowaniu do predefiniowanego zestawu interfejsów? Przejście z obszaru do wtyczki.

Mam pewne doświadczenia z architekturą plug-in, ale nie masy więc wszelkie wskazówki w tym zakresie byłyby przydatne.

Author: ThinkingStiff, 2008-12-04

6 answers

Zrobiłem proof of concept kilka tygodni temu, gdzie umieściłem kompletny stos komponentów: klasę modelu, klasę kontrolera i powiązane z nimi widoki w DLL, dodałem/poprawiłem jeden z przykładów klas VirtualPathProvider, które pobierają widoki, aby odpowiednio zaadresować te w DLL.

W końcu po prostu wrzuciłem DLL do odpowiednio skonfigurowanej aplikacji MVC i działało to tak, jakby od początku było częścią aplikacji MVC. Trochę przesunąłem. dalej i działało z 5 tych małych wtyczek mini-MVC po prostu dobrze. Oczywiście, musisz obserwować swoje referencje i zależności konfiguracji podczas tasowania go dookoła, ale to zadziałało.

Ćwiczenie miało na celu funkcjonalność wtyczki dla platformy opartej na MVC, którą buduję dla klienta. Istnieje podstawowy zestaw kontrolerów i widoków, które są rozszerzane o więcej opcjonalnych w każdej instancji witryny. Będziemy tworzyć te opcjonalne bity w tych modułowych wtyczkach DLL. Więc daleko tak dobrze.

Napisałem przegląd mojego prototypu i przykładowe rozwiązanie dla ASP.NET wtyczki MVC na mojej stronie.

EDIT: od 4 lat robię sporo ASP.NET aplikacje MVC z wtyczkami i nie używają już metody opisanej powyżej. W tym momencie uruchamiam wszystkie moje wtyczki przez MEF i w ogóle nie wkładam kontrolerów do wtyczek. Raczej robię ogólne kontrolery, które wykorzystują informacje o routingu, aby wybrać wtyczki MEF i przekazać pracę wtyczce itp. Pomyślałem, że dodam, skoro ta odpowiedź jest trafna.

 51
Author: J Wynia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:25:03

Pracuję nad frameworkiem rozszerzalności do wykorzystania na ASP.NET MVC. Mój Framework rozszerzalności jest oparty na słynnym kontenerze IOC: Structuremap .

Przypadek użycia, który staram się spełnić jest prosty: stworzyć aplikację, która powinna mieć kilka podstawowych funkcji, które można rozszerzyć dla każdego klienta (=multi-tenancy). Powinna istnieć tylko jedna instancja aplikacji hostowanej, ale ta instancja może być dostosowana dla każdego klienta bez wprowadzania żadnych zmian w Strona główna.

Zainspirował mnie artykuł o Multi tenacy napisany przez Ayende Rahien: http://ayende.com/Blog/archive/2008/08/16/Multi-Tenancy--Approaches-and-Applicability.aspx Innym źródłem inspiracji była książka Erica Evansa na temat Domain Driven Design. Mój framework rozszerzalności opiera się na wzorze repozytorium i koncepcji agregatów korzeniowych. Aby móc korzystać z frameworka, aplikacja hostingowa powinna być zbudowana wokół repozytoriów i obiektów domeny. Na Kontrolery, repozytoria lub obiekty domeny są bindowane w czasie wykonywania przez ExtensionFactory.

Wtyczka jest po prostu asselmbly, który zawiera kontrolery, repozytoria lub obiekty domeny, które przestrzegają określonej konwencji nazewnictwa. Konwencja nazewnictwa jest prosta, każda klasa powinna być poprzedzona przez customerID, np.: AdventureworksHomeController.

Aby rozszerzyć aplikację należy skopiować plug-in assembly w folderze extension aplikacji. Gdy użytkownik zażąda strony w folderze głównym klienta np.: http://multitenant-site.com/[customerID] / [controller] / [action] framework sprawdza, czy istnieje wtyczka dla tego konkretnego klienta i tworzy instancje niestandardowych klas wtyczek, w przeciwnym razie ładuje domyślny raz. Klasami niestandardowymi mogą być Kontrolery-repozytoria lub obiekty domeny. Takie podejście umożliwia rozszerzenie aplikacji na wszystkich poziomach, od bazy danych po interfejs użytkownika, poprzez model domeny, repozytoria.

Kiedy chcesz rozszerzenie niektórych istniejących funkcji tworzysz wtyczkę złożoną, która zawiera podklasy podstawowej aplikacji. Kiedy musisz stworzyć zupełnie nowe funkcje, dodajesz nowe kontrolery wewnątrz wtyczki. Kontrolery te zostaną załadowane przez framework MVC, gdy wymagany jest odpowiedni adres url. Jeśli chcesz rozszerzyć interfejs użytkownika, możesz utworzyć nowy widok wewnątrz folderu rozszerzenia i odwołać się do niego za pomocą nowego lub podklasowanego kontrolera .aby zmodyfikować istniejące zachowanie, możesz utworzyć nowy repozytoria lub obiekty domeny lub podklasy wychodzące z nich. Zadaniem frameworka jest określenie, który kontroler / repozytorium / obiekt domeny powinien zostać załadowany dla konkretnego klienta.
Radzę rzucić okiem na structuremap (http://structuremap.sourceforge.net/Default.htm ), a szczególnie w funkcji rejestru DSL http://structuremap.sourceforge.net/RegistryDSL.htm .

Jest to kod, którego używam przy starcie aplikacji do rejestracji wszystkich plug-in controllers / repozytoria or domain objects:

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", ""));
            });
        }

Używam również rozszerzenia dziedziczącego z systemu.Www.MVC. DefaultControllerFactory. Ta fabryka jest odpowiedzialna za załadowanie obiektów rozszerzenia(kontrolerów/rejestrów lub obiektów domeny). Możesz podłączyć własne fabryki, rejestrując je przy starcie w globalnym.plik asax:

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

Ten framework jako w pełni funkcjonalny Przykładowy obiekt można znaleźć na: http://code.google.com/p/multimvc/

 14
Author: Geo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-03 22:19:43

Więc trochę pobawiłem się przykładem z J Wynia powyżej. Wielkie dzięki za to btw.

Zmieniłem rzeczy tak, że rozszerzenie VirtualPathProvider używało statycznego konstruktora do tworzenia listy wszystkich dostępnych zasobów kończących się na.aspx w różnych bibliotekach dll w systemie. To żmudne, ale robimy to tylko raz.

To chyba totalne nadużycie sposobu, w jaki mają być używane również pliki wirtualne; -)

Kończysz z a:

Private static IDictionary resourceVirtualFile;

Ciąg znaków jest ścieżkami wirtualnymi.

Poniższy kod przyjmuje pewne założenia dotyczące przestrzeni nazw .pliki aspx, ale będzie działać w prostych przypadkach. Fajną rzeczą jest to, że nie musisz tworzyć skomplikowanych ścieżek widoków, które są tworzone z nazwy zasobu.

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

Możesz zrobić coś takiego w rozszerzonym 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);
    }
 4
Author: Simon Farrow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:45:36

Myślę, że można zostawić swoje poglądy w projektach wtyczek.

To mój pomysł: potrzebujesz ViewEngine, który wywołałby wtyczkę (prawdopodobnie przez interfejs) i zażądał widoku (IView). Wtyczka utworzy wtedy instancję widoku nie poprzez swój adres url (jak robi to zwykły ViewEngine - / Views/Shared / View.asp), ale poprzez nazwę widoku) np. poprzez reflection lub di / IoC container).

Powrót widoku w wtyczce może mi się nawet (prosty przykład poniżej):

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

...to był tylko pomysł, ale mam nadzieję, że może się udać lub po prostu być dobrą inspiracją.

 3
Author: gius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-04 12:14:04

Ten post może się trochę spóźnić, ale bawiłem się z ASP.NET MVC2 i wymyślili prototyp używając funkcji "obszary".

Oto link dla wszystkich zainteresowanych: http://www.veebsbraindump.com/2010/06/asp-net-mvc2-plugins-using-areas/

 3
Author: Veebs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 05:23:11

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

Świetne rozwiązanie-użyłem podejścia J Wynia i dostałem go do renderowania widoku z osobnego montażu. Jednak takie podejście wydaje się, że renderuje tylko widok. Kontrolery wewnątrz wtyczki nie wydają się być obsługiwane, prawda? Na przykład, jeśli Widok z wtyczki zrobił post, kontroler widoków wewnątrz wtyczki będzie , a nie będzie nazywany. Zamiast tego zostanie przekierowany do kontrolera wewnątrz root MVC application . Czy dobrze to rozumiem, czy istnieje obejście tego problemu?

 0
Author: tbehunin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-23 21:15:44