2

Azure Functions V2 now supports .net dependency injection

In order to achieve that you need to do the following code:

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient();
            builder.Services.AddSingleton((s) => {
                return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
            });
            builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
        }
    }
}

I want to change the default container from .net to "Lamar" DI framework.

On their documentation they have an example for a WebHost:

var builder = new WebHostBuilder();
builder
    // Replaces the built in DI container
    // with Lamar
    .UseLamar()

    // Normal ASP.Net Core bootstrapping
    .UseUrls("http://localhost:5002")
    .UseKestrel()
    .UseStartup<Startup>();

builder.Start();

But I'm not able to change IFunctionsHostBuilder to use "UseLamar()" extension. Since this extends IWebHostBuilder. The only ways I was able to intercept the initialization of azure functions was Either with FunctionsStartup that configures IFunctionsHostBuilder or IWebJobsStartup that configures IWebJobsBuilder, but I don't find extensions for those kinds of builds on Lamar.

I've tried to check the existing extension to create a similar code but is not working because probably I need to create more stuff:

[assembly: FunctionsStartup(typeof(FunctionAppPrototype.Startup))]
namespace FunctionAppPrototype
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var container = new Container(x =>
            {
                x.AddTransient<IMyService, MyService>();
            });

            builder.Services.AddSingleton<IServiceProviderFactory<IServiceCollection>, LamarServiceProviderFactory>();
            builder.Services.AddSingleton<IServiceProviderFactory<ServiceRegistry>, LamarServiceProviderFactory>();
        }
    }
}
João Antunes
  • 798
  • 8
  • 27
  • AFAIK, you can't change the default DI in azure functions to one of your own. If you desperately need another framework, you can workaround this by using the old, improvised method. Here is a link to a blog post about using Autofac: http://dontcodetired.com/blog/post/Azure-Functions-Dependency-Injection-with-Autofac – Alex Pshul Sep 03 '19 at 21:20
  • I was not able to do with Lamar because I had to write an extension to support it, but Autofac has already an extension prepared for IWebJobsStartup so I was able to implement it, I'll post the solution – João Antunes Sep 04 '19 at 08:43

2 Answers2

3

After some research, I was able to find a solution using Autofac. I was not able to do it with Lamar it had no extension either for IFunctionsHostBuilder or IWebJobsBuilder.

Source Code: Binding extensions for dependency injection in Azure Function v2

Nuget: Willezone.Azure.WebJobs.Extensions.DependencyInjection

First, you need to intercept the startup of the function app by doing the following code:

[assembly: WebJobsStartup(typeof(AutoFacFunctionAppPrototype.WebJobsStartup))]

namespace AutoFacFunctionAppPrototype
{
    public class WebJobsStartup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder) =>
            builder.AddDependencyInjection<AutoFacServiceProviderBuilder>();
    }
}

Then create the container and register the dependencies:

namespace AutoFacFunctionAppPrototype.Builders
{
    public class AutoFacServiceProviderBuilder : IServiceProviderBuilder
    {
        private readonly IConfiguration configuration;

        public AutoFacServiceProviderBuilder(IConfiguration configuration) 
            => this.configuration = configuration;

        public IServiceProvider Build()
        {
            var services = new ServiceCollection();
            services.AddTransient<ITransientService, TransientService>();
            services.AddScoped<IScopedService, ScopedService>();

            var builder = new ContainerBuilder();

            builder.RegisterType<SingletonService>().As<ISingletonService>().SingleInstance();

            builder.Populate(services); // Populate is needed to have support for scopes.
            return new AutofacServiceProvider(builder.Build());
        }
    }
}

Then you can use them on the function using the attribute [Inject]:

namespace AutoFacFunctionAppPrototype.Functions
{
    public static class CounterFunction
    {
        [FunctionName("Counter")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
            [Inject]ITransientService transientService,
            [Inject]IScopedService scopedService,
            [Inject]ISingletonService singletonService,
             ILogger logger)
        {
            logger.LogInformation("C# HTTP trigger function processed a request.");

            string result = String.Join(Environment.NewLine, new[] {
                $"Transient: {transientService.GetCounter()}",
                $"Scoped: {scopedService.GetCounter()}",
                $"Singleton: {singletonService.GetCounter()}",
            });
            return new OkObjectResult(result);
        }
    }
}

Using this approach I was only able to inject parameters, was not able to do constructor or property injection, even though I was a non-static class.

Note: If in the future Autofac supports extension for IFunctionsHostBuilder, probably would be better to use that approach instead of IWebJobsStartup.

João Antunes
  • 798
  • 8
  • 27
2

I've created a new way to use Autofac DI in a Azure Functions v3 project, without using static functions or inject attributes. The disposal of services are called using the appropriate scope.

GitHub: https://github.com/junalmeida/autofac-azurefunctions
NuGet: https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions

Feel free to contribute with me!

Example

public class Function1 
{
    private readonly IService1 _service1;

    public Function1(IService1 service1)
    {
        _service1 = service1;
    }

    [FunctionName(nameof(Function1))]
    public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem, ILogger log)
    {
        ...
    }
Marcos Junior
  • 209
  • 2
  • 9