我正在使用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.HttpContext
将IHttpContextAccessor
注入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
。以下是可能的解决方案。我按照我的偏好顺序列出它们(从最好到最差)。
如果每个控制器仅使用一种(或仅几种)类型的数据对象,则最佳选择是避免直接实例化并转向正常的依赖项注入。
因此,如果
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 提供的所有优势。
如果一个控制器可以使用数十个不同的数据对象,则可以注入创建数据对象实例的工厂对象。它可以是基于
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); } }
与第一个选项相比,
Manufacturer
和DbObject
的代码没有变化。我认为没有任何理由不使用选项 #1 或 #2。但是,为了完成图片,我将描述另外两个选项。
将
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 提供的许多优势。最糟糕的选择是使用带有注入
IHttpContextAccessor
的静态对象工厂。使用这种方法,您还可以失去 DI 的好处。此外,您会在Startup
某处获得初始化IHttpContextAccessor
静态实例的丑陋代码。当你来到这种方法时,你会发现你并不是一个相当优雅的方法。
我的建议:使用选项 #1,直到你有充分的理由反对它。然后使用选项 #2。
这是GitHub 上的示例项目,其中包含方法 ##1-3 的示例。