Как ввести HttpContextAccessor прямо из метода ConfigureServices - PullRequest
1 голос
/ 25 мая 2020

Моя цель - установить строку имени пользователя в зависимости от среды, над которой я буду работать, которая должна быть:

  • произвольной строкой для среды разработки и тестирования
  • HttpContext.User.Identity.Name в производстве.

Это связано с тем, что я должен иметь возможность имитировать различные типы пользователей, и я добиваюсь этого, вызывая метод FindByIdAsync в моей пользовательской реализации UserIdentity, используя этот строка имени пользователя в качестве параметра, например:

public class HomeController : Controller
{
    UserManager<AppUser> userManager;
    AppUser connectedUser;

    public HomeController(UserManager<AppUser> usrMgr, IContextUser ctxUser)
    {
        connectedUser = usrMgr.FindByNameAsync(ctxUser.ContextUserId).Result;
    }
}

Я начал создавать три файла appsettings.{environment}.json для трех обычных сред разработки, подготовки и производства; development и staging. json файлы имеют следующую конфигурацию:

...
"Data": {
  ...
  "ConnectedUser" : "__ADMIN"
}
...

в то время как файл конфигурации производственной среды не имеет этого ключа.

Я создал простой интерфейс

public interface IContextUser
{
    public string ContextUserId { get; }
}

и его реализация:

public class ContextUser : IContextUser
{
    string contextUser;
    IHttpContextAccessor contextAccessor;

    public ContextUser(IHttpContextAccessor ctxAccessor, string ctxUser = null)
    {
        contextUser = ctxUser;
        contextAccessor = ctxAccessor;
    }

    public string ContextUserId => contextUser ?? contextAccessor.HttpContext.User.Identity.Name;
}

Теперь я подумал о простой настройке метода ConfigureServices в классе Startup:

public void ConfigureServices(IServiceCollection services)
{
    // --- add other services --- //

    string ctxUser = Configuration["Data:ConnectedUser"];
    services.AddSingleton(service => new ContextUser( ??? , ctxUser ));

}

, но ему нужен объект IHttpContextAccessor, который кажется недоступным на данном этапе приложения. Как я могу решить эту проблему?

1 Ответ

2 голосов
/ 25 мая 2020

HttpContextAccessor использует скрытое свойство stati c AsyncLocal<T>, что означает, что любая реализация HttpContextAccessor будет обращаться к одним и тем же данным. Это означает, что вы можете просто сделать следующее:

services.AddSingleton(c => new ContextUser(new HttpContextAccessor(), ctxUser));

// Don't forget to call this; otherwise the HttpContext property will be
// null on production.
services.AddHttpContextAccessor();

Если вы обнаружите, что это слишком неявно, или если реализация HttpContextAccessor не сломается в будущем, вы также можете сделать следующее:

var accessor = new HttpContextAccessor();

services.AddSingleton<IHttpContextAccessor>(accessor);
services.AddSingleton(c => new ContextUser(accessor, ctxUser));

Или вы можете «вытащить» зарегистрированный экземпляр из класса ServiceCollection:

services.AddHttpContextAccessor();

var accessor = (IHttpContextAccessor)services.Last(
    s => s.ServiceType == typeof(IHttpContextAccessor)).ImplementationInstance;

services.AddSingleton(c => new ContextUser(accessor, ctxUser));

Однако я считаю более приятным решение, особенно с точки зрения дизайна, разделить класс ContextUser; в настоящее время он, кажется, реализует два разных решения. Вы можете разделить их:

public sealed class HttpContextContextUser : IContextUser
{
    private readonly IHttpContextAccessor accessor;

    public HttpContextContextUser(IHttpContextAccessor accessor) =>
        this.accessor = accessor ?? throw new ArgumentNullException("accessor");

    public string ContextUserId => this.accessor.HttpContext.User.Identity.Name;
}


public sealed class FixedContextUser : IContextUser
{
    public FixedContextUser(string userId) =>
        this.ContextUserId = userId ?? throw new ArgumentNullException("userId");

    public string ContextUserId { get; }
}

Теперь, в зависимости от среды, в которой вы работаете, вы регистрируете любой из них:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();

    if (this.Configuration.IsProduction())
    {
        services.AddSingleton<IContextUser, HttpContextContextUser>();
    }
    else
    {
        string ctxUser = Configuration["Data:ConnectedUser"];
        services.AddSingleton<IContextUser>(new FixedContextUser(ctxUser));
    }
}
...