20

I want to do call an awaitable async method during a registration like this:

// builder variable contains Autofac ContainerBuilder
builder.Register(
    (async (context, parameters) => // need async here
        {
            var someClass = new SomeClass(context.Resolve<ISomeDependency>());
            await someClass.SomeAsyncInitMethod(); // need to await result
            return someClass;
        })).As<ISomeClass>().SingleInstance();

SomeClass implements ISomeClass as Service. The important part is the someClass.SomeAsyncInitMethod() call. This is async, so because of this I need to await it here and put the async keyword into the Register method. But now Autofac thinks this returns a Task<SomeClass> which is not registerable as Service ISomeClass.

How to achieve the above and register SomeClass as ISomeClass when awaiting the async init Method?

currarpickt
  • 2,290
  • 4
  • 24
  • 39
Beachwalker
  • 7,685
  • 6
  • 52
  • 94

3 Answers3

7

I think that doing any I/O intensive work at resolution phase is wrong design, because it's usually important to have full control over the order of this operations, catch their exceptions, repeat them, control time between some of them, etc.

Solution is to postpone them with factories. Let me replace SomeClass with a more meaningful NpgsqlConnection:

var builder = new ContainerBuilder();
builder.Register(context =>
{
    // make sure not to capture temporary context:
    // https://autofaccn.readthedocs.io/en/latest/advanced/concurrency.html#service-resolution
    var connectionString = context.Resolve<IConfiguration>().GetConnectionString("MyDb");

    return new Func<Task<NpgsqlConnection>>(async () =>
    {
        var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();
        return connection;
    });
});

And here's how connection user can look like:

public sealed class Repository
{
    private readonly Func<Task<NpgsqlConnection>> _connectionFactory;

    public Repository(Func<Task<NpgsqlConnection>> connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public async Task<string> GetServerVersionAsync()
    {
        using (var openedConnection = await _connectionFactory())
            return openedConnection.ServerVersion;
    }
}

Even if Autofac would support async registrations, it is still would be beneficial to require a factory in Repository constructor, because the connection is a limited resource and it is better to limit the time it is opened.

astef
  • 8,575
  • 4
  • 56
  • 95
5

This is an old question, but I think autofac does not support that.

We used:

builder.Register(c =>
  {
      var bar= c.Resolve<IBar>();
      var foo = new Foo(bar);
      return foo.ComputeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
  })
.As<IFoo>()
.SingleInstance();

But as mentioned on the comments: Registering async factory in Autofac

Vetras
  • 1,609
  • 22
  • 40
1

We ran into the same issue but found a different solution, I thought I'd share. You can wrap your async code within the Register method in a Task.Run and call .GetResult() or .Wait(). This will spawn a new context which ensures that any async code runs nicely within that context before being returned.

Sample taken from the other response snippet:

builder.Register(c =>
  {
      var bar= c.Resolve<IBar>();
      var foo = new Foo(bar);
      var resultTask = Task.Run(async () => {
           return await foo.ComputeAsync();
      });
      return resultTask.GetResult();
  })
.As<IFoo>()
.SingleInstance();

Jasper
  • 1,697
  • 2
  • 23
  • 48