Autofac: Registering an Async Factory method

点点圈 提交于 2019-12-10 15:15:51

问题


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? i.e., 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. i.e., 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 Exceptions 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), i.e. 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.


回答1:


(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.




回答2:


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)




回答3:


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 - i.e. 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>>();


来源:https://stackoverflow.com/questions/33524706/autofac-registering-an-async-factory-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!