如何直接从ConfigureServices方法注入HttpContextAccessor

Dav*_*ali 3 dependency-injection asp.net-core

我的目标是根据我将要工作的环境设置用户名字符串,该字符串必须是:

  • 用于开发和登台环境的任意字符串
  • 正在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

appsettings.{environment}.json我开始为三个常用的开发、登台和生产环境创建三个文件;development 和 staging .json 文件都具有以下配置:

...
"Data": {
  ...
  "ConnectedUser" : "__ADMIN"
}
...
Run Code Online (Sandbox Code Playgroud)

而生产环境配置文件中没有这个key。

我创建了一个简单的界面

public interface IContextUser
{
    public string ContextUserId { get; }
}
Run Code Online (Sandbox Code Playgroud)

及其实施:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,我想到简单地配置类ConfigureServices中的方法Startup

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

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

}
Run Code Online (Sandbox Code Playgroud)

但它需要一个IHttpContextAccessor对象,在应用程序的这个阶段似乎不可用。我该如何解决这个问题?

Ste*_*ven 8

它在幕后HttpContextAccessor使用静态属性,这意味着任何实现都将访问相同的数据。这意味着您可以简单地执行以下操作: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();
Run Code Online (Sandbox Code Playgroud)

HttpContextAccessor如果您发现这太隐含了,或者将来不会破坏实现,您还可以执行以下操作:

var accessor = new HttpContextAccessor();

services.AddSingleton<IHttpContextAccessor>(accessor);
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
Run Code Online (Sandbox Code Playgroud)

或者您可以将注册的实例从类中“拉出” ServiceCollection

services.AddHttpContextAccessor();

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

services.AddSingleton(c => new ContextUser(accessor, ctxUser));
Run Code Online (Sandbox Code Playgroud)

然而,我发现一个更令人愉快的解决方案,特别是从设计角度来看,是拆分类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; }
}
Run Code Online (Sandbox Code Playgroud)

现在,根据您运行的环境,您可以注册其中之一:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我非常喜欢您拆分 ContextUser 类的替代解决方案,这就是我所做的。谢谢。 (2认同)