4

My scenario: My application is a Web Api 2 app using a business logic and repository layer for data access. The web application uses ASP.NET Impersonation to login to the database as the user accessing the website (authenticated via PKI). I have several async controller methods. However, when I await on the data access methods, the database call may complete on a different thread which will then access the database under the identity of my application pool which is not allowed to connect to the database.

Example Controller:

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

Example Business Logic Class:

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

If I move the using statement into the controller (wrapped around the await), it works fine.

The issue seems to be that because the await is outside of the impersonation context, it does not impersonate the database call (the CountAsync()) and I am unable to open a connection to my database.

The Question:

Is there a way I could write an ActionFilter or some other attribute on my controller method so that the method itself (containing the await call) would be automatically wrapped in the using statement?

Adam Modlin
  • 2,994
  • 2
  • 22
  • 39

3 Answers3

2

merpmerp's answer is going to be problematic in multi-threaded servers. Since there is only one instance of an ActionFilterAttribute per decorated method, two simultaneous requests to the same method will result in usingVariable being overwritten, and only one will end up being disposed.

You'll need to take this a step further and store the ImpersonationContext somewhere in the request context-- e.g. in filterContext.Request.Properties.

bdhess
  • 628
  • 3
  • 6
1

I don't believe there is a way to actually wrap a method in a using statement with an attribute, but you could essentially do the same thing by using the OnActionExecuting and OnResultExecuted methods in a custom ActionFilter.

public class IdentityImpersonateActionFilter : ActionFilterAttribute
{
    IDisposable usingVaribale;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        usingVaribale.Dispose();
    }
}

Then you could decorate your methods or your whole controller class with [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();  
}

You could also access this using variable in your function if you wish

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    usingVaribale = WindowsIdentity.GetCurrent().Impersonate();
    filterContext.ActionParameters.Add("parameterName", usingVaribale);
}

And add the parameter to your controller function

[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();  
}

Hope this helps!

merpmerp
  • 86
  • 3
0

If you want to keep the impersonation the responsibility of the business logic, then you can just do this:

public async Task<int> CountMyJobsAsync(string name)
{
    using (WindowsIdentity.GetCurrent().Impersonate())
    {
        return await _jobRepository.Where(i => i.Name == name).CountAsync()
            .ConfigureAwait(false);
    }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810