【问题标题】:How to inject HttpContextAccessor directly from ConfigureServices method如何直接从 ConfigureServices 方法注入 HttpContextAccessor
【发布时间】:2020-05-26 01:03:37
【问题描述】:

我的目标是根据我将要处理的环境设置用户名字符串:

  • 开发和登台环境的任意字符串
  • HttpContext.User.Identity.Name 正在生产中。

这是因为我必须能够模拟不同类型的用户,我通过在 UserIdentity 的自定义实现上使用此用户名字符串作为参数调用 FindByIdAsync 方法来实现这一点,如下所示:

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"
}
...

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

我已经创建了一个简单的界面

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

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

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

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

}

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

【问题讨论】:

    标签: asp.net-core dependency-injection


    【解决方案1】:

    HttpContextAccessor 在幕后利用了静态AsyncLocal&lt;T&gt; 属性,这意味着任何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));
        }
    }
    

    【讨论】:

    • 我非常喜欢您拆分 ContextUser 类的替代解决方案,这就是我所做的。谢谢。
    猜你喜欢
    • 2017-05-15
    • 2018-03-07
    • 1970-01-01
    • 2019-10-25
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    • 2020-05-29
    • 1970-01-01
    相关资源
    最近更新 更多