简体   繁体   中英

SimpleInjector.ActivationException when using RegisterWebApiRequest in custom TypeFormatter

I use a custom JsonMediaTypeFormatter to extend data in a request. But when I use the GetInstance method, the formatter throws an exception. What am I doing wrong?

Global.asax:

// Create the container as usual.
var container = new Container();

// Register your types, for instance using the RegisterWebApiRequest
// extension from the integration package:
container.RegisterWebApiRequest<TestDataContext>();

// This is an extension method from the integration package.
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

container.Verify();

GlobalConfiguration.Configuration.DependencyResolver =
    new SimpleInjectorWebApiDependencyResolver(container);

// Here your usual Web API configuration stuff.
GlobalConfiguration.Configuration.Formatters.Add(new StringFormatter(container));

The formatter (simplified so it works with a new MVC Web Api Project):

public class StringFormatter : JsonMediaTypeFormatter
{
    private readonly Container _container;

    public StringFormatter(Container container)
    {
        _container = container;
        SupportedMediaTypes.Add(
            new MediaTypeHeaderValue("application/vnd.mywebapplication+json"));
    }

    public override bool CanReadType(Type type)
    {
        return false;
    }

    public override bool CanWriteType(Type type)
    {
        return (type == typeof (IEnumerable<string>) || type == typeof (string));
    }

    public override void WriteToStream(Type type, object value, Stream writeStream,
        System.Text.Encoding effectiveEncoding)
    {
        // Throws SimpleInjector.ActivationException
        var dataContext = _container.GetInstance<TestDataContext>(); 

        var result = dataContext.GetText();
        type = result.GetType();
        value = result;

        base.WriteToStream(type, value, writeStream, effectiveEncoding);
    }
}

This is the exception message:

The registered delegate for type TestDataContext threw an exception. The TestDataContext is registered as 'Web API Request' lifestyle, but the instance is requested outside the context of a Web API Request.

Bit messy stacktrace:

at SimpleInjector.InstanceProducer.GetInstance() 
at SimpleInjector.Container.GetInstance[TService]() 
at WebApplication4.Formatter.StringFormatter.WriteToStream(Type type, Object value, Stream writeStream, Encoding effectiveEncoding) in d:\UserProfiles\luuk\Documents\Visual Studio 2013\Projects\WebApplication4\WebApplication4\Formatter\StringFormatter.cs:line 35 
at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content) 
at System.Net.Http.Formatting.BaseJsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()"

at SimpleInjector.Scope.GetScopelessInstance[TService,TImplementation](ScopedRegistration'2 registration) 
at SimpleInjector.Scope.GetInstance[TService,TImplementation](ScopedRegistration'2 registration, Scope scope) 
at SimpleInjector.Advanced.Internal.LazyScopedRegistration'2.GetInstance(Scope scope) 
at DynamicInstanceProducer2.GetInstance(Object[] constants) 
at SimpleInjector.CompilationHelpers.<>c__DisplayClass1b'1.

After some debugging I found out that there is something odd going on in how Web API handles TypeFormatter s. It calls the WriteToStream after the IDependencyScope for the request is created, but before that scope is disposed. But although the WriteToStream is called in between the start and the end of the IDependencyScope , it is NOT called in the same Execution Context as the rest of the Web API request.

This means that what Simple Injector is concerned, there is no request at that point in time, since Simple Injector uses .NET's Execution Context to keep track of the SimpleInjector.Scope class.

This might be a bug in Web API, but it is probably something that is by-design in Web API. Perhaps the reasoning is that a type formatter just contains basic logic, and no interaction with the current request or some backend database. That sounds reasonable to me, but doesn't solve your problem.

An easy way to solve this is by explicitly creating an ExecutionContextScope :

public override void WriteToStream(Type type, object value, Stream writeStream,
    System.Text.Encoding effectiveEncoding)
{
    using (container.BeginExecutionContextScope())
    {
        var dataContext = _container.GetInstance<TestDataContext>(); 

        var result = dataContext.GetText();
        type = result.GetType();
        value = result;

        base.WriteToStream(type, value, writeStream, effectiveEncoding);
    }
}

The WebApiRequestLifestyle is in fact just a practical wrapper around the ExecutionContextScopeLifestyle and just looks for an active ExecutionContextScope . So by explictly starting such a scope (using BeginExecutionContextScope ) you can make sure there is a scope from what the TestDataContext can be resolved.

I think you'll the same trouble with other DI frameworks. Either those frameworks will use the execution context as well, or they allow you to pass on the active resolution scope onto the caller, both of which are not possible in this approach. Some containers however will make you think that resolving the context will work, while in fact they return a singleton instance when there's no scope. Using a singleton data context is of course a very bad thing and your application will break horribly. But it will of course only break in production, because that's the thin with concurrency bugs.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM