Autofac - how to resolve Func for ISomething from

2020-02-19 07:58发布

I'm trying to use Autofac to inject dependencies into FluentValidation in an MVC 4 app. I think I've got the strategy worked out, but I'm getting stuck with resolving my per-request ISomething from a singleton.

Here's the scenario: I've got a validator that derives from FluentValidation's AbstractValidator. I've read that FluentValidation validators perform best as singletons, so my constructor expects a Func and stores that Factory for use later. When the validator is used, it should ask the stored factory for an IDataStore, get the instance created for that request and use it. That's the theory. I want to give credit to https://github.com/robdmoore/UnobtrusiveMVCTechniques, which helped me settle on this solution. Here's the validator...

public class SiteAdminViewModelValidator : AbstractValidator<SiteAdminViewModel> {
    private readonly Func<IDataStore> _dbFactory;

    public SiteAdminViewModelValidator(Func<IDataStore> dbFactory) {
        _dbFactory = dbFactory;

        RuleFor(model => model.SiteCode).Length(1, 40).Must(BeSpecial);
    }

    public bool BeSpecial(string siteCode) {
        var db = _dbFactory();
        List<Stuff> stuffs = db.All<Stuff>().ToList();

        return true;
    }
}

If someone can point me to a working example of what I'm trying to accomplish, that would be great, but I'd also like to know the solution to this particular piece of Autofac tricksyness.

Here's my validator registration...

public class FluentValidatorModule : Module {
    protected override void Load(ContainerBuilder builder) {
        base.Load(builder);
        builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();

    var validators = AssemblyScanner.FindValidatorsInAssembly(System.Reflection.Assembly.GetExecutingAssembly());
    validators.ToList().ForEach(v => builder.RegisterType(v.ValidatorType).As(v.InterfaceType).SingleInstance());
    }
}

Here's my registration for the IDataStore factory...

builder.RegisterType<SuperDB>().As<IDataStore>().InstancePerHttpRequest();
builder.Register<Func<IDataStore>>(c => {
                                       var context = c.Resolve<IComponentContext>();
                                       return context.Resolve<IDataStore>;
                                   });

Here's the error I'm getting when my validator asks for an IDataStore on the line - var db = _dbFactory();

No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This 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 DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.

...which is exactly what I got when I tried it prior to writing my own Factory registration - the Func registration. From reading various answers to similar questions, it looked like what I have above should work because I thought I was now resolving the Func to get the current resolver.

Any help will be greatly appreciated.

2条回答
家丑人穷心不美
2楼-- · 2020-02-19 08:32

I agree that this should work - the Func<IDataStore> is defining a factory that will produce the dependency in each method as required.

The way that I got around this method is to use the DependencyResolver.Current like the error message suggests. The main reason is that I already had it set up using the Autofac.Mvc4 nuget package...

DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

So to actually setup the method I have the following func

public Func<T> PerHttpSafeResolve<T>()
{
    return () => DependencyResolver.Current.GetService<T>();
} 

And when constructing the container

builder.RegisterType<SuperDB>().As<IDataStore>().InstancePerHttpRequest();
builder.RegisterInstance(PerHttpSafeResolve<IDataStore>());

EDIT: The second line that is registering the instance is saying - If you need a Func<IDataStore> then use the value passed into the method. The result of the PerHttpSafeResolve<IDataStore> is just a function (factory), so it can live as a single instance.

查看更多
Root(大扎)
3楼-- · 2020-02-19 08:52

The issue is the scope of the validators. Autofac always resolves SingleInstance dependencies from the application container, which means the validators' dependencies come from the application container as well.

Autofac is requesting the Func<IDataStore> instance from there, not from the request container. When you resolve IComponentContext you are getting the container in which the validator is being resolved: the application container. As Func<IDataStore> is scoped to a request, there is no way for Autofac to provide it at the application level, hence the error.

The correct approach is to register the validators as InstancePerHttpRequest. A component's lifetime is dictated by its longest-lived dependency; validators work best as singletons when they don't have dependencies. In this case, though, a validator which depends on IDataStore is constrained to live for at most the lifetime of the IDataStore instance. (On the bright side, you can get rid of the Func.)

Think about it like going to a party: if you catch a ride with the host, you have to stay there until the party is over. You can't ride with the host if you want to leave early.

查看更多
登录 后发表回答