在HttpContext的从StructureMap获得空用户 [英] Null User on HttpContext obtained from StructureMap

查看:160
本文介绍了在HttpContext的从StructureMap获得空用户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好吧,我的previous问题/设置有太多的变数,所以我剥离下来到它的梗概组件。

Ok, my previous question/setup had too many variables, so I'm stripping this down to it's bare bones components.

StructureMap3 ...

Given the code below using 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;
    }
}

如何在监视窗口中这可能吗?

How is this possible in the Watch window?

我有我升级到使用StructureMap 3.X从2.x的Web应用程序,但我发现在特定的依赖奇怪的行为。

I have a web application that I'm upgrading to using StructureMap 3.x from 2.x, but I'm getting odd behavior on specific dependency.

我有一个ISecurityService,我用它来获得核实一些事情,当用户请求一个页面。该服务依赖一个小界面,我已经叫ICurrentUser上。这个类实现是pretty平原,实际上它可能是一个结构。

I have a ISecurityService that I use to obtain verify some things when a user requests a page. This service depends on a small interface that I've called ICurrentUser. The class implementation is pretty plain, really it could be a struct.

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

这是通过依赖注入使用下面的code获得的。

This is obtained via dependency injection using the below code.

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

当一个请求到来时,我的网站广泛认证首先发生,这取决于 ISecurityService 。这种情况OWIN内并出现之前 HttpContext.User中来发生已被填充,所以它的空,就这样吧。

When a request comes in, my site wide authentication happens first, which depends on ISecurityService. This happens inside of OWIN and appears to occur before HttpContext.User has been populated, so it's null, so be it.

后来,我有一个检查ActionFilter,通过 ISecurityService ,如果当前用户已经同意到当前版本的TermsOfUse为网站,如果他们不被重定向到页面先同意他们。

Later on, I have an ActionFilter that checks, via a ISecurityService, if the current user has agreed to the current version of the TermsOfUse for the site, if not they are redirected to the page to agree to them first.

这一切structuremap 2.x中工作正常对于我的迁移StructureMap3我已经安装了的NuGet包StructureMap.MVC5有助于加速的东西为我说话。

This all worked fine in structuremap 2.x. For my migration to StructureMap3 I've installed the Nuget package StructureMap.MVC5 to help speed things up for me.

在我的code获取到线在我ActionFilter检查使用我有这样的条款。

When my code gets to the line in my ActionFilter for checking the terms of use I have this.

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

里面的 CheckLoginAgreedToTermsOfUse(),我的的currentUser 的实例为null。尽管它会hazve成功了,我getCurrentUser内断点()似乎从来没有被击中。它仿佛是一个定局,因为它是空的最后一次,即使它已经解决了这个时候。

Inside of CheckLoginAgreedToTermsOfUse(), my instance of CurrentUser is null. Even though it would hazve succeeded, and my breakpoint inside of getCurrentUser() never seems to be hit. Its almost as if it's a foregone conclusion, since it was null the last time , even though it would have resolved this time.

我有点困惑,为什么 getCurrentUser()永远不会被调用对 ISecurityService 的请求。在我的接线图()处理 ICurrentUser 有没有效果<;我甚至尝试明确坚持一个 .LifecycleIs&LT; UniquePerRequestLifecycle过夜。 / S>

I'm kind of baffled as to why getCurrentUser() is never called on the request for ISecurityService. I even tried explicitly sticking a .LifecycleIs<UniquePerRequestLifecycle>() on my hookup for handling ICurrentUser with no effect.

更新:
好了,所以刚抬起头,我用下面接受的方法开始,虽然它一直很大,到目前为止,它并没有解决我的核心问题。原来,新的 StructureMap.MVC5 ,根据 StructureMap3 ,使用NestedContainers。它的范围及其对NestedContainer的寿命,而不管默认的是瞬态的请求。所以,当我要求 HttpContextBase 首次,它就会返回同一个实例的请求(即使后来在请求生命周期的其余部分,已经时过境迁你需要或者不使用NestedContainer(其中,据我所知这将事情ASP.NET vNext复杂),或者你明确地设置对于&LT的生命周期;&GT;()使用&lt;&GT;( )映射给你一个新实例,每个请求。请注意,每NestedContainer此作用域会导致控制器的问题,以及在MVC。虽然 StructureMap.MVC5 包处理这种以 ControllerConvention ,它不处理意见,并用递归的观点或看法多次可能会造成问题为好。我还在寻找对于问题的意见永久性的修复,目前我已经恢复到 DefaultContainer

UPDATE: Ok so just a heads up, I've started using the method accepted below, and while it has worked great so far, it didn't resolve my core problem. Turns out the new StructureMap.MVC5, based on StructureMap3, uses NestedContainers. Which scope their requests to the lifetime of the NestedContainer, regardless of the default being Transient. So when I requested HttpContextBase for the first time, it will then return that same instance for the rest of the request (even though later on in the request lifespan, the context has changed. You need to either not use NestedContainer (which, as I understand it will complicate things ASP.NET vNext), or you explicitly set the lifecycle of the For<>().Use<>() mapping to give you a new instance per request. Note that this scoping per NestedContainer causes problems with Controllers as well in MVC. While the StructureMap.MVC5 package handles this with a ControllerConvention, it does not handle Views, and recursive views or views used multiple times will likely cause you problems as well. I'm still looking for a permanent fix for the Views problem, for the moment I've reverted to the DefaultContainer.

推荐答案

我还没有与OWIN的工作,但在IIS集成模式下承载时的HttpContext未填充后才HttpApplication.Start事件完成。在DI而言,这意味着你可以不依赖于任何使用构造函数的HttpContext的属性。

I haven't worked with OWIN, but when hosting in IIS integrated mode the HttpContext is not populated until after the HttpApplication.Start event is complete. In terms of DI, this means that you cannot rely on using properties of HttpContext in any constructor.

这是有道理的,如果你仔细想想,因为应用程序应任何个人用户上下文之外进行初始化。

This makes sense if you think about it because the application should be initialized outside of any individual user context.

要解决这个问题,你可以注入一个抽象工厂到ICurrentUser实施和使用Singleton模式来访问它,它保证的HttpContext将不能访问它,直到填充

To get around this, you could inject an abstract factory into your ICurrentUser implementation and to use a Singleton pattern to access it, which guarantees HttpContext won't be accessed until it is populated.

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

就个人而言,我会做的用户ID和用户名的属性只读,这将简化设计,并确保它们不会在其他地方申请劫持。我也会使被注入ICurrentUser的构造,而不是在一个扩展方法检索的权利要求Id的一个IClaimsIdentityRetriever服务。扩展方法违背DI的粮食,一般只适用于保证不会有任何依存关系(如字符串或序列处理)的任务非常有用。使得松耦合它服务也意味着你可以很容易地交换或延长实施。

Personally, I would make the UserId and UserName properties readonly, which would simplify the design and ensure they don't get hijacked elsewhere in the application. I would also make an IClaimsIdentityRetriever service that is injected into the constructor of ICurrentUser instead of retrieving the claims Id in an extension method. Extension methods go against the grain of DI and are generally only useful for tasks that are guaranteed not to have any dependencies (such as string or sequence manipulation). The loose coupling of making it a service also means you can easily swap or extend the implementation.

当然,这意味着你不能打电话给你的currentUser类的用户ID或用户名的属性在任何构造为好。如果任何其他类依赖于ICurrentUser,您可能还需要为了安全地使用它的ICurrentUserFactory。

Of course, this implies that you cannot call the UserId or UserName properties of your CurrentUser class in any constructor as well. If any other class depends on ICurrentUser, you may also need an ICurrentUserFactory in order to safely use it.

抽象工厂是难以注入依赖交易和解决的问题,包括这一个主机时的救星。

Abstract factory is a lifesaver when dealing with difficult-to-inject dependencies and solves a host of problems including this one.

这篇关于在HttpContext的从StructureMap获得空用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆