Autofac scope issues

|▌冷眼眸甩不掉的悲伤 提交于 2019-12-06 12:37:36

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!