问题
I am trying to get autofac to work, but having issues with my unitofwork / user manager classes.
Initially I set my unit of work up as a per request instance like this:
builder.RegisterType<UnitOfWork<DatabaseContext>>().As<IUnitOfWork>().InstancePerRequest();
But in my StartupConfig.cs I was trying to set up oAuth like this:
private static OAuthAuthorizationServerOptions ConfigureOAuthTokenGeneration(IAppBuilder app, ILifetimeScope scope)
{
var t = scope.Resolve<IPasswordHasher>();
// Get our providers
var authProvider = scope.Resolve<OAuthProvider>();
var refreshTokenProvider = scope.Resolve<IAuthenticationTokenProvider>();
// Create our OAuth options
return new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, // TODO: Remove this line
TokenEndpointPath = new PathString("/oauth/access_token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new Business.Authentication.JwtFormat("http://localhost:62668"),
Provider = authProvider,
RefreshTokenProvider = refreshTokenProvider
};
}
The scope is obtained by this:
var scope = config.DependencyResolver.GetRootLifetimeScope();
Because of this, I could not use InstancePerRequest for the UnitOfWork, instead I changed it to this:
builder.RegisterType<UnitOfWork<DatabaseContext>>().As<IUnitOfWork>().InstancePerLifetimeScope();
Now the application actually runs, but I get a new error with my UserProvider, it is instantiated like this:
builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerRequest();
But if I run that, I get this error:
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
If you see this during execution of a web application, it generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario). Under the web integration always request dependencies from the dependency resolver or the request lifetime scope, never from the container itself.
This is actually being invoked by the line:
var authProvider = scope.Resolve<OAuthProvider>();
which is in my StartupConfig.cs. The OAuthProvider does need the UserProvider, the signature looks like this:
public OAuthProvider(IAdvancedEncryptionStandardProvider helper, IUserProvider userProvider)
{
this._helper = helper;
this._userProvider = userProvider;
}
So because this is not in the "request", I changed the UserProvider to be resolved like this:
builder.RegisterType<UserProvider>().As<IUserProvider>().InstancePerLifetimeScope();
which matches the UnitOfWork now, the project will load. But if I have an interface that tries to do 2 things (get the current user and list all users) it creates 2 requests, both creating a new instance of the UserController:
public UsersController(IUserProvider provider)
{
this._provider = provider;
}
which in turn tries to create 2 instances of the UserProvider. This throws an error:
The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.
So, I guess I need to know how I can resolve this. It's like I need 2 scopes, one for the start of the application and then another for everything else. Can anyone help me with this?
回答1:
The issue
Since, at the time of the OWIN middleware registration, you need to provide an instance of OAuthAuthorizationServerOptions
, there's no way to resolve the Provider
and RefreshTokenProvider
properties per HTTP request.
What we need is a way to create the OAuthAuthorizationServerOptions
per HTTP request. By extension, the same concept would apply to the OAuthAuthorizationServerMiddleware
.
A possible solution
That's exactly what AutofacMiddleware<T> does; It wraps an OWIN middleware by resolving it during the HTTP request from the lifetime scope stored in the OWIN context, then executes it. This means that we can now instantiate a new OAuthAuthorizationServerMiddleware
for each HTTP request.
As explained in the documentation, when you use app.UseAutofacMiddleware(container)
in your Startup
class, Autofac does 2 things:
- it hooks itself in the OWIN pipeline to create a nested lifetime scope for each HTTP request
- it wraps all the OwinMiddleware services registered in the container with AutofacMiddleware and adds them to the OWIN pipeline
The solution is then to register OAuthAuthorizationServerMiddleware
and all its dependencies in the Autofac container, and it will be automatically resolved for each request and executed.
OAuthAuthorizationServerMiddleware
has 3 dependencies:
- The next OWIN middleware in the pipeline, which AutofacMiddleware takes care of as it provides it to the
Resolve
method -TypedParameter.From(this.Next)
- An instance of
IAppBuilder
- The
OAuthAuthorizationServerOptions
instance
We have to register the last two dependencies plus the middleware itself in the container. Let's have a look at what this could look like:
Disclaimer: I didn't try the code below
// Here go all the registrations associated with the `Provider`
// and `RefreshTokenProvider` properties with the appropriate lifetimes
builder
.RegisterInstance(app)
.As<IAppBuilder>();
builder
.Register(x => new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true, // TODO: Remove this line
TokenEndpointPath = new PathString("/oauth/access_token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new Business.Authentication.JwtFormat("http://localhost:62668"),
Provider = x.Resolve<OAuthProvider>(),
RefreshTokenProvider = x.Resolve<IAuthenticationTokenProvider>()
})
.AsSelf()
// InstancePerDependency is same as InstancePerLifetimeScope
// in this case since the middleware will get resolved exactly one
// time per HTTP request anyway
.InstancePerDependency();
builder.RegisterType<OAuthAuthorizationServerMiddleware>();
Controlling the middleware order
While this could work, it's possible that it doesn't suit your needs since the OAuth middleware will be registered in the OWIN pipeline where you call app.UseAutofacMiddleware(container)
.
If you want more control over middleware order, you can separate the Autofac lifetime scope creation from the middleware registration in the OWIN pipeline:
Disclaimer: I didn't try the code below
// creates the per HTTP request lifetime scope
app.UseAutofacLifetimeScopeInjector(container);
// "usual" OWIN middlewares registrations
app.UseFoo();
// now use one from the container
app.UseMiddlewareFromContainer<OAuthAuthorizationServerMiddleware>();
// other "usual" OWIN middlewares registrations
app.UseBar();
来源:https://stackoverflow.com/questions/41061034/autofac-scope-issues