2013-09-02 5 views
6

I controller nel tempo sviluppano molte dipendenze e la creazione di un'istanza di controller diventa troppo costosa per ogni richiesta (in particolare con DI). C'è qualche soluzione per rendere single i controllori?Come rendere il controller una singola istanza per applicazione in ASP.NET MVC?

+0

È possibile [creare il proprio factory controller] (http://www.dotnetcurry.com/ShowArticle.aspx?ID=878) facilmente ... ma si stanno chiedendo i possibili svantaggi di * utilizzando * controller a istanza singola ? – bzlm

+1

In effetti, ho già realizzato la "istanza singola per controller" dei miei controllori, ma non funziona: "..Se è in uso un controller personalizzato, assicurati che crei una nuova istanza del controller per ogni richiesta.' – 6opuc

+5

Mentre nulla è impossibile, suggerisco che provare ad implementare ciò causerebbe più problemi che risposte. Ad esempio, la proprietà HttpContext di un'istanza di un controller è ovviamente unica per quella richiesta. Innanzitutto probabilmente molti problemi che potresti incontrare. – Mark

risposta

16

Creazione di istanze di controller è un'operazione abbastanza veloce e semplice. Ciò che diventa troppo costoso è la creazione di dipendenze per ogni richiesta. Quindi, quello di cui hai veramente bisogno sono molti controller che condividono le stesse istanze di dipendenze.

E.g. hai seguente controller

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

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

    // ...  
} 

Si dovrebbe configurare il quadro di iniezione di dipendenza affinché utilizzi le stesse istanze di repository per tutte le applicazioni (tenere a mente, si possono avere problemi di sincronizzazione). Ora la creazione di dipendenze non è più costosa. Tutte le dipendenze vengono create un'istanza solo una volta e riutilizzate per tutte le richieste.

Se si hanno molte dipendenze e ci si preoccupa dei costi per ottenere il riferimento all'istanza di ciascuna dipendenza e fornire questi riferimenti all'istanza del controller (che non credo sarà molto costosa), è possibile raggruppare le proprie dipendenze (qualcosa di simile a introdurre dei parametri oggetto refactoring):

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

    // ...  
} 

Ora avete dipendenza da un unico, che sarà iniettato molto rapidamente. Se si configura il framework di distribuzione delle dipendenze per utilizzare Singleton SalesService, tutti i SalesControllers riutilizzeranno la stessa istanza di servizio. La creazione di controller e la fornitura di dipendenze sarà molto veloce.

+1

Molto buono e dettagliato, grazie!È la mia ultima risorsa se non ci sono soluzioni [hack] per rendere singleton controller ... Troppo refactoring e xml-magic (sto usando spring) – 6opuc

+0

sicuramente i problemi di sincronizzazione saranno troppo alti per pagare – user195166

1

Quindi, prima una risposta alla domanda iniziale:

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

Come indicato nella domanda iniziale, se i controllori hanno grandi alberi di dipendenza costituiti principalmente da richiesta Scoped o transitori dipendenze creando poi separatamente per ogni richiesta può avere un po 'di spazio sulla scalabilità della tua applicazione (in Java ad esempio le istanze Servlet sono singletons di default esattamente per questo motivo). Mentre solitamente la CPU e il tempo reale necessari per creare anche un albero di grandi dimensioni sono trascurabili (a meno che non si abbiano calcoli pesanti o comunicazioni di rete nei costruttori dei componenti, che non è quasi mai una buona idea per i componenti transitori o richiesti), l'utilizzo della memoria l'impronta è qualcosa su cui contare. Nel caso delle comuni app di DB-Web, la memoria è il principale fattore che limita il numero di richieste simultanee che un singolo nodo macchina può gestire. Se ogni richiesta ha una copia separata di un grande albero delle dipendenze, insieme possono consumare una quantità significativa di memoria (l'altra cosa da tenere in considerazione è la dimensione iniziale dello stack per un nuovo thread, a proposito).

La risposta accettata 1220560 risolve anche il problema, ma lo considero un brutto attacco e presenta alcuni inconvenienti: è necessario creare questo servizio singleton artificiale che verrà utilizzato dai controller come localizzatore di servizi o proxy per altri servizi. Se si dispone di un solo oggetto singleton per tutti i controller, si nascondono effettivamente dipendenze reali del controller: ad esempio se qualcuno desidera scrivere un test unitario per il controller, deve analizzare attentamente la sua implementazione per vedere quali dipendenze effettivamente usa, in modo che sappia quali falsi/falsi ha bisogno di fornire nella sua configurazione di prova. Se in un secondo momento si modifica il controller e, a seguito della modifica del sottoinsieme di servizi, il controller utilizza anche le modifiche, è molto facile dimenticare di aggiornare anche l'installazione di prova. Questo a volte può portare a bug difficili da tracciare. Contrariamente a ciò, se le vostre dipendenze sono dichiarate esplicitamente come parametri del costruttore, otterrete subito un errore del compilatore nella configurazione del test.Un'altra cosa che puoi fare è avere un proxy singleton/localizzatore di servizi separato per ogni controller, ma in fondo è un sacco di problemi.

Indipendentemente dal fatto che si utilizzi la soluzione proposta da me o quella dalla risposta n. 1220560, è necessario prestare attenzione durante l'iniezione della richiesta delle dipendenze scopate in oggetti singleton come descritto in https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection#registering-your-own-services proprio alla fine della "registrazione dei propri servizi" " sezione. Puoi trovare le possibili soluzioni a questo problema qui: how to use scoped dependency in a singleton in C#/ASP Un'altra cosa da tenere a mente è il problema della concorrenza: è possibile accedere contemporaneamente agli oggetti singleton da più thread che gestiscono richieste concorrenti diverse, quindi assicurati di aggiungere la sincronizzazione corretta a qualsiasi risorsa non thread-safe usa il tuo singleton.

edit:

Ho appena realizzato la domanda iniziale era di circa ASP.NET e questa risposta è per ASP.NET core, in modo che probabilmente non funzionerà per "non-core".

Problemi correlati