Search code examples
c#dependency-injectionazure-functionsserverlesslamar

Proper way of registering 3rd party DI Framework (Lamar/Autofac) on Azure functions V2


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>();
        }
    }
}

Solution

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