0

好的,我之前的问题/设置有太多变量,所以我将其剥离为它的基本组件。

鉴于下面使用 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;
    }
}

在“监视”窗口中这怎么可能?

在此处输入图像描述


我有一个 Web 应用程序,我正在从 2.x 升级到使用 StructureMap 3.x,但我在特定依赖项上出现了奇怪的行为。

我有一个 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. 这发生在 OWIN 内部,并且似乎在HttpContext.User填充之前发生,所以它是空的,就这样吧。

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

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

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

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

在里面CheckLoginAgreedToTermsOfUse(),我的实例CurrentUser是空的。即使它会成功,而且我在 getCurrentUser() 中的断点似乎永远不会被击中。这几乎就像已成定局,因为上次它是 null ,即使这次它会解决。

我有点困惑,为什么getCurrentUser()从来没有要求ISecurityService. 我什至尝试在我的连接上明确粘贴一个.LifecycleIs<UniquePerRequestLifecycle>()以进行处理ICurrentUser,但没有任何效果。

更新:好的,请注意,我已经开始使用下面接受的方法,虽然到目前为止效果很好,但它并没有解决我的核心问题。原来新的StructureMap.MVC5,基于StructureMap3,使用 NestedContainers。无论默认值是 Transient,它们的请求范围都是 NestedContainer 的生命周期。因此,当我第一次请求HttpContextBase时,它将为请求的其余部分返回相同的实例(即使在请求生命周期的后期,上下文已经改变。你需要要么不使用 NestedContainer (据我所知它会使事情复杂化 ASP.NET vNext),或者您明确设置For<>().Use<>()映射为每个请求提供一个新实例。请注意,每个 NestedContainer 的这个作用域会导致控制器以及 MVC 中的问题。虽然StructureMap.MVC5包用 处理这个ControllerConvention,但它不处理视图,递归视图或多次使用的视图也可能会给您带来问题。我仍在寻找 Views 问题的永久解决方案,目前我已恢复到DefaultContainer.

4

1 回答 1

5

我没有使用过 OWIN,但是在 IIS 集成模式下托管时,直到 HttpApplication.Start 事件完成后才会填充 HttpContext。就 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 服务,该服务被注入到 ICurrentUser 的构造函数中,而不是在扩展方法中检索声明 Id。扩展方法与 DI 背道而驰,通常只对保证没有任何依赖关系的任务有用(例如字符串或序列操作)。使其成为服务的松散耦合还意味着您可以轻松地交换或扩展实现。

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

在处理难以注入的依赖项时,抽象工厂是救命稻草,并解决了包括这个问题在内的许多问题。

于 2014-11-22T07:46:56.093 回答