Implementing IMiddlewareFactory with Simple Injector for ASP.NET Core
See original GitHub issueHi Steven (@dotnetjunkie),
I’m working on the advanced IMiddleware
/IMiddlewareFactory
topic for the ASP.NET Core docs, and I’ve hit a snag with scope lifetimes that I can’t work out. I probably have created a completely bonkers wiring-up job and/or factory! lol
The introductory topic that compares conventional middleware activation with factory-based activation is here 👉 https://docs.microsoft.com/aspnet/core/fundamentals/middleware/extensibility
I’m working on a new topic that uses the factory with Simple Injector. Everything seemed to be going along fairly well until I hit ye 'olde scoping problems, such as …
The AppDbContext is registered as ‘Async Scoped’ lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.
Exception has occurred: CLR/SimpleInjector.ActivationException
An exception of type 'SimpleInjector.ActivationException' occurred in SimpleInjector.dll but was not handled in user code: 'The AppDbContext is registered as 'Async Scoped' lifestyle, but the instance is requested outside the context of an active (Async Scoped) scope.'
at SimpleInjector.Scope.GetScopelessInstance[TImplementation](ScopedRegistration`1 registration)
at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
at SimpleInjector.InstanceProducer.GetInstance()
at SimpleInjector.Container.GetInstance(Type serviceType)
at MiddlewareExtensibilitySample.Middleware.SimpleInjectorMiddlewareFactory.Create(Type middlewareType) in C:\Users\guard\Documents\GitHub\MiddlewareExtensibilitySample2\Middleware\SimpleInjectorMiddlewareFactory.cs:line 22
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.<<UseMiddlewareInterface>b__1>d.MoveNext()
The middleware is unremarkable … just writes a query string value to an in-memory dB:
public class SimpleInjectorActivatedMiddleware : IMiddleware
{
private readonly AppDbContext _db;
public SimpleInjectorActivatedMiddleware(AppDbContext db)
{
_db = db;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var keyValue = context.Request.Query["key"];
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "SimpleInjectorActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
I’d like the factory to get that going. I can’t confirm this works because of the scoping issue, but here’s what I was thinking …
public class SimpleInjectorMiddlewareFactory : IMiddlewareFactory
{
private readonly Container _container;
public SimpleInjectorMiddlewareFactory(Container container)
{
_container = container;
}
public IMiddleware Created { get; private set; }
public IMiddleware Released { get; private set; }
public IMiddleware Create(Type middlewareType)
{
Created = _container.GetInstance(middlewareType) as IMiddleware;
return Created;
}
public void Release(IMiddleware middleware)
{
Released = middleware;
}
}
… an unremarkable pipeline for this sample (trying to keep the Startup
class lean, so I moved InitializeContainer
bits up to ConfigureServices
) …
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseSimpleInjectorActivatedMiddleware();
app.UseStaticFiles();
app.UseMvc();
}
… and the wiring up bits in ConfigureServices
look like this (again, dropping the IntegrateSimpleInjector
format to keep things lean) …
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<SimpleInjectorActivatedMiddleware>();
services.AddTransient<IMiddlewareFactory>(_ =>
{
return new SimpleInjectorMiddlewareFactory(container);
});
services.AddMvc();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<AppDbContext>(() =>
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseInMemoryDatabase("InMemoryDb");
return new AppDbContext(optionsBuilder.Options);
}, Lifestyle.Scoped);
container.Register<SimpleInjectorMiddlewareFactory>();
container.Register<SimpleInjectorActivatedMiddleware>();
container.Verify();
}
The full project is over here 👉 https://github.com/guardrex/MiddlewareExtensibilitySample2
… and the scopes you see in the GH project and here ☝️ I hacked around with trying to resolve this but no luck. It actually seems like lifestyles aren’t my problem and that I haven’t wired Simple Injector up properly or I built a broken factory.
Issue Analytics
- State:
- Created 6 years ago
- Comments:13
Ah! I see. Thanks. As an aside, I hope [@]DamianEdwards received that feedback. Anywho … the workaround in #362 makes perfect sense. Thanks again! This sample is almost there!
@dotnetjunkie As of this moment, I’m just following orders. I DO stand ready to create a new sample using another container if they order such a move. In the meantime, I hope I reduce your concerns a tad with some very strong language in the note that will be added to the topic on https://github.com/aspnet/Docs/pull/5382.