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.
I can see two ways of achieving what you want to.
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.
There's 2 drawbacks to the first solution:
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.
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.