Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving per-user/per-request dependency with Autofac in StartUp

I'm trying to resolve a dependency in the StartUp class of an ASP.Net WebApi 2 project. Autofac is used to configure the dependency injection. The scenario is as follows:

  • Different users see different data. Every user has certain rights which allow that user to see certain data.
  • A domain-driven architecture is being used which contains several boundaries. It's not allowed to do cross-boundary calls in the domain and data access layers. This is important, because in order to get all the codes of the user's allowed data, we need to do some cross-boundary calls.
  • We want to filter data inside the data access layer to only retrieve allowed data for every user. No cross-boundary calls allowed here.
  • In order to achieve this, I want to inject a list of all the codes from the records that the user can see.
  • For this, I tried to register a factory with Autofac which creates this list based on the user. Let's call the factory the StuffFactory and the list the StuffModel.

The problem now is that all of this needs to be registered as InstancePerRequest. This throws an InvalidOperatonException, which sounds reasonable because the Request lifetime scope cannot be accessed in the StartUp?

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.

The code where the allowed stuff is registered in the container:

private IContainer RegisterAllowedStuff(IContainer container)
{
    var builder = new ContainerBuilder();

    builder.Register(x => GetAllowedStuffForUser(container))
        .As<StuffModel>()
        .InstancePerRequest();

    builder.Update(container);
    return container;
}

private static StuffModel GetAllowedStuffForUser(IContainer container)
{
    var stuffFactory = container.Resolve<IStuffFactory>();
    return stuffFactory.CreateStuffModel(Helper.GetParsedUserName());
}

I am kind of stuck on how to advance from here. Is there an easy solution for the Autofac problem that I am completely overseeing? Does someone maybe have a better idea of how I could implement this? Thanks in advance!

like image 865
Kaj Nelissen Avatar asked Mar 11 '23 01:03

Kaj Nelissen


1 Answers

You should not have any business logic in your startup class. Autofac allows you to register lambda method which will be called at resolve time. At this time, a HttpRequest is available, so you can register theses lambda as InstancePerRequest.

In your case, if you want to register a StuffModel as InstancePerRequest you can do it like this :

builder.RegisterType<ConcreteStuffFactory>()
       .As<IStuffFactory>();
builder.Register(c => c.Resolve<IStuffFactory>()
                       .CreateStuffModel(Helper.GetParsedUserName()))
       .As<IStuffModel>()
       .InstancePerRequest();

By the way, I would also not used an Helper class which looks like an anti-pattern, you can create a service to get the user name of the current user, for exemple IUserContextProvider. You can also add a IUserContextProvider dependency on the ConcreteStuffFactory

public class ConcreteStuffFactory
{
    public ConcreteStuffFactory(IUserContextProvider userContextProvider)
    {
         this._userContextProvider = userContextProvider; 
    }

    private readonly IUserContextProvider _userContextProvider; 


    public IStuffModel CreateStuffModel()
    {
        String userName = this._userContextProvider.UserName; 
        // do things with userName
    }
}

and the registration :

builder.RegisterType<ConcreteStuffFactory>()
       .As<IStuffFactory>();
builder.RegisterType<HttpContextUserContextProvider>()
       .As<IUserContextProvider>()
       .InstancePerRequest();
builder.Register(c => c.Resolve<IStuffFactory>().CreateStuffModel())
       .As<IStuffModel>()
       .InstancePerRequest();
like image 72
Cyril Durand Avatar answered Apr 27 '23 16:04

Cyril Durand