example c# asp.net-mvc structuremap structuremap3

c# - example - Usuario nulo en HttpContext obtenido de StructureMap



structure map example c# (1)

No he trabajado con OWIN, pero cuando se aloja en modo integrado de IIS, el HttpContext no se rellena hasta que se completa el evento HttpApplication.Start. En términos de DI, esto significa que no puede confiar en el uso de propiedades de HttpContext en cualquier constructor.

Esto tiene sentido si lo piensas porque la aplicación debe inicializarse fuera de cualquier contexto de usuario individual.

Para evitar esto, puede inyectar una fábrica abstracta en su implementación de ICurrentUser y utilizar un patrón de Singleton para acceder a ella, lo que garantiza que no se accederá a HttpContext hasta que se complete.

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, haría las propiedades UserId y UserName de solo lectura, lo que simplificaría el diseño y garantizaría que no fueran secuestradas en ningún otro lugar de la aplicación. También crearía un servicio IClaimsIdentityRetriever que se inyecta en el constructor de ICurrentUser en lugar de recuperar el ID de reclamaciones en un método de extensión. Los métodos de extensión van en contra de DI y generalmente solo son útiles para tareas que tienen la garantía de no tener ninguna dependencia (como la manipulación de cadenas o secuencias). El acoplamiento flexible de convertirlo en un servicio también significa que puede intercambiar o ampliar fácilmente la implementación.

Por supuesto, esto implica que tampoco puede llamar a las propiedades UserId o UserName de su clase CurrentUser en ningún constructor. Si cualquier otra clase depende de ICurrentUser, también puede necesitar ICurrentUserFactory para usarla de manera segura.

La fábrica abstracta es un salvavidas cuando se trata de dependencias difíciles de inyectar y resuelve una serie de problemas, incluido este.

Bien, mi pregunta / configuración anterior tenía demasiadas variables, así que lo estoy reduciendo a sus componentes básicos.

Dado el siguiente código usando StructureMap3 ...

//IoC setup For<HttpContextBase>().UseSpecial(x => x.ConstructedBy(y => HttpContext.Current != null ? new HttpContextWrapper(HttpContext.Current) : null )); For<ICurrentUser>().Use<CurrentUser>(); //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; } }

¿Cómo es esto posible en la ventana de Inspección?

Tengo una aplicación web a la que me estoy actualizando usando StructureMap 3.x desde 2.x, pero estoy obteniendo un comportamiento extraño en dependencias específicas.

Tengo un ISecurityService que uso para obtener verificar algunas cosas cuando un usuario solicita una página. Este servicio depende de una pequeña interfaz que he llamado ICurrentUser. La implementación de la clase es bastante simple, realmente podría ser una estructura.

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

Esto se obtiene a través de la inyección de dependencia usando el siguiente código.

For<ICurrentUser>().Use(ctx => getCurrentUser(ctx.GetInstance<HttpContextBase>())); For<HttpContextBase>().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); }

Cuando aparece una solicitud, la autenticación de mi sitio se realiza primero, lo que depende de ISecurityService . Esto ocurre dentro de OWIN y parece ocurrir antes de que HttpContext.User se haya rellenado, por lo que es nulo, que así sea.

Más adelante, tengo un ActionFilter que verifica, a través de un ISecurityService , si el usuario actual ha aceptado la versión actual de los TermsOfUse para el sitio, si no se los redirige a la página para aceptarlos primero.

Todo esto funcionó bien en structuremap 2.x. Para mi migración a StructureMap3, he instalado el paquete Nuget StructureMap.MVC5 para ayudarme a acelerar las cosas.

Cuando mi código llega a la línea en mi ActionFilter para verificar los términos de uso, tengo esto.

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

Dentro de CheckLoginAgreedToTermsOfUse() , mi instancia de CurrentUser es nula. Aunque todo hubiera tenido éxito, y mi punto de interrupción dentro de getCurrentUser () nunca parece ser golpeado. Es casi como si fuera una conclusión inevitable, ya que fue nula la última vez, a pesar de que se habría resuelto esta vez.

Estoy un poco desconcertado sobre por qué getCurrentUser() nunca se getCurrentUser() en la solicitud de ISecurityService . Incluso traté de .LifecycleIs<UniquePerRequestLifecycle>() explícitamente .LifecycleIs<UniquePerRequestLifecycle>() en mi conexión para manejar ICurrentUser sin ningún efecto.

ACTUALIZACIÓN: Ok, solo un aviso, comencé a usar el método aceptado a continuación, y aunque ha funcionado muy bien hasta ahora, no resolvió mi problema central. Resulta que el nuevo StructureMap.MVC5 , basado en StructureMap3 , usa NestedContainers. Que abarcan sus solicitudes a la duración del NestedContainer, independientemente de que el valor predeterminado sea Transitorio. Así que cuando HttpContextBase por primera vez, devolverá esa misma instancia para el resto de la solicitud (aunque más adelante en la vida útil de la solicitud, el contexto ha cambiado. No necesita usar NestedContainer (que, como yo) entiendo que complicará las cosas ASP.NET vNext), o establece explícitamente el ciclo de vida de la asignación For<>().Use<>() para darle una nueva instancia por solicitud. Tenga en cuenta que este alcance por NestedContainer causa problemas con los Controladores también en MVC. Aunque el paquete StructureMap.MVC5 maneja esto con ControllerConvention , no maneja Views, y las vistas recursivas o vistas que se usan varias veces probablemente también causarán problemas. Todavía estoy buscando una solución permanente para el Ve el problema, por el momento he vuelto al DefaultContainer .