2013-03-21 4 views
13

Sto tentando di utilizzare Autofac per iniettare dipendenze in FluentValidation in un'app MVC 4. Penso di aver risolto la strategia, ma mi sto bloccando con la risoluzione della mia richiesta ISomething da un singleton.Autofac - come risolvere Func per ISomething da Singleton dove ISomething è InstancePerHttpRequest

Ecco lo scenario: Ho un validatore che deriva da AbstractValidator di FluentValidation. Ho letto che i validatori di FluentValidation funzionano al meglio come singleton, quindi il mio costruttore si aspetta un Func e lo memorizza per essere utilizzato in seguito. Quando viene utilizzato il validatore, dovrebbe chiedere al factory archiviato un IDataStore, ottenere l'istanza creata per quella richiesta e utilizzarla. Questa è la teoria. Voglio dare credito allo https://github.com/robdmoore/UnobtrusiveMVCTechniques, che mi ha aiutato a risolvere questa soluzione. Ecco il validatore ...

public class SiteAdminViewModelValidator : AbstractValidator<SiteAdminViewModel> { 
    private readonly Func<IDataStore> _dbFactory; 

    public SiteAdminViewModelValidator(Func<IDataStore> dbFactory) { 
     _dbFactory = dbFactory; 

     RuleFor(model => model.SiteCode).Length(1, 40).Must(BeSpecial); 
    } 

    public bool BeSpecial(string siteCode) { 
     var db = _dbFactory(); 
     List<Stuff> stuffs = db.All<Stuff>().ToList(); 

     return true; 
    } 
} 

Se qualcuno mi può indicare un esempio funzionante di quello che sto cercando di realizzare, che sarebbe grande, ma mi piacerebbe anche sapere la soluzione a questo particolare pezzo di trucidosità di Autofac.

Ecco la mia registrazione del validatore ...

public class FluentValidatorModule : Module { 
    protected override void Load(ContainerBuilder builder) { 
     base.Load(builder); 
     builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance(); 

    var validators = AssemblyScanner.FindValidatorsInAssembly(System.Reflection.Assembly.GetExecutingAssembly()); 
    validators.ToList().ForEach(v => builder.RegisterType(v.ValidatorType).As(v.InterfaceType).SingleInstance()); 
    } 
} 

Ecco la mia registrazione per la fabbrica IDataStore ...

builder.RegisterType<SuperDB>().As<IDataStore>().InstancePerHttpRequest(); 
builder.Register<Func<IDataStore>>(c => { 
             var context = c.Resolve<IComponentContext>(); 
             return context.Resolve<IDataStore>; 
            }); 

Ecco l'errore che sto ottenendo quando il mio validatore richiede un IDataStore su la riga - var db = _dbFactory();

Nessun ambito con un tag corrispondente a "AutofacWebRequest" è visibile dall'ambito in cui è stata richiesta l'istanza. Questo indica generalmente che un componente registrato come per-HTTP richiesta sta per essere richiesto da un SingleInstance() componente (o di uno scenario simile.) Sotto l'integrazione web richiedere sempre le dipendenze dal DependencyResolver.Current o ILifetimeScopeProvider.RequestLifetime, mai dal contenitore stesso.

... che è esattamente quello che ho ottenuto quando ho provato prima di scrivere la mia propria registrazione di fabbrica - la registrazione di Func. Dalla lettura di varie risposte a domande simili, sembrava che quello che ho sopra dovrebbe funzionare perché pensavo che ora stavo risolvendo il Func per ottenere il risolutore corrente.

Qualsiasi aiuto sarà molto apprezzato.

risposta

17

Sono d'accordo che questo dovrebbe funzionare: lo Func<IDataStore> sta definendo uno stabilimento che produrrà la dipendenza in ciascun metodo come richiesto.

Il modo in cui sono riuscito a utilizzare questo metodo è utilizzare lo DependencyResolver.Current come suggerisce il messaggio di errore. La ragione principale è che ho già avuto da esso istituito usando il pacchetto NuGet Autofac.Mvc4 ...

DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 

Così effettivamente messa a punto del metodo ho il seguente func

public Func<T> PerHttpSafeResolve<T>() 
{ 
    return() => DependencyResolver.Current.GetService<T>(); 
} 

E quando la costruzione della contenitore

builder.RegisterType<SuperDB>().As<IDataStore>().InstancePerHttpRequest(); 
builder.RegisterInstance(PerHttpSafeResolve<IDataStore>()); 

EDIT: la seconda linea che sta registrando l'istanza sta dicendo - Se avete bisogno di un Func<IDataStore> quindi utilizzare il valore passato nel metodo. Il risultato di PerHttpSafeResolve<IDataStore> è solo una funzione (di fabbrica), quindi può vivere come una singola istanza.

+0

Questo funziona! Grazie. Sono nuovo di Autofac. Posso ottenere solo un breve chiarimento sulla registrazione del builder.RegisterInstance (PerHttpSafeResolve [IDataStore]()); linea? Così dice "ogni volta che qualcuno chiede un Func [IDataStore], usa questo metodo per dargliene uno"? –

+0

Vedi la mia modifica - cercherò di spiegarlo – Felix

+0

Bello !! Quella tecnica è incredibile. Lo adoro. Tanto che ho scritto un post sul blog: http://robdmoore.id.au/blog/2013/03/23/resolving-request-scoped-objects-into-a-singleton-with-autofac/. Se hai un blog tu stesso fammelo sapere e ci collegherò ad esso invece del tuo SO user. –

7

Il problema è lo scopo dei validatori. Autofac risolve sempre le dipendenze SingleInstance dal contenitore dell'applicazione, il che significa che anche le dipendenze dei validatori provengono dal contenitore dell'applicazione.

Autofac richiede l'istanza Func<IDataStore> da , non dal contenitore della richiesta. Quando risolvi IComponentContext stai ricevendo il contenitore in cui è stato risolto il validatore: il contenitore dell'applicazione. Poiché Func<IDataStore> ha un ambito per una richiesta, Autofac non è in grado di fornirlo a livello di applicazione, quindi l'errore.

L'approccio corretto consiste nel registrare i validatori come InstancePerHttpRequest. La vita di un componente è dettata dalla sua dipendenza più lunga; i validatori funzionano meglio come singleton quando non hanno dipendenze.In questo caso, tuttavia, un validatore che dipende da IDataStore è costretto a vivere al massimo per la durata dell'istanza IDataStore. (Sul lato positivo, puoi liberarti di Func.)

Pensa ad andare a una festa: se fai un giro con l'ospite, devi rimanere lì fino a quando la festa è finita. Non puoi guidare con l'host se vuoi andartene presto.

+1

Questo mi aiuta a capire cosa sta succedendo. Immagino sia per questo che la soluzione di Felix funziona e la mia no. Il suo esplicitamente utilizza DependencyResolver.Current mentre stavo usando c.Resolve. Voglio davvero evitare di costruire i validatori più spesso di quanto sia assolutamente necessario. Dal momento che posso ottenere una soluzione singleton per lavorare, ho intenzione di seguire questa strada. –

+0

@TimHardy: Felice di sentirlo. Non dimenticare di dare voti positivi alle risposte che ti hanno aiutato :-) –

Problemi correlati