0

Хорошо, у меня в предыдущем вопросе/настройке было слишком много переменных, поэтому я разделяю это на его компоненты с голыми костями.Null User on HttpContext, полученный из StructureMap

Учитывая приведенный ниже код, используя 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; 
    } 
} 

Как это возможно в окне Watch?

enter image description here


У меня есть веб-приложение, которое я модернизирует к использованию StructureMap 3.x от 2.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, поэтому оно равно null, так и должно быть.

Позже у меня есть ActionFilter, который проверяет через ISecurityService, если текущий пользователь согласился с текущей версией TermsOfUse для сайта, если они не перенаправлены на страницу, чтобы сначала согласиться с ними.

Все это прекрасно работало в структуре map 2.x. Для моей миграции в StructureMap3 я установил пакет Nuget StructureMap.MVC5, чтобы ускорить работу для меня.

Когда мой код попадает в строку моего ActionFilter для проверки условий использования, у меня есть это.

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

Внутри CheckLoginAgreedToTermsOfUse(), мой экземпляр CurrentUser является недействительным. Несмотря на то, что это было бы заметно, и моя точка останова внутри getCurrentUser() никогда не попадает. Это почти так, как если бы это было предрешено, так как в последний раз это было нулевым, хотя на этот раз оно разрешилось бы.

Я как бы не понял, почему getCurrentUser() никогда не вызывается по запросу ISecurityService. Я даже попытался явно наклеить .LifecycleIs<UniquePerRequestLifecycle>() на мой крючок для обработки ICurrentUser без эффекта.

UPDATE: Хорошо, так что только хэдз-ап, я начал использовать метод, принятый ниже, и пока он отлично работает, он не разрешил мою основную проблему. Оказывается, новый StructureMap.MVC5, основанный на StructureMap3, использует NestedContainers. Какая область их запросов на время жизни NestedContainer, независимо от того, по умолчанию является Transient. Поэтому, когда я впервые обратился к HttpContextBase, он вернет тот же самый экземпляр для остальной части запроса (хотя позже в течение срока действия запроса контекст изменился. Вам нужно либо не использовать NestedContainer (который, как я понять это усложнит ситуацию ASP.NET vNext), или вы явно задали жизненный цикл отображения For<>().Use<>(), чтобы предоставить вам новый экземпляр для каждого запроса. Обратите внимание, что эта область видимости для NestedContainer вызывает проблемы с контроллерами, а также в MVC. В то время как пакет StructureMap.MVC5 обрабатывает это с помощью ControllerConvention, он не обрабатывает представления, а рекурсивные виды или представления, используемые несколько раз, также могут вызвать проблемы. Я все еще ищу постоянное исправление проблемы Views, на данный момент я вернусь к DefaultContainer.

+0

Не могли бы вы вывести код для вашей реализации ISecurityService? – NightOwl888

+0

У меня было слишком много переменных, которые могли бы привести к возможной помощи по неправильному пути. Теперь я упростил вопрос о проблеме с голой костью. –

ответ

4

Я не работал с OWIN, но при размещении в интегрированном режиме IIS HttpContext не заполняется до завершения события HttpApplication.Start. В терминах 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 вместо того, чтобы извлекать идентификатор требований в методе расширения. Методы расширения идут вразрез с зерном DI и обычно полезны только для задач, которые гарантированно не имеют каких-либо зависимостей (например, строковые или последовательные манипуляции). Свободное соединение, предоставляющее ему услугу, также означает, что вы можете легко поменять или расширить реализацию.

Конечно, это означает, что вы не можете вызывать свойства UserId или UserName вашего класса CurrentUser в любом конструкторе. Если какой-либо другой класс зависит от ICurrentUser, вам также может понадобиться ICurrentUserFactory, чтобы безопасно его использовать.

Аннотация Завод является спасателем при работе с трудными для инъекций зависимостями и решает множество проблем, включая этот.

+0

Интересно, это то, о чем я не знал о приложениях с интегрированным режимом. Я по-прежнему не уверен, почему StructureMap, похоже, ведет себя так, как в моем примере, но ваше предположение об использовании комбинации фабрики с одноконтактным httpcontext и свойствами readonly выглядит так, как будто это решит мою проблему. Также имеет смысл, учитывая некоторые другие части моего приложения и то, как они себя ведут, принять тот же шаблон в другом месте. Фантастический ответ за то, что я искал. Благодаря :-) –

Смежные вопросы