如何在非控制器对象中提供ASP核心声明主体?

时间:2018-04-11 20:59:56

标签: authentication asp.net-core dependency-injection jwt

我正在使用JWT身份验证和Dapper ORM开发ASP Core 2项目。

与所有ASP项目一样,我有很多控制器,每个控制器都实例化其关联的数据对象。每个数据对象都继承自提供数据库访问服务的抽象DbObject类。我还有一个AuthenticatedUser对象,它抽象JWT使它的属性更容易使用。

我想要做的是在DbObject的构造函数中创建AuthenticatedUser对象。当然,一种方法是在控制器中创建它并将其传递给每个具体的数据对象,但这很麻烦,因为它必须传递数百次(而且感觉不对)。

有没有办法在身份验证后使用ASP Core中间件获取令牌,并通过DbObject中的依赖注入使其可用?

编辑 希望这能澄清我的意图。我希望控制器创建数据对象并使用它们的属性和方法而不考虑实现(即DbObject)。但是,DbObject执行的查询将被登录用户的令牌中的信息过滤。

public class ManufacturerController : Controller {

    [HttpGet]
    public async Task<IActionResult> Get() {
        var manufacturers = await new Manufacturer().SelectMany();
        return Ok(manufacturers);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id) {
        var manufacturer = await new Manufacturer().SelectOne(id);
        return Ok(manufacturer);
    }...

public class Manufacturer : DbObject<Manufacturer> {

    protected override string QrySelectOne => @"  
        Select * 
        From org.fn_Manufacturers ({0}) 
        Where Id = {1}";

    protected override string QrySelectMany => @" 
        Select * 
        From org.fn_Manufacturers ({0})";

    public int Id { get; set; }
    public string Name { get; set; }
    public string Phone { get; set; }...

public abstract class DbObject<T> {

    protected readonly AuthenticatedUser authenticatedUser;

    public DbObject(IHttpContextAccessor contextAccessor) {
        authenticatedUser = new 
            AuthenticatedUser(contextAccessor.HttpContext.User);
    }

    protected abstract string QrySelectOne { get; }
    protected abstract string QrySelectMany { get; }

    public async Task<T> SelectOne (int id) {...}
    public async Task<T> SelectOne(params object[] ids) {...}

    public async Task<IEnumerable<T>> SelectMany () {...}
    public async Task<IEnumerable<T>> SelectMany (params object[] ids) {...}

我想一个解决方案可能是创建一个注入了IHttpContextAccessor的静态数据对象工厂?

1 个答案:

答案 0 :(得分:0)

ASP.NET Core提供了IHttpContextAccessor接口,用于从非控制器对象访问HttpContext

用法很简单。将IHttpContextAccessor注入DbObject并通过调用HttpContext访问IHttpContextAccessor.HttpContext

public abstract class DbObject
{
    protected DbObject(IHttpContextAccessor contextAccessor)
    {
        var context = contextAccessor.HttpContext;

        //  Create instance of AuthenticatedUser based on context.User or other request data
    }
}

修改

您的控制器直接实例化数据对象(使用new运算符),这就是您不能开箱即用IHttpContextAccessor的原因。这是可能的解决方案。我按照我的偏好(从最好到最差)列出它们。

  1. 如果每个控制器只使用一种(或几种)数据对象,最好的选择是避免直接实例化并转向正常的依赖注入。

    因此,如果ManufacturerController只需要样本中的Manufacturer,那么最好将Manufacturer实例注入控制器,而不是在内部创建:

    public class Manufacturer1Controller : Controller
    {
        private readonly Manufacturer manufacturer;
    
        public Manufacturer1Controller(Manufacturer manufacturer)
        {
            this.manufacturer = manufacturer ?? throw new ArgumentNullException(nameof(manufacturer));
        }
    
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var manufacturers = await manufacturer.SelectMany();
            return Ok(manufacturers);
        }
    
        //  ...
    }
    

    IHttpContextAccessor会被注入Manufacturer并传递给基地DbObject

    public class Manufacturer : DbObject<Manufacturer>
    {
        public Manufacturer(IHttpContextAccessor contextAccessor) : base(contextAccessor)
        {
        }
    }
    

    这是清单中最干净的解决方案。您以经典方式使用DI并使用所有benefits DI provides

  2. 如果一个控制器可以使用许多不同的数据对象,则可以注入创建数据对象实例的工厂对象。它可以是基于IServiceProvider的简单实现:

    public interface IDbObjectFactory
    {
        TDbObject Create<TDbObject>() where TDbObject : DbObject<TDbObject>;
    }
    
    public class DbObjectFactory : IDbObjectFactory
    {
        private readonly IServiceProvider serviceProvider;
    
        public DbObjectFactory(IServiceProvider serviceProvider)
        {
            this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
        }
    
        public TDbObject Create<TDbObject>() where TDbObject : DbObject<TDbObject>
        {
            return serviceProvider.GetRequiredService<TDbObject>();
        }
    }
    
    public class Manufacturer2Controller : Controller
    {
        private readonly IDbObjectFactory dbObjectFactory;
    
        public Manufacturer2Controller(IDbObjectFactory dbObjectFactory)
        {
            this.dbObjectFactory = dbObjectFactory ?? throw new ArgumentNullException(nameof(dbObjectFactory));
        }
    
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var manufacturer = dbObjectFactory.Create<Manufacturer>();
            var manufacturers = await manufacturer.SelectMany();
            return Ok(manufacturers);
        }
    }
    

    ManufacturerDbObject的代码与第一个选项相比没有变化。

    我认为没有任何理由不使用选项#1或#2。然而,为了完成图片,我将描述另外两个选项。

  3. IHttpContextAccessor注入到conroller中,并将此实例(或IHttpContextAccessor.HttpContext.User)传递给使用运算符new调用的数据对象构造函数:

    public class Manufacturer3Controller : Controller
    {
        private readonly IHttpContextAccessor contextAccessor;
    
        public Manufacturer3Controller(IHttpContextAccessor contextAccessor)
        {
            this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
        }
    
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var manufacturer = await new Manufacturer(contextAccessor).SelectMany();
            //  or
            //  var manufacturer = await new Manufacturer(contextAccessor.HttpContext.User).SelectMany();
            return Ok(manufacturer);
        }
    }
    

    这是一个糟糕的解决方案,因为您不在Manufacturer使用依赖注入,而是松散了许多advantages that DI provides

  4. 最糟糕的选择是使用注入IHttpContextAccessor的静态对象工厂。通过这种方法,您也可以放弃DI的好处。此外,您在Startup中的某个地方获得了丑陋的代码,用于初始化IHttpContextAccessor的静态实例。当你采用这种方法时,你会发现这不是很优雅的方法。

  5. 我的建议:使用选项#1,直到你有充分的理由反对它。然后使用选项#2。

    以下是Sample Project on GitHub,其中包含方法## 1-3的样本。