Utente null su HttpContext ottenuto da StructureMap

Ok, la mia domanda / configurazione precedente aveva troppe variabili, quindi sto spogliando questo per i suoi componenti bare bones.

Dato il codice sottostante usando StructureMap3 …

//IoC setup For().UseSpecial(x => x.ConstructedBy(y => HttpContext.Current != null ? new HttpContextWrapper(HttpContext.Current) : null )); For().Use(); //Classes used public class CurrentUser : ICurrentUser { public CurrentUser(HttpContextBase httpContext) { if (httpContext == null) return; if (httpContext.User == null) return; var user = httpContext.User; if (!user.Identity.IsAuthenticated) return; UserId = httpContext.User.GetIdentityId().GetValueOrDefault(); UserName = httpContext.User.Identity.Name; } public Guid UserId { get; set; } public string UserName { get; set; } } public static class ClaimsExtensionMethods public static Guid? GetIdentityId(this IPrincipal principal) { //Account for possible nulls var claimsPrincipal = principal as ClaimsPrincipal; if (claimsPrincipal == null) return null; var claimsIdentity = claimsPrincipal.Identity as ClaimsIdentity; if (claimsIdentity == null) return null; var claim = claimsIdentity.FindFirst(x => x.Type == ClaimTypes.NameIdentifier); if (claim == null) return null; //Account for possible invalid value since claim values are strings Guid? id = null; try { id = Guid.Parse(claim.Value); } catch (ArgumentNullException) { } catch (FormatException) { } return id; } } 

Com’è ansible nella finestra di Watch?

inserisci la descrizione dell'immagine qui


Ho un’applicazione web con cui sto aggiornando l’uso di StructureMap 3.x da 2.x, ma ho un comportamento strano su dipendenze specifiche.

Ho un ISecurityService che uso per verificare alcune cose quando un utente richiede una pagina. Questo servizio dipende da una piccola interfaccia che ho chiamato ICurrentUser. L’implementazione della class è abbastanza semplice, potrebbe davvero essere una struttura.

 public interface ICurrentUser { Guid UserId { get; } string UserName { get; } } 

Questo è ottenuto tramite l’iniezione di dipendenza utilizzando il codice seguente.

 For().Use(ctx => getCurrentUser(ctx.GetInstance())); For().Use(() => getHttpContext()); private HttpContextBase getHttpContext() { return new HttpContextWrapper(HttpContext.Current); } private ICurrentUser getCurrentUser(HttpContextBase httpContext) { if (httpContext == null) return null; if (httpContext.User == null) return null; // <--- var user = httpContext.User; if (!user.Identity.IsAuthenticated) return null; var personId = user.GetIdentityId().GetValueOrDefault(); return new CurrentUser(personId, ClaimsPrincipal.Current.Identity.Name); } 

Quando arriva una richiesta, viene ISecurityService prima l’autenticazione del mio sito, che dipende da ISecurityService . Questo accade all’interno di OWIN e sembra verificarsi prima che HttpContext.User sia stato popolato, quindi è null, quindi sia.

Successivamente, ho un ActionFilter che controlla, tramite un ISecurityService , se l’utente corrente ha accettato la versione corrente di TermsOfUse per il sito, in caso contrario vengono reindirizzati alla pagina per accettarli per primi.

Tutto questo ha funzionato bene in structuremap 2.x. Per la mia migrazione a StructureMap3 ho installato il pacchetto Nuget StructureMap.MVC5 per velocizzare le cose per me.

Quando il mio codice raggiunge la linea nel mio ActionFilter per verificare i termini di utilizzo, ho questo.

 var securityService = DependencyResolver.Current.GetService(); agreed = securityService.CheckLoginAgreedToTermsOfUse(); 

All’interno di CheckLoginAgreedToTermsOfUse() , la mia istanza di CurrentUser è nullo. Anche se sarebbe pericoloso, e il mio breakpoint all’interno di getCurrentUser () non sembra mai essere colpito. È quasi come se fosse una conclusione scontata, poiché era l’ultima volta nullo, anche se sarebbe stata risolta questa volta.

Sono ISecurityService sconcertato sul motivo per cui getCurrentUser() non viene mai chiamato sulla richiesta di ISecurityService . Ho anche provato ad attaccare esplicitamente un .LifecycleIs() sul mio collegamento per la gestione di ICurrentUser senza alcun effetto.

AGGIORNAMENTO: Ok, quindi solo un avviso, ho iniziato ad usare il metodo accettato di seguito e, sebbene abbia funzionato fino ad ora, non ha risolto il mio problema principale. Risolve il nuovo StructureMap.MVC5 , basato su StructureMap3 , utilizza NestedContainers. Quale ambito le loro richieste alla durata del NestedContainer, indipendentemente dall’impostazione predefinita Transient. Quindi, quando ho richiesto HttpContextBase per la prima volta, restituirà la stessa istanza per il resto della richiesta (anche se più tardi nella durata della richiesta, il contesto è cambiato. Non è necessario utilizzare NestedContainer (che, come capire che complicherà le cose ASP.NET vNext), o impostare in modo esplicito il ciclo di vita di For().Use() mapping For().Use() per darti una nuova istanza per richiesta.Tieni che questo ambito per NestedContainer causa problemi con i controller anche in MVC, mentre il pacchetto StructureMap.MVC5 gestisce questo con un ControllerConvention , non gestisce le Visualizzazioni e le visualizzazioni ricorsive o le visualizzazioni utilizzate più volte probabilmente causano anche dei problemi. Sto ancora cercando una soluzione permanente per Problemi di visualizzazioni, per il momento sono tornato a DefaultContainer .

    Non ho lavorato con OWIN, ma quando hosting in modalità integrata IIS HttpContext non viene popolato fino a quando l’evento HttpApplication.Start non è completo. In termini di DI, ciò significa che non è ansible fare affidamento sull’uso delle proprietà di HttpContext in qualsiasi costruttore.

    Questo ha senso se ci si pensa perché l’applicazione deve essere inizializzata al di fuori di ogni singolo contesto utente.

    Per aggirare questo problema, è ansible inserire una fabbrica astratta nell’implementazione di ICurrentUser e utilizzare un modello Singleton per accedervi, il che garantisce che HttpContext non sarà accessibile finché non viene popolato.

     public interface IHttpContextFactory { HttpContextBase Create(); } public class HttpContextFactory : IHttpContextFactory { public virtual HttpContextBase Create() { return new HttpContextWrapper(HttpContext.Current); } } public class CurrentUser // : ICurrentUser { public CurrentUser(IHttpContextFactory httpContextFactory) { // Using a guard clause ensures that if the DI container fails // to provide the dependency you will get an exception if (httpContextFactory == null) throw new ArgumentNullException("httpContextFactory"); this.httpContextFactory = httpContextFactory; } // Using a readonly variable ensures the value can only be set in the constructor private readonly IHttpContextFactory httpContextFactory; private HttpContextBase httpContext = null; private Guid userId = Guid.Empty; private string userName = null; // Singleton pattern to access HTTP context at the right time private HttpContextBase HttpContext { get { if (this.httpContext == null) { this.httpContext = this.httpContextFactory.Create(); } return this.httpContext; } } public Guid UserId { get { var user = this.HttpContext.User; if (this.userId == Guid.Empty && user != null && user.Identity.IsAuthenticated) { this.userId = user.GetIdentityId().GetValueOrDefault(); } return this.userId; } set { this.userId = value; } } public string UserName { get { var user = this.HttpContext.User; if (this.userName == null && user != null && user.Identity.IsAuthenticated) { this.userName = user.Identity.Name; } return this.userName; } set { this.userName = value; } } } 

    Personalmente, renderei le proprietà UserId e UserName in sola lettura, il che semplificherebbe la progettazione e assicurerebbe che non vengano dirottate altrove nell’applicazione. Creo anche un servizio IClaimsIdentityRetriever che viene iniettato nel costruttore di ICurrentUser invece di recuperare l’ID attestazioni in un metodo di estensione. I metodi di estensione vanno contro la grana del DI e sono generalmente utili solo per compiti a cui è garantito di non avere alcuna dipendenza (come la manipolazione di stringhe o sequenze). L’accoppiamento lento di renderlo un servizio significa anche che puoi facilmente scambiare o estendere l’implementazione.

    Ovviamente, ciò implica che non è ansible chiamare le proprietà UserId o UserName della class CurrentUser in qualsiasi costruttore. Se qualsiasi altra class dipende da ICurrentUser, potrebbe anche essere necessario un ICurrentUserFactory per poterlo utilizzare in tutta sicurezza.

    La fabbrica astratta è un vero toccasana quando si ha a che fare con dipendenze difficili da iniettare e risolvere una serie di problemi, incluso questo.