Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac runtime parameters

I'm new to autofac and looking to see the best practices on passing runtime values to constructor. I've read a bunch of stackoverflow questions where this is asked but none are fully fleshed out. Should we be using delegates, factory to create service etc. I know passing the container around is not the best way to accomplish this.

In my particular case I have a service that access multiple dependencies, say logging, dataprovider, etc. Along with the few services being passed I also have run-time parameters I need to capture, say userid, password. The userid and password are required for the SomeService and are looked up when a web viewer performs a particular action. Below is what I have and highlighted is the issue.

public class SomeService : ISomeService
{
    private readonly IDataProvider _dataProvider;
    private readonly ILog _log;
    private readonly string _username;  
    private readonly string _password;

    public SomeService(IDataProvider dataProvider, ILog log,
        string username, string password)
    {
      _dataProvider = dataProvider;
      _log = log;
      _username = username;
      _password = password;
    }
}

The dataprovider, and log are configured in autofac

builder.RegisterType<DataProviderService>().As<IDataProvider>()
builder.RegisterType<SomeLogService>().As<ILog>()

Most of the functionality of this "SomeService" requires a username and password to verify before performing tasks, so figured it best to pass into constructor when creating but have never dealt with run-time requirements for autofac. I've reviewed the question Autofac - resolving runtime parameters without having to pass container around and it seems close to what I need but need some more feedback on the best way to accomplish this.

like image 231
maguy Avatar asked Aug 11 '14 22:08

maguy


2 Answers

AutoFac supports the resolution of services with runtime parameters through the concept of Parameterized Instantiation.

In the constructor of the client with the dependency on the service with specific runtime parameters, declare your dependency as a Func which returns that dependency in terms of its strongly typed parameters.

E.g. Func<string, ISomeService> myService

When AutoFac sees the Func it creates a delegate that acts as a factory method for the creation of the service.

From the documentation:

If type T is registered with the container, Autofac will automatically resolve dependencies on Func as factories that create T instances through the container.

It is not possible to have duplicate types in the parameter list of your dependency, as is the case of ISomeService in your question. E.g. Func<string, string, ISomeService> will not work. In this case, you need to supply a custom delegate factory.

From the documentation:

Factory adapters provide the instantiation features of the container to managed components without exposing the container itself to them.

One way to implement this approach is to declare a delegate type alongside the definition of your type that AutoFac will use as a factory method.

E.g.

public class SomeService : ISomeService
{
    // Factory method
    public delegate SomeService Factory(string userName, string password);

    public SomeService(IDataProvider dataProvider,
        ILog log,
        string username,
        string password)
    {
        // ..

Your client of ISomeService would then look like this:

public class MyClient
{
    public MyClient(SomeService.Factory serviceFactory)
    {
        // Resolve ISomeService using factory method
        // passing runtime parameters
        _myService = serviceFactory("this", "that");
    }
}

Note that all non-runtime parameters to the service (in your case IDataProvider and ILog) continue to be automatically resolved by the container.

like image 102
Matt Caton Avatar answered Oct 19 '22 05:10

Matt Caton


In general you should prevent passing runtime values into constructors. That will complicate your design and your DI configuration a lot. Constructors are for dependencies and configuration values. Pass runtime values either through method arguments or inject a service that allows you to retrieve those runtime values. Take for instance an IUserContext service that allows retrieving the name current logged in user.

like image 43
Steven Avatar answered Oct 19 '22 05:10

Steven