c# - httpcontextwrapper - httpcontext current not found in.net core



StructureMapから取得したHttpContextのヌルユーザー (1)

私はOWINで作業していませんが、HttpApplication.Startイベントが完了するまでHttpContextにはIIS統合モードでホストされません。 DIの点では、任意のコンストラクタでHttpContextのプロパティを使用することに依存できないことを意味します。

これは、個々のユーザーコンテキストの外部でアプリケーションを初期化する必要があるため、考えてみると意味があります。

これを回避するには、抽象ファクトリをICurrentUser実装に挿入し、シングルトンパターンを使用してアクセスすることができます。これにより、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サービスを、拡張メソッドのクレームIDを取得する代わりに、ICurrentUserのコンストラクタに注入するようにします。 拡張メソッドはDIのグレインに逆らっており、通常は依存性がないことが保証されているタスク(文字列やシーケンスの操作など)にのみ有効です。 それをサービスにするという疎結合は、実装を簡単にスワップまたは拡張できることを意味します。

もちろん、これは、どのコンストラクタでもCurrentUserクラスのUserIdまたはUserNameプロパティを呼び出すことができないことを意味します。 他のクラスがICurrentUserに依存する場合は、ICurrentUserFactoryが安全に使用されるようにする必要があります。

抽象的なファクトリは、注入が困難な依存関係を扱う際のライフセーバーであり、これを含む多くの問題を解決します。

さて、私の以前の質問/セットアップには多すぎる変数があったので、これを裸の骨のコンポーネントに分解しています。

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

ウォッチウィンドウでこれはどのように可能ですか?

2.xからStructureMap 3.xを使用するようにアップグレードしているWebアプリケーションがありますが、特定の依存関係で奇妙な動作が発生しています。

ユーザーがページをリクエストしたときに取得するために使用するISecurityServiceがあります。 このサービスは、ICurrentUserと呼ばれる小さなインターフェイスに依存します。 クラスの実装はかなり単純ですが、実際には構造体になる可能性があります。

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

これは、以下のコードを使用して依存性注入によって取得されます。

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 。これはISecurityService依存しISecurityService 。 これはOWINの内部で発生し、 HttpContext.Userが生成される前に発生するように見えるので、nullです。

後で、ISecurityServiceを介して現行のユーザーがサイトのTermsOfUseの現行バージョンに同意したかどうかを確認するActionFilterがあります。そうでない場合は、最初に同意するページにリダイレクトされます。

これは構造マップ2.xでうまくいきました。 StructureMap3への移行のために、私は物事をスピードアップするためにStructureMap.MVC5というNugetパッケージをインストールしました。

私のコードが私のActionFilterの中で使用条件をチェックする行になると、私はこれを持っています。

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

CheckLoginAgreedToTermsOfUse()内部で、 CurrentUserインスタンスはnullです。 たとえそれが成功したとしても、getCurrentUser()の中のブレークポイントは決してヒットしないようです。 今回は解決されたにしても、前回はnullだったので、それはあくまで結論です。

getCurrentUser()ISecurityServiceの要求で呼び出されない理由について、私は戸惑うことがあります。 私は明示的に、 .LifecycleIs<UniquePerRequestLifecycle>()を処理するための私の接続に.LifecycleIs<UniquePerRequestLifecycle>()ICurrentUserました。

更新:それでは、頭を上げて、私は下で受け入れられた方法を使い始めました。これまでのところうまくいきましたが、それは私の中核的な問題を解決しませんでした。 StructureMap.MVC5に基づいて、新しいStructureMap.MVC5がNestedContainersを使用することがわかります。 デフォルトのTransientに関係なく、NestedContainerの存続期間に対する要求のスコープ だから私が最初にHttpContextBaseをリクエストしHttpContextBaseときに、リクエストの残りの部分で同じインスタンスが返されます(リクエストライフスパンで後でコンテキストが変更されたとしても)。NestedContainerを使用しないでくださいあなたがリクエストごとに新しいインスタンスを提供するためにFor<>().Use<>()マッピングをFor<>().Use<>() 。NestedContainerごとにこのスコープがコントローラに問題を引き起こすことに注意してくださいまた、 StructureMap.MVC5パッケージはControllerConventionでこれを処理しControllerConvention 、Viewsを処理しませんし、複数回使用される再帰的なビューやビューも問題を引き起こす可能性があります。私がDefaultContainer戻った瞬間、問題を表示します。





structuremap3