1

I have a question about creating a temporary request scope when using the Web API OWIN pipeline with Autofac.

We have the need to disable some external dependencies on demand so our QA team can test their negative test cases. I did not want to change ANY code in the normal application flow, so what I did was create a custom middleware that inspects a request for certain QA headers, and when they are present extends the normal container with a temporary new scope, registers a replacement object only for that call, overrides the autofac:OwinLifetimeScope, then disposes that temporary scope at the end of that call.

This has allowed me to override the normal container behaviour for that request only, but allow all other requests to continue as normal.

Here is a modified sample of my middleware. This code is working fully as expected.

public override async Task Invoke(IOwinContext context)
{
    var headerKey = ConfigurationManager.AppSettings["QaTest.OfflineVendors.HeaderKey"];

    if (headerKey != null && context.Request.Headers.ContainsKey(headerKey))
    {
        var offlineVendorString = context.Request.Headers[headerKey].ToUpper(); //list of stuff to blow up

        Action<ContainerBuilder> qaRegistration = builder =>
        {
            if (offlineVendorString.Contains("OTHERAPI"))
            {
                var otherClient = new Mock<IOtherClient>();
                otherClient.Setup(x => x.GetValue()).Throws<APIServiceUnavailableException>();
                builder.Register(c => otherClient.Object).As<IOtherClient>();
            }
        };

        using (
            var scope =
                context.GetAutofacLifetimeScope()
                    .BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag, qaRegistration))
        {
            var key = context.Environment.Keys.First(s => s.StartsWith("autofac:OwinLifetimeScope"));
            context.Set(key, scope);

            await this.Next.Invoke(context).ConfigureAwait(false);
        }
    }
    else
    {
        await this.Next.Invoke(context).ConfigureAwait(false);
    }
}

However, the lines

var key = context.Environment.Keys.First(s => s.StartsWith("autofac:OwinLifetimeScope"));
context.Set(key, scope);

seem very hacky and I don't like them. I have searched all around, but I have not found a way to cleanly override the context object, or found a better way to implement this functionality.

I'm looking for any suggestions for a better way to handle this.

Ken Hundley
  • 3,833
  • 1
  • 13
  • 8
  • Also, this middleware is only registered when the app is deployed to a QA environment, and is never registered in production. So this code can never be executed in a prod. – Ken Hundley Nov 11 '16 at 00:35

1 Answers1

2

I can see two ways of achieving what you want to.

1. Dynamic registration

The first possibility is to mimic what Autofac itself does to inject the current HttpRequestMessage when integrated with ASP.NET Web API.

You can have a look at how it's done here. What it does is create another ContainerBuilder, registers the desired type, and calls the Update method on the lifetime scope's ComponentRegistry.

Applied to your scenario, it could look something like:

public override Task Invoke(IOwinContext context)
{
    var headerKey = ConfigurationManager.AppSettings["QaTest.OfflineVendors.HeaderKey"];
    if (headerKey != null && context.Request.Headers.ContainsKey(headerKey))
    {
        // Not sure how you use this, I assume you took it out of the logic
        var offlineVendorString = context.Request.Headers[headerKey].ToUpper(); //list of stuff to blow up

        // Get Autofac's lifetime scope from the OWIN context and its associated component registry
        // GetAutofacLifetimeScope is an extension method in the Autofac.Integration.Owin namespace
        var lifetimeScope = context.GetAutofacLifetimeScope();
        var componentRegistry = lifetimeScope.ComponentRegistry;

        // Create a new ContainerBuilder and register your mock
        var builder = new ContainerBuilder();
        var otherClient = new Mock<IOtherClient>();
        otherClient.Setup(x => x.GetValue()).Throws<APIServiceUnavailableException>();
        builder.Register(c => otherClient.Object).As<IOtherClient>();

        // Update the component registry with the ContainerBuilder
        builder.Update(componentRegistry);
    }

    // Also no need to await here, you can just return the Task and it'll
    // be awaited somewhere up the call stack
    return this.Next.Invoke(context);
}

Warning: Even though Autofac itself uses dynamic registration after the container has been built in the example above, the Update method on ContainerBuilder is marked as obsolete with the following message - spanned across several lines for readability:

Containers should generally be considered immutable.
Register all of your dependencies before building/resolving.
If you need to change the contents of a container, you technically should rebuild the container.
This method may be removed in a future major release.

2. Conditional registration

There's 2 drawbacks to the first solution:

  • it uses an obsolete method that could be removed
  • it involves conditional registration of the OWIN middleware so that it's only applied in the QA environment

Another way would be to register IOtherClient per-request. Since the Autofac OWIN integration registers the OWIN context in the lifetime scope - as you can see here, you could determine for each request which instance of IOtherClient you want to register.

It could look something like:

var headerKey = ConfigurationManager.AppSettings["QaTest.OfflineVendors.HeaderKey"];
if (CurrentEnvironment == Env.QA && !string.IsNullOrEmpty(headerKey))
{
    builder
        .Register(x =>
        {
            var context = x.Resolve<IComponentContext>();
            var owinContext = context.Resolve<IOwinContext>();

            // Not sure how you use this, I assume you took it out of the logic
            var offlineVendorString = context.Request.Headers[headerKey].ToUpper();  //list of stuff to blow up

            var otherClient = new Mock<IOtherClient>();
            otherClient.Setup(x => x.GetValue()).Throws<APIServiceUnavailableException>();

            return otherClient.Object;
        })
        .As<IOtherClient>()
        .InstancePerLifetimeScope();
}
else
{
    // normally register the "real" instance of IOtherClient
}

Registering the fake IOtherClient with InstancePerLifetimeScope is really important, as it means the logic will be executed for each request.


3. Notes

I think using Moq outside of test projects is not a very good idea. I would suggest creating a stub implementation of IOtherClient that would throw an exception when needed. This way you can free yourself of a dependency that has nothing to do in production code.

Mickaël Derriey
  • 12,796
  • 1
  • 53
  • 57
  • I completely agree about Moq. I built the process, but other devs filled in the override code :) I will try these and see how well they work. Thanks for the input – Ken Hundley Dec 14 '16 at 17:12
  • I ended up staying with my current structure, but I think #2 is a valid alternative so I'm marking it as the answer. Thank you for your help and detailed reponse – Ken Hundley Jun 01 '17 at 15:43