我的方案:我的应用程序是一个Web Api 2应用程序,它使用业务逻辑和存储库层进行数据访问。 Web应用程序使用ASP.NET Impersonation以访问网站的用户身份登录数据库(通过PKI进行身份验证)。我有几个异步控制器方法。但是,当我在数据访问方法上await
时,数据库调用可能会在另一个线程上完成,然后该线程将以我的应用程序池的身份访问数据库,该数据库不允许连接到数据库。
示例控制器:
public class TestApiController : ApiController {
private IBusinessLogicObject _myBlObject;
public TestApiController(IBusinessLogicObject myBlObject){
_myBlObject = myBlObject; //Populated through Unity
}
public async Task<int> CountMyJobs(string name){
return await _myBlObject.CountMyJobsAsync(name);
}
}
业务逻辑类示例:
public class BusinessLogicObject : IBusinessLogicObject
{
private IGenericRepository<Job> _jobRepository;
public BusinessLogicObject(IGenericRepository<Job> _jobRepository)
{
_jobRepository = jobRepository; //Populated with Unity
}
public Task<int> CountMyJobsAsync(string name)
{
using (WindowsIdentity.GetCurrent().Impersonate())
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
}
}
如果我将using
语句移动到控制器中(包含在await中),它可以正常工作。
问题似乎是因为await
在模拟上下文之外,它没有模拟数据库调用(CountAsync()
),我无法打开与数据库的连接。
问题:
有没有办法在我的控制器方法上编写ActionFilter
或其他属性,以便方法本身(包含await调用)会自动包含在using语句中?
答案 0 :(得分:2)
merpmerp的答案在多线程服务器中会出现问题。由于每个修饰方法只有一个ActionFilterAttribute实例,因此对同一方法的两个同时请求将导致 usingVariable 被覆盖,并且只有一个将最终被处理。
您需要更进一步,将ImpersonationContext存储在请求上下文中的某个位置 - 例如在filterContext.Request.Properties中。
答案 1 :(得分:1)
我不相信有一种方法可以在带有属性的using语句中实际包装方法,但是在自定义ActionFilter中使用OnActionExecuting和OnResultExecuted方法基本上可以做同样的事情。
public class IdentityImpersonateActionFilter : ActionFilterAttribute
{
IDisposable usingVaribale;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
usingVaribale.Dispose();
}
}
然后,您可以使用[IdentityImpersonate]
[IdentityImpersonate]
public Task<int> CountMyJobsAsync(string name)
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
如果您愿意,也可以使用函数中的变量
来访问它public override void OnActionExecuting(ActionExecutingContext filterContext)
{
usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
filterContext.ActionParameters.Add("parameterName", usingVaribale);
}
并将参数添加到控制器功能
[IdentityImpersonate]
public Task<int> CountMyJobsAsync(object parameterName, string name)
{
//JobRepository is effectively a DbSet<Job> and this call returns IQueryable<Job>
return _jobRepository.Where(i => i.Name == name).CountAsync();
}
希望这有帮助!
答案 2 :(得分:0)
如果您希望将模拟作为业务逻辑的责任,那么您可以这样做:
public async Task<int> CountMyJobsAsync(string name)
{
using (WindowsIdentity.GetCurrent().Impersonate())
{
return await _jobRepository.Where(i => i.Name == name).CountAsync()
.ConfigureAwait(false);
}
}