从StructureMap获取的HttpContext上的Null User

好吧,我之前的问题/设置有太多的变量,所以我把它剥离到它的裸骨组件。

给出下面的代码使用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; } } 

如何在Watch窗口中实现这一点?

在此处输入图像描述


我有一个Web应用程序,我正在使用2.x中的StructureMap 3.x进行升级,但我对特定依赖项的行为很奇怪。

我有一个ISecurityService,用于获取用户请求页面时的某些内容。 这项服务取决于我称之为ICurrentUser的小型接口。 类实现非常简单,实际上它可能是一个结构。

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

这是通过使用以下代码的dependency injection获得的。

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

当请求进入时,我的站点范围身份validation首先发生,这取决于ISecurityService 。 这发生在OWIN内部并且似乎在HttpContext.User填充之前发生,所以它是null,所以就是这样。

稍后,我有一个ActionFilter,它通过ISecurityService检查当前用户是否同意该站点的当前版本的TermsOfUse,如果不是,他们会被重定向到页面以便首先同意它们。

这一切在structuremap 2.x中运行良好。 为了我迁移到StructureMap3,我已经安装了Nuget包StructureMap.MVC5来帮助我加快速度。

当我的代码到达我的ActionFilter中的行以检查使用条款时,我有这个。

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

CheckLoginAgreedToTermsOfUse()内部,我的CurrentUser实例为null。 即使它成功了,我的断点getCurrentUser()似乎永远不会被击中。 它几乎就像已成定局一样,因为它最后一次是空的,尽管这次它已经解决了。

我有点困惑的是为什么从来没有在ISecurityService的请求上调用getCurrentUser() 。 我甚至尝试在我的连接上明确地使用.LifecycleIs()来处理ICurrentUser而不起作用。

更新:好的,所以只是抬头,我已经开始使用下面接受的方法,虽然它到目前为止工作得很好,但它并没有解决我的核心问题。 结果是新的StructureMap.MVC5 ,基于StructureMap3 ,使用NestedContainers。 它们的请求范围是NestedContainer的生命周期,无论默认值是Transient。 因此,当我第一次请求HttpContextBase时,它将为请求的其余部分返回相同的实例(即使稍后在请求生命周期中,上下文已经更改。您需要不使用NestedContainer(就像我一样)理解它会使ASP.NET vNext复杂化,或者你显式设置For().Use()的生命周期For().Use()映射为每个请求提供一个新实例。注意每个NestedContainer的这个范围会导致控制器出现问题虽然StructureMap.MVC5包使用ControllerConvention处理它,但它不处理视图,并且多次使用的递归视图或视图也可能会导致问题。我仍然在寻找永久修复查看问题,目前我已恢复为DefaultContainer

我没有使用OWIN,但在IIS集成模式下托管时,HttpContext事件完成后才会填充HttpContext。 就DI而言,这意味着您不能依赖于在任何构造函数中使用HttpContext的属性。

如果您考虑它,这是有道理的,因为应该在任何单个用户上下文之外初始化应用程序。

为了解决这个问题,您可以将抽象工厂注入到ICurrentUser实现中并使用Singleton模式来访问它,这可以保证在填充HttpContext之前不会访问它。

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

就个人而言,我会将UserId和UserName属性设置为只读,这将简化设计并确保它们不会在应用程序的其他位置被劫持。 我还会创建一个IClaimsIdentityRetriever服务,该服务被注入到ICurrentUser的构造函数中,而不是在扩展方法中检索声明Id。 扩展方法与DI的粒度相反,通常仅对保证不具有任何依赖性的任务(例如字符串或序列操作)有用。 使其成为服务的松散耦合也意味着您可以轻松地交换或扩展实现。

当然,这意味着您也不能在任何构造函数中调用CurrentUser类的UserId或UserName属性。 如果任何其他类依赖于ICurrentUser,您可能还需要一个ICurrentUserFactory才能安全地使用它。

抽象工厂是处理难以注入的依赖关系并解决包括这一问题在内的许多问题的救星。