문제

I have an MVC application in which I have a controller that receives data from the user and then uploads a file to Azure blob storage. The application is using Unity IoC to handle dependency injection.

During the workflow I have isolated the following code as demonstrating the problem

public class MvcController : Controller
{
    private IDependencyResolver _dependencyResolver;

    public MvcController() : this(DependencyResolver.Current)
    {
    }

    public MvcController(IDependencyResolver dependencyResolver)
    {
        this._dependencyResolver = dependencyResolver;
    }

    public GetService<T>()
    {
        T resolved = _dependencyResolver.GetService<T>()
        if (resolved == null)
            throw new Exception(string.Format("Dependency resolver does not contain service of type {0}", typeof(T).Name));
        return resolved;
    }
}
public class MyController : MvcController
{
    [NoAsyncTimeout]
    public async Task<ActionResult> SaveFileAsync(/* A bunch of arguments */)
    {
        /* A bunch of code */

        //This line gets a concrete instance from HttpContext.Current successfully...
        IMyObject o = GetService<IMyObject>();

        await SaveFileToAzure(/* A bunch of parameters */);
        .
        .
        /* Sometime later */
        Method2(/* A bunch of parameters */);
    }

    private Method2(/* A bunch of parameters */)
    {
        //This line fails because HttpContext.Current is null
        IMyObject o = GetService<IMyObject>();

        /* A bunch of other code */
    }

    private async Task SaveFileToAzure(/* A bunch of parameters */)
    {
        //Grab a blob container to store the file data...
        CloudBlobContainer blobContainer = GetBlobContainer();
        ICloudBlob blob = blobContainer.GetBlockBlobReference(somePath);

        Stream dataStream = GetData();
        System.Threading.CancellationToken cancelToken = GetCancellationToken();

        //All calls to DependencyResolver.GetService<T>() after this line of code fail...
        response = await blob.UploadStreamAsync(dataStream, cancelToken);

        /* A bunch of other code */
    }
}

Unity has a registration for my object:

container.RegisterType<IMyObject, MyObject>(new HttpLifetimeManager());

My lifetime manager is defined as follows:

public sealed class HttpRequestLifetimeManager : LifetimeManager
{
    public Guid Key { get; private set; }

    public HttpRequestLifetimeManager()
    {
        this.Key = Guid.NewGuid();
    }

    public override object GetValue()
    {
        return HttpContext.Current.Items[(object)this.Key];
    }

    public override void SetValue(object newValue)
    {
        HttpContext.Current.Items[(object)this.Key] = newValue;
    }

    public override void RemoveValue()
    {
        HttpContext.Current.Items.Remove((object)this.Key);
    }
}

Nothing complicated.

Stepping into the HttpRequestLifetimeManager on the failing GetService() calls shows that after the UploadStreamAsync() call HttpContext.Current is null...

Has anyone else come across this problem? If so, is this a bug? Is this expected behaviour? Am I doing something out of the ordinary? What should I do to resolve it?

I can hack around it by storing a reference to HttpContext.Current prior to the offending call and restoring it after, but that doesn't seem like the right approach.

Any ideas?

도움이 되었습니까?

해결책

To echo @Joachim - http context may not be available to your async thread. Compare the current thread id where you can see httpcontext is available, to the thread id where you can see that it isn't - i'm assuming you will see they are 2 different threads. If my assumption is correct this may be a sign that your main thread (the one with httpcontext) does not have a "synchronizationcontext". (you can see http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx for more details of how that works) If so, it may mean that the code immediately after your await statement is actually not running on the same thread as the code prior to the await statement! So from your perspective, one moment you have http context and the next you don't because execution has actually been switched to another thread! You should probably look at implementing / setting a synchronizationcontext on your main thread if that's the case and then control will be returned to your original thread with http context and that should fix your problem, or alternatively you could retrieve your object from http context on the original thread and find a way to pass it as a parameter to the async method/s so that they don't need to access http context to get their state.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top