2013-09-02 11 views
6

Z biegiem czasu kontrolery rozwijają wiele zależności, a tworzenie instancji kontrolera staje się zbyt kosztowne dla każdego żądania (szczególnie z DI). Czy istnieje jakieś rozwiązanie, które sprawi, że kontroler będzie działał jako singleton?Jak uczynić kontroler jedną instancję na aplikację w ASP.NET MVC?

+0

Możesz [utworzyć własną fabrykę kontrolerów] (http://www.dotnetcurry.com/ShowArticle.aspx?ID=878) łatwo ... ale czy pytasz o możliwe wady * używania * pojedynczych kontrolerów instancji ? – bzlm

+1

Udało mi się już utworzyć pojedynczą instancję kontrolerów dla każdej aplikacji, ale to nie działa: "..Jeżeli używana jest fabryka kontrolerów niestandardowych, upewnij się, że tworzy ona nowe wystąpienie kontrolera dla każdego żądanie.' – 6opuc

+5

Chociaż nic nie jest niemożliwe, sugeruję, że próba wdrożenia tego spowoduje więcej problemów niż odpowiedzi. Na przykład właściwość HttpContext instancji kontrolera jest oczywiście unikalna dla tego żądania. Najpierw prawdopodobnie wiele problemów, które możesz napotkać. – Mark

Odpowiedz

16

Tworzenie instancji kontrolerów to dość szybka i prosta operacja. To, co staje się zbyt drogie, tworzy zależności dla każdego żądania. Tak naprawdę potrzebujemy wielu kontrolerów, które dzielą te same instancje zależności.

E.g. masz następujące kontroler

public class SalesController : Controller 
{ 
    private IProductRepository productRepository; 
    private IOrderRepository orderRepository; 

    public SalesController(IProductRepository productRepository, 
          IOrderRepository orderRepository) 
    { 
     this.productRepository = productRepository; 
     this.orderRepository = orderRepository; 
    } 

    // ...  
} 

Należy skonfigurować zależność ram wtrysku do korzystania z tych samych wystąpień repozytoria dla wszystkich aplikacji (należy pamiętać, możesz mieć problemy z synchronizacją). Teraz tworzenie zależności nie jest już drogie. Wszystkie zależności są tworzone tylko raz i ponownie wykorzystywane dla wszystkich żądań.

Jeśli masz wiele zależności i martwisz się kosztami uzyskania odniesienia do instancji każdej zależności i dostarczeniem tych odwołań do instancji kontrolera (co nie wydaje mi się być bardzo drogie), możesz pogrupować zależności (coś takiego jak wprowadzenie refaktoryzacji obiektu parametrycznego):

public class SalesController : Controller 
{ 
    private ISalesService salesService; 

    public SalesController(ISalesService salesService) 
    { 
     this.salesService = salesService; 
    } 

    // ...  
} 

public class SalesService : ISalesService 
{ 
    private IProductRepository productRepository; 
    private IOrderRepository orderRepository; 

    public SalesService(IProductRepository productRepository, 
          IOrderRepository orderRepository) 
    { 
     this.productRepository = productRepository; 
     this.orderRepository = orderRepository; 
    } 

    // ...  
} 

Teraz masz pojedynczą zależność, która zostanie szybko wstrzyknięta. Jeśli skonfigurujesz strukturę zależności wtrysku w celu użycia pojedynczej usługi SalesService, wówczas wszystkie kontrolery będą ponownie używać tego samego wystąpienia usługi. Tworzenie kontrolerów i zapewnianie zależności będzie bardzo szybkie.

+1

Bardzo dobry i szczegółowy aswer, dzięki!To moja ostatnia deska ratunku, jeśli nie ma rozwiązania [hack], aby kontrolerów singletons ... Zbyt dużo refaktoryzacji i xml-magic (używam wiosny) – 6opuc

+0

na pewno problemy z synchronizacją będą zbyt wysokie cena do zapłaty – user195166

1

Więc pierwsza odpowiedź do pierwotnego pytania:

public void ConfigureServices(IServiceCollection services) { 
    // put other services bindings here 

    // bind all Controller classes as singletons 
    services.AddSingleton<HomeController, HomeController>(); 

    // tell framework to obtain Controller instances from ServiceProvider. 
    services.AddMvc().AddControllersAsServices(); 
} 

Jak podano w pierwotnym pytaniu, czy kontrolery mają duże drzewa z zależnościami, na które składają się głównie z życzeniem lunetą lub Transient zależności następnie tworząc je osobno dla każdego żądania mogą mieć trochę śladu na skalowalności twojej aplikacji (w Javie na przykład instancje Servleta są domyślnie z tego powodu domyślnie). O ile zwykle procesor i czas rzeczywisty potrzebny do utworzenia nawet dużego drzewa zależności są pomijalne (chyba że masz ciężkie obliczenia lub komunikację sieciową w konstruktorach swoich komponentów, co prawie nigdy nie jest dobrym pomysłem na przejściowe lub żądane komponenty), wykorzystanie pamięci ślad jest czymś, z czym trzeba się liczyć. W przypadku typowych aplikacji sieciowych DB-Web głównym czynnikiem ograniczającym liczbę współbieżnych żądań może obsłużyć jeden komputer-węzeł. Jeśli każde żądanie ma oddzielną kopię dużego drzewa zależności, razem mogą one zużywać znaczną ilość pamięci (inną rzeczą do oglądania jest początkowy rozmiar stosu dla nowego wątku, nawiasem mówiąc).

Przyjęta odpowiedź 1220560 rozwiązuje również problem, ale uważam to za brzydkie włamanie i ma pewne wady: musisz stworzyć tę sztuczną usługę singleton, która będzie używana przez Kontrolerów jako lokalizator usług lub proxy dla innych usług. Jeśli masz tylko jeden taki obiekt singletonowy dla wszystkich kontrolerów, to skutecznie ukrywasz rzeczywiste zależności kontrolera: na przykład, jeśli ktoś chce napisać test jednostkowy dla kontrolera, musi dokładnie przeanalizować jego implementację, aby zobaczyć, które zależności faktycznie są używa, aby wiedzieć, jakie drwiny/podróbki musi dostarczyć w konfiguracji testowej. Jeśli później zmienisz kontroler, a w wyniku zmiany podzestaw usług, których kontroler używa również zmian, bardzo łatwo jest zapomnieć o zaktualizowaniu ustawień testu. Może to czasami prowadzić do błędów, które trudno jest śledzić. W przeciwieństwie do tego, jeśli twoje zależności są jawnie zadeklarowane jako parametry konstruktora, natychmiast dostaniesz błąd kompilatora w konfiguracji testowej.Inną rzeczą, którą możesz zrobić, jest posiadanie oddzielnego, takiego singletonowego proxy/service locator dla każdego kontrolera, ale w zasadzie jest to dużo kłopotów.

Bez względu na to, czy korzystasz z rozwiązania zaproponowanego przeze mnie, czy też z odpowiedzi # 1220560, musisz być ostrożny przy wstrzykiwaniu wniosku Zależności Scoped na pojedyncze obiekty zgodnie z opisem w https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection#registering-your-own-services na samym końcu "rejestracji własnych usług" " Sekcja. Możesz znaleźć możliwe rozwiązania tego problemu tutaj: how to use scoped dependency in a singleton in C#/ASP Kolejną rzeczą, na którą warto zwrócić uwagę, jest problem współbieżności: pojedyncze obiekty mogą być dostępne jednocześnie przez kilka wątków obsługujących różne jednoczesne żądania, więc upewnij się, że dodałeś odpowiednią synchronizację do dowolnych zasobów, które nie są wątkowane Twój singleton używa.

edit:

Właśnie zrealizowane oryginalne pytanie była o ASP.NET i ta odpowiedź jest dla ASP.NET Rdzenia, więc prawdopodobnie nie będzie działać dla "non-core".

Powiązane problemy