如何使 ASP 核心声明主体在非控制器对象中可用?



我正在使用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的静态数据对象工厂?

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

用法相当简单。通过调用IHttpContextAccessor.HttpContextIHttpContextAccessor注入DbObject和访问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,并利用 DI 提供的所有优势。

  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注入到控制器中,并将此实例(或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,并且失去了 DI 提供的许多优势。

  4. 最糟糕的选择是使用带有注入IHttpContextAccessor的静态对象工厂。使用这种方法,您还可以失去 DI 的好处。此外,您会在Startup某处获得初始化IHttpContextAccessor静态实例的丑陋代码。当你来到这种方法时,你会发现你并不是一个相当优雅的方法。

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

这是GitHub 上的示例项目,其中包含方法 ##1-3 的示例。

最新更新