11

Ich habe eine Konfiguration für Simple Injector, wo ich alle meine Registrierungen in OWIN-Pipeline verschoben habe.Registrieren IAuthenticationManager mit Simple Injector

Nun das Problem ist, ich habe einen Controller AccountController, die tatsächlich Parameter wie

public AccountController(
    AngularAppUserManager userManager, 
    AngularAppSignInManager signinManager, 
    IAuthenticationManager authenticationManager) 
{ 
    this._userManager = userManager; 
    this._signInManager = signinManager; 
    this._authenticationManager = authenticationManager; 
} 

meine Owin Pipeline nimmt Jetzt sieht Konfigurationen so etwas wie dieses

public void Configure(IAppBuilder app) 
{ 
    _container = new Container(); 
    ConfigureOwinSecurity(app); 
    ConfigureWebApi(app); 
    ConfigureSimpleinjector(_container); 

    app.Use(async (context, next) => 
    { 
     _container.Register<IOwinContext>(() => context); 
     await next(); 
    }); 

    _container.Register<IAuthenticationManager>(
     () => _container.GetInstance<IOwinContext>().Authentication); 

    _container.Register<SignInManager<Users, Guid>, AngularAppSignInManager>(); 
} 

private static void ConfigureOwinSecurity(IAppBuilder app) 
{ 
    app.UseCookieAuthentication(new CookieAuthenticationOptions 
    { 
     AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
     CookieName = "AppNgCookie", 
     //LoginPath = new PathString("/Account/Login") 
    }); 
} 

private static void ConfigureWebApi(IAppBuilder app) 
{ 
    HttpConfiguration config = new HttpConfiguration(); 
    WebApiConfig.Register(config); 
    app.UseWebApi(config); 
} 

private static void ConfigureSimpleinjector(Container container) 
{ 
    SimpleInjectorInitializer.Initialize(container); 
} 

And Simple Injector Initializer sieht ungefähr so ​​

private static void InitializeContainer(Container container) 
{ 
    container.Register<DbContext, AngularAppContext>(); 

    container.Register<IUserStore<Users, Guid>, AngularAppUserStore>(); 
    container.Register<IRoleStore<Roles, Guid>, AngularAppRoleStore>(); 

    container.Register<UserManager<Users, Guid>, AngularAppUserManager>(); 
    container.Register<RoleManager<Roles, Guid>, AngularAppRoleManager>(); 
    //container.RegisterPerWebRequest<SignInManager<Users, Guid>, AngularAppSignInManager>(); 

    container.Register<IdentityFactoryOptions<AngularAppUserManager>, IdentityFactoryOptions<AngularAppUserManager>>(); 
    //container.Register<IAuthenticationManager>(() => HttpContext.Current.GetOwinContext().Authentication); 

    //container.Register<SignInManager<Users, Guid>, AngularAppSignInManager>(); 
    // For instance: 
    // container.Register<IUserRepository, SqlUserRepository>(); 
} 

Jetzt das Problem m ist Der Controller kann IAuthenticationManager nicht registrieren. Ich habe versucht,

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication); 

Aber das läßt mich mit Ausnahme als mit:

System.InvalidOperationException: Nein owin.Environment Artikel wurde im Kontext gefunden.

In dieser Linie

container.Register<IAuthenticationManager>(
    () => HttpContext.Current.GetOwinContext().Authentication); 

Ich habe auch versucht, statt in public void Configure(app)HttpContext.Current.GetOwinContext().Authentication Verfahren mit der oben genannten Konfiguration unter Verwendung registriert app.Use() verwenden. Und dann lösen Sie es später über den Container auf, um die IAuthenticationManager zu erhalten. Aber alle Möglichkeiten haben mich gescheitert.

Was fehlt mir hier? Warum kann HttpContext.Current.GetOwinContext().Authentcation die Authentifizierung von OwinContext nicht auflösen?

Und wenn das nicht ist, warum funktioniert die gleiche Konfiguration über app.Use auch nicht?

+0

Ich stimme Rics Antwort völlig zu. IMO sollte es als Antwort markiert sein. – Steven

Antwort

6

Was Sie mit IAuthenticationManager Registrierung worked for me ohne Probleme tun. Irgendwann war ich immer die gleiche Ausnahme wie Sie immer waren, aber das wurde durch die Linie verursacht mit

container.Verify(); 

kurz nach dem Container Konfiguration. Es wurde versucht, alle Instanzen von registrierten Objekten zu erstellen, aber es war kein HttpContext.Current vorhanden, daher die Ausnahme.

Erhalten Sie keine Instanzen aus dem Container, bevor eine HTTP-Anforderung verfügbar ist? Wenn Sie sie wirklich brauchen, ist die einzige Möglichkeit, dies zu umgehen, die Verwendung von Factory, wie von NightOwl888 vorgeschlagen.Wenn Sie vor der HTTP-Anfrage keinen Container benötigen, dann refaktorieren Sie ihn, so dass er nicht außerhalb der HTTP-Anfrage verwendet wird.

+0

Dies ist tatsächlich derjenige, der das Problem verursacht hat. Ich frage mich, ob es irgendwelche Möglichkeiten gibt, dass ich eine bestimmte Registrierung aus der verify() -Methode oder so ausschließen kann? – Joy

+0

Nein, glaube nicht, dass du Ausschluss machen kannst. Aber 'Verify()' ist nicht obligatorisch und zu Testzwecken. Ist das richtig, @ Steven? – trailmax

+0

Es scheint, als ob dieses Problem zuvor gekommen war https://simpleinjector.codeplex.com/discussions/461871 – Joy

3

Siehe meine Antwort here.

Obwohl Sie auf einen anderen Typ zugreifen, ist das Problem das gleiche. Sie können sich beim Starten der Anwendung nicht auf Eigenschaften von HttpContext verlassen, da die Anwendung außerhalb des Benutzerkontexts initialisiert wird. Die Lösung besteht darin, eine abstrakte Factory so zu erstellen, dass die Werte zur Laufzeit und nicht zur Objekterstellung gelesen werden und der Factory-Typ anstelle des IAuthenticationManager-Typs in den Controller injiziert wird.

public class AccountController 
{ 
    private readonly AngularAppUserManager _userManager; 
    private readonly AngularAppSignInManager _signInManager; 
    private readonly IAuthenticationManagerFactory _authenticationManagerFactory; 

    public AccountController(AngularAppUserManager userManager 
     , AngularAppSignInManager signinManager 
     , IAuthenticationManagerFactory authenticationManagerFactory) 
    { 
     this._userManager = userManager; 
     this._signInManager = signinManager; 
     this._authenticationManagerFactory = authenticationManagerFactory; 
    } 

    private IAuthenticationManager AuthenticationManager 
    { 
     get { return this._authenticationManagerFactory.Create(); } 
    } 

    private void DoSomething() 
    { 
     // Now it is safe to call into HTTP context 
     var manager = this.AuthenticationManger; 
    } 
} 

public interface IAuthenticationMangerFactory 
{ 
    IAuthenticationManger Create(); 
} 

public class AuthenticationMangerFactory 
{ 
    public IAuthenticationManger Create() 
    { 
     HttpContext.Current.GetOwinContext().Authentication; 
    } 
} 

// And register your factory... 
container.Register<IAuthenticationManagerFactory, AuthenticationMangerFactory>(); 
+0

Die Idee scheint gültig zu sein. Aber gibt es keinen anderen Weg? Ich meine, Microsoft hätte keinen so großen Fehler gemacht. Ich nehme an, dass sie AuthenticationManager eng mit system.web verbinden würden. – Joy

+2

Viele Leute machen den Fehler zu denken, dass der DI-Container ein magisches Geschoss ist, das jede Instanz erstellen soll. Aber nicht jedes Objekt kann beim Start der Anwendung erstellt werden. Dies ist der einzige Zeitpunkt, an dem das Erstellen der Anwendung relevant ist. Manchmal müssen wir in der Lage sein, Instanzen zur Laufzeit zu bekommen, und dann wenden wir uns an abstract factory, um das Problem zu lösen (was besser ist als [service locator] (http://blog.ploeh.dk/2010/02/03)/ServiceLocatorisanAnti-Pattern /)). – NightOwl888

+0

@ NightOwl888 nur eine Sache, ist es richtig, eine GenericFactory mit einem 'T Create ()' zu erstellen, das den Container dazu auffordert, 'container.GetInstance ()'? –

24

Als TrailMax bereits erwähnt, wurde die Ausnahme, die Sie erhalten haben, wahrscheinlich während des Anrufs auf container.Verify() ausgelöst. Zum Zeitpunkt des Starts der Anwendung gibt es keine HttpContext, daher die Ausnahme.

Obwohl die Entfernung des Anrufs zu container.Verify() das Problem "lösen" würde, rate ich davon ab, dies zu tun, und ich werde eine bessere Lösung unten vorschlagen.

NightOwl888 verweist auf einen alten Artikel von Mark Seemann (den ich für seine Arbeit an DI sehr schätze). In that article erklärt Mark, warum er denkt, dass die Überprüfung des Containers nutzlos ist. Dieser Artikel scheint jedoch veraltet zu sein und widerspricht neuen Artikeln von Mark. In a newer article Mark erklärt, dass einer der großen Vorteile der Verwendung Pure DI (das ist Dependency Injection ohne Verwendung eines DI-Containers) ist, dass it provides the fastest feedback about correctness that you can get. Mark und der Rest von uns schätzen offensichtlich sowohl die Rückmeldung des Compilers als auch die Rückmeldung von statischen Code-Analyse-Tools als schnellen Feedback-Mechanismus. Sowohl der Simple Injector .Verify() als auch der Diagnostic Services versuchen, dieses schnelle Feedback zurück zu bringen. Meiner Ansicht nach nimmt die Methode .Verify() von Simple Injector die Aufgabe an, die der Compiler für Sie bei der Ausführung von Pure DI übernehmen würde, und die Diagnostic Services sind in gewisser Hinsicht ein auf Ihre DI-Konfiguration spezialisiertes Tool zur statischen Codeanalyse.

Während es für einen Container in der Tat nicht möglich ist, seine Konfiguration zu 100% zu überprüfen, erwies sich die Überprüfung für mich immer als wertvolle Praxis. Es wäre albern zu denken, dass ein einfacher Anruf zu .Verify() in einer völlig fehlerfreien oder sogar einer funktionierenden Anwendung resultieren würde. Wenn jemand denken könnte, dass das, was "Überprüfung" Ihrer DI-Konfiguration bedeutet, ich verstehe, warum sie argumentieren, dass diese Funktionalität wertlos ist. Klingt wie eine Aussage der Binsenweisheit. Es gibt keinen Container, einschließlich Simple Injector, der vorgibt, eine solche Funktion zu haben.

Sie sind natürlich immer noch verantwortlich für das Schreiben von Integrations- und/oder Komponententests für z. Erkennen, ob die Reihenfolge der angewandten Dekoratoren korrekt ist oder ob alle Implementierungen von ISomeService<T> tatsächlich im Container registriert sind.

Ich möchte 2 spezifische Argumente aus Marks Blog gegen die Überprüfung des Containers erwähnen.

Es ist leicht, in die Situation zu gelangen, die der Container verifiziert, aber zur Laufzeit noch bricht.

ich damit einverstanden, aber ich glaube, dass die einfache Injector Dokumentation einige große Leitlinien zu bekommen hat, wie man diese here zu nähern.

Wenn Convention über Konfiguration durchgeführt wird, ist es leicht, Registrierungen zu erhalten, die sowieso nicht im Container sein sollten.

Ich hatte dieses Problem nie, weil ich denke, es ist eine vernünftige Übung, um in dieser Situation sowieso zu vermeiden.

Zurück zur Frage:

Obwohl eine der Spitzen im Simple Injector Dokumentation ist abstrakt Fabriken zu verwenden, würde ich tun, dass in diesem Fall nicht. Eine Fabrik für etwas zu schaffen, was bereits existiert, klingt für mich ziemlich komisch.Vielleicht ist es nur ein Problem der korrekten Benennung, aber warum würde ein AccountController eine AuthenticationFactory oder AuthenticationContext benötigen? Mit anderen Worten, warum sollte die Anwendung etwas von uns wissen, wenn wir Probleme haben, die Dinge wegen einiger Design-Eigenheiten in ASP.NET Identity zu vertauschen?

Stattdessen können wir durch Anpassung der Registrierung für die IAuthenticationManager eine Authentifizierungskomponente aus einem neu erstellten OwinContext zum Zeitpunkt des Starts/Verifizierens zurückgeben und zur Laufzeit das 'normale' oder konfigurierte AuthenticationManager zurückgeben. Dadurch wird die Notwendigkeit einer Fabrik beseitigt und die Verantwortung an die Kompositionswurzel verschoben, wo sie sein sollte. Und Sie können die IAuthenticationManager überall dort injizieren, wo Sie es brauchen, während Sie immer noch einen Anruf an .Verify() machen können.

Der Code sieht so aus:

container.RegisterPerWebRequest<IAuthenticationManager>(() => 
    AdvancedExtensions.IsVerifying(container) 
     ? new OwinContext(new Dictionary<string, object>()).Authentication 
     : HttpContext.Current.GetOwinContext().Authentication); 

Ein noch feste Lösung wäre aber gar nicht auf den IAuthenticationManager abzuhängen, weil abhängig von dieser Schnittstelle uns bewirkt, dass das Schnittstelle Segregationsprinzip verletzen, so dass es schwer Erstellen Sie eine Proxy-Implementierung, die die Erstellung verzögert.

Sie könnten dies tun, indem Sie eine Abstraktion definieren, die Ihren Bedürfnissen und nur Ihren Bedürfnissen entspricht. Betrachtet man die Identitätsvorlage Aufrufe an die IAuthenticationManager diese Abstraktion würde nichts mehr als die .SignIn() und Methoden benötigen. Dies würde jedoch dazu führen, dass Sie den fehlerhaften AccountController, den Sie von der Visual Studio-Vorlage "kostenlos" erhalten haben, komplett umgestalten, was ein ziemliches Unterfangen sein kann.

+2

Dies ist die beste Antwort! – Mrchief

+2

"refaktorieren Sie den beschissenen' AccountController', den Sie von der Visual Studio Vorlage "kostenlos" bekommen haben ", <= dieses Zitat. meinen Tag gerettet. – QuantumHive

+0

Dies muss definitiv die akzeptierte Lösung sein. Das Refactoring dieses Authentifizierungs-Controllers führt zu positiven Änderungen in der gesamten Pipeline, wie z. B. das Fixieren der AppUserManager-Registrierungen mit SI und OWIN. So chaotisch vorher und so sauber nach – Isaac