Azure Functions Integration¶
This integration guide has the following prerequisites:
- Azure Function v3 (.NET Core)
- Simple Injector core library >= v5.3
- Simple Injector Service Collection Integration >= v5.3
Please be aware that due to current state of Azure Functions, it is impossible to inject Simple Injector-registered components into an Azure Function class. Azure Function classes can only contain dependencies registered through the built-in registration API. The integration below tries to mitigate this by injecting an Adapter (the AzureToSimpleInjectorMediator) into the Azure Function that forwards the call to Simple Injector.
Instead of implementing business logic inside an Azure Function class, you yield better results by moving this logic out of your Azure Function class and make a Function class into a Humble Object. This can be done by injecting the extracted service directly into the Azure Function’s constructor -or- as shown below, by introducing a Mediator that delegates the request to an underlying handler implementation. This next code snippet demonstrates the suggested way of constructing your Azure Function classes:
public class Function1
{
// Note that this IMediator interface is defined later on in this document.
private readonly IMediator mediator;
public Function1(IMediator mediator) => this.mediator = mediator;
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req)
{
return await this.mediator.HandleAsync(new Function1Request(req));
}
}
The previous Function1 class is a refactored version of the Azure Function that comes with Visual Studio’s Azure Functions template. All Function1 does is wrapping all relevant request information into a new Function1Request object and pass it on to the mediator. Function1Request is shown in the following snippet:
public sealed class Function1Request : IRequest<ObjectResult>
{
public Function1Request(HttpRequest req) => this.Req = req;
public HttpRequest Req { get; }
}
All relevant logic is extracted from Function1 into the Function1Handler, shown in the following listing.
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
public sealed class Function1Handler : IRequestHandler<Function1Request, ObjectResult>
{
private readonly ILogger log;
public Function1Handler(ILogger log) => this.log = log;
public async Task<ObjectResult> HandleAsync(Function1Request message)
{
// NOTE: Don't forget to add your applications root namespace to logging/logLevel
// node of the the application's host.json. Otherwise the line below won't log.
this.log.LogInformation("C# HTTP trigger function processed a request.");
string name = message.Req.Query["name"];
string requestBody = await new StreamReader(message.Req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the " +
"query string or in the request body for a personalized response."
: "Hello, " + name + ". This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
Function1Handler is a plain-old C# object, which contains the code extracted from the Azure Function. It implements the application-defined IRequestHandler<TRequest, TResult> interface. The addition of this interface allows the IMediator implementation to dispatch the request to the correct underlying handler, and additionally allows cross-cutting concerns to be applied around the execution of those handlers.
The previous code samples showed usages of the IMediator, IRequest<TResult>, and IRequestHandler<TRequest, TResult> interfaces. The listing below shows their definitions:
public interface IMediator
{
Task<TResult> HandleAsync<TResult>(IRequest<TResult> message);
}
public interface IRequest<TResult> { }
public interface IRequestHandler<TRequest, TResult> where TRequest : IRequest<TResult>
{
Task<TResult> HandleAsync(TRequest message);
}
To start, your Azure Functions application requires a bootstrapper that ties everything together. The following Startup class demonstrates how to tie Simple Injector in with the Azure Functions eco system:
using System;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using SimpleInjector;
[assembly: FunctionsStartup(typeof(MyAzureFunctionsApp.Startup))]
namespace MyAzureFunctionsApp
{
public class Startup : FunctionsStartup
{
private readonly Container container = new Container();
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(this);
services.AddSingleton<Completion>();
services.AddScoped(typeof(IMediator), typeof(AzureToSimpleInjectorMediator));
services.AddSimpleInjector(container, options =>
{
// Prevent the use of hosted services (not supported by Azure Functions).
options.EnableHostedServiceResolution = false;
// Allow injecting ILogger into application components
options.AddLogging();
});
InitializeContainer();
}
private void InitializeContainer()
{
// Batch-register all your request handlers.
container.Register(typeof(IRequestHandler<,>), this.GetType().Assembly);
// TODO: Add your registrations here.
}
public void Configure(IServiceProvider app)
{
// Complete the Simple Injector integration (enables cross wiring).
app.UseSimpleInjector(container);
container.Verify();
}
public override void Configure(IFunctionsHostBuilder builder) =>
this.ConfigureServices(builder.Services);
// HACK: Triggers the completion of the Simple Injector integration
public sealed class Completion
{
public Completion(Startup s, IServiceProvider app) => s.Configure(app);
}
}
}
The only part missing from the equation is the IMediator implementation, which is given in this last listing:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using SimpleInjector;
using SimpleInjector.Integration.ServiceCollection;
using SimpleInjector.Lifestyles;
public sealed class AzureToSimpleInjectorMediator : IMediator
{
private readonly Container container;
private readonly IServiceProvider serviceProvider;
public AzureToSimpleInjectorMediator(
// NOTE: Do note remove the Completion dependency. Its resolution triggers the
// finalization of the Simple Injector integration.
Startup.Completion completor, Container container, IServiceProvider provider)
{
this.container = container;
this.serviceProvider = provider;
}
private interface IRequestHandler<TResult>
{
Task<TResult> HandleAsync(IRequest<TResult> message);
}
// NOTE: There seems to be no support for async disposal for framework types in AF3,
// but using the code below, atleast Simple Injector-registered components will get
// disposed asynchronously.
public async Task<TResult> HandleAsync<TResult>(IRequest<TResult> message)
{
// Wrap the operation in a Simple Injector scope
await using (AsyncScopedLifestyle.BeginScope(this.container))
{
// Allow Simple Injector to cross wire framework dependencies.
this.container.GetInstance<ServiceScopeProvider>().ServiceScope =
new ServiceScope(this.serviceProvider);
return await this.HandleCoreAsync(message);
}
}
private async Task<TResult> HandleCoreAsync<TResult>(IRequest<TResult> message) =>
await this.GetHandler(message).HandleAsync(message);
private IRequestHandler<TResult> GetHandler<TResult>(IRequest<TResult> message)
{
var handlerType = typeof(IRequestHandler<,>)
.MakeGenericType(message.GetType(), typeof(TResult));
var wrapperType = typeof(RequestHandlerWrapper<,>)
.MakeGenericType(message.GetType(), typeof(TResult));
return (IRequestHandler<TResult>)Activator.CreateInstance(
wrapperType, container.GetInstance(handlerType));
}
private class RequestHandlerWrapper<TRequest, TResult> : IRequestHandler<TResult>
where TRequest : IRequest<TResult>
{
public RequestHandlerWrapper(IRequestHandler<TRequest, TResult> handler) =>
this.Handler = handler;
public IRequestHandler<TRequest, TResult> Handler { get; }
public Task<TResult> HandleAsync(IRequest<TResult> message) =>
this.Handler.HandleAsync((TRequest)message);
}
private sealed class ServiceScope : IServiceScope
{
public ServiceScope(IServiceProvider serviceProvider) =>
this.ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public void Dispose() { }
}
}
The presented code provides you with a template for a working Azure Functions application. Using this template, you can now start adding your own functions, requests, and handlers to start building your own awesome Azure Functions application.