Autofac scope issues

时间:2016-12-09 12:56:53

标签: c# autofac

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 个答案:

答案 0 :(得分:2)

问题

因为在OWIN中间件注册时,您需要提供OAuthAuthorizationServerOptions的实例,所以无法解析每个HTTP请求的ProviderRefreshTokenProvider属性。 / p>

我们需要的是一种为每个HTTP请求创建OAuthAuthorizationServerOptions的方法。通过扩展,相同的概念将适用于OAuthAuthorizationServerMiddleware

可能的解决方案

这正是AutofacMiddleware<T>的作用;它通过从存储在OWIN上下文中的生命周期范围中解析 HTTP请求来包装OWIN中间件,然后执行它。这意味着我们现在可以为每个HTTP请求实例化一个新的OAuthAuthorizationServerMiddleware

正如the documentation中所述,当您在app.UseAutofacMiddleware(container)课程中使用Startup时,Autofac会2 things

然后解决方案是在Autofac容器中注册OAuthAuthorizationServerMiddleware及其所有依赖项,并为每个请求自动解析并执行。

OAuthAuthorizationServerMiddleware有3个依赖项:

  • 管道中的下一个OWIN中间件AutofacMiddleware takes care of,它提供给Resolve方法 - TypedParameter.From(this.Next)
  • IAppBuilder
  • 的一个实例
  • OAuthAuthorizationServerOptions实例

我们必须在容器中注册最后两个依赖项和中间件本身。让我们来看看它的外观:

免责声明:我没有尝试下面的代码

// 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>();

控制中间件订单

虽然这可行,但它可能不适合您的需求,因为OAuth中间件将在您调用app.UseAutofacMiddleware(container)的OWIN管道中注册。

如果您想要更多地控制中间件订单,可以separate the Autofac lifetime scope creation from the middleware registration in the OWIN pipeline

免责声明:我没有尝试下面的代码

// 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();