简体   繁体   中英

Autofac: Registering an Async Factory method

TL;DR: Does Autofac support something like AutoFixture's fixture.Get() mechanism ?

I'm using Autofac and need to invoke async factory methods which look like this:

class AppModel
{
     public static async Task<AppModel> CreateAsync(IDependency x, IDependency2 y)
     { ... }
}

What is the simplest way for me to execute such a method and have the arguments be supplied by Autofac ? ie, I want to be able to do something like:

Task<AppModel> creationTask = <some autofaccery>(AppModel.CreateAsync);
var appModel = await creationTask();

where <some autofaccery> represents some mechanism of interacting with ContainerBuilder and/or IContainer and/or some form of generated Delegates or similar which is succinct in nature and isolates me from specifying the arguments to the Factory Method explicitly. ie, I want to avoid having to explicitly resolve each argument [and/or have to update them as the dependencies change] like I do atm:

var appModel = await AppModel.CreateAsync( 
    container.Resolve<IDependency>(),
    container.Resolve<IDependency2>());

I am in infrastructure components territory, close to the Composition Root and could potentially programmatically define Component Registrations and/or do other nastiness that should be confined to there. I don't mind reflection being involved as it's only being invoked once.

What is critical is that I do need any Exception s emanating from the Task to be observed.

The Task<T> is a red herring to a large degree, but the point is that following the normal pattern of defining a synchronous factory method and having Autofac work through that won't fly (at least not directly), ie I can't just change it to:

     public static AppModel CreateAsync(IDependency x, IDependency2 y)
     { ... }

I'd also like to avoid two-phase initialization - I don't need the object to be available until it's been initialized.

(Inspired by the TL;DR I added at the top)

You could implement a ResolveAsync family of methods:-

public static Task<T> ResolveAsync<T1, T>(Func<T1, Task<T>> func)
{
    return func(_container.Resolve<T1>());
}

public static Task<T> ResolveAsync<T1, T2, T>(Func<T1, T2, Task<T>> func)
{
    return func(_container.Resolve<T1>(), _container.Resolve<T2>());
}

public static Task<T> ResolveAsync<T1, T2, T3, T>(Func<T1, T2, T3, Task<T>> func)
{
    return func(_container.Resolve<T1>(), _container.Resolve<T2>(), _container.Resolve<T3>());
}

This allows me to do:

var appModel = await ResolveAsync<IDependency,IDependency2>(AppModel.CreateAsync);

Or obviously these could be turned into extension methods:-

var appModel = await container.ResolveAsync<IDependency,IDependency2>(AppModel.CreateAsync);

working on a wrapper to allow C#'s primitive type inference to be used :( I'm going to conserve my energy and not sink any further time into achieving a proper solution in a language/async paradigm not built for the job .

In addition to necessitating a set of ugly extension methods, my first answer leaks the signature of the factory into the calling modules, necessitating references to namespaces that are not directly required. In order to hide this, one can use the same scheme, but encapsulate the dependency set in an AsyncFactory class :-

class AppModel
{
     public class AsyncFactory
     {
           public AsyncFactory(IDependency x, IDependency2 y)
           { 
               CreateAsync = async() =>
                   new AppModel( x.CalculateA(await y.CalculateB()));
           }
           public Func<Task<AppModel> CreateAsync { get;}
     }
     private AppModel(A a) { ... }
}

Then the caller can use a uniform mechanism to use the factory:-

var appModel = await container.Resolve<AppModel.Factory>().CreateAsync();

(Note there is no restatement of the argument types so adding further dependencies to the AsyncFactory will not trigger knock-on changes to the calling code)

Abusing LazyTask<T> , one can transform a CreateAsync method into something Autofac can resolve :- a Type [derived from LazyTask<T> ], which looks like so:

class AppModel
{
     public class AsyncFactory : LazyTask<AppModel>
     {
           public AsyncFactory(IDependency x, IDependency2 y) : base(async() => 
               new AppModel( x.CalculateA(await y.CalculateB())))
           {}
     }
     private AppModel(A a) { ... }
}

This can be consumed as follows:-

var appModel = await container.Resolve<AppModel.AsyncFactory>();

Not accepting this as I still feel there is an opportunity for this to be clearer - ie if Autofac was to apply a special treatment to Task<T> CreateAsync methods written as follows:-

class AppModel
{
    public async Task<AppModel> CreateAsync(IDependency x, IDependency2 y) =>
        new AppModel( x.CalculateA(await y.CalculateB()));
}

automatically registering them as the type Task<T> , allowing one to consume as follows without relying on my Task wrapper type:-

var appModel = await container.Resolve<Task<AppModel>>();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM