# Middleware support for Azure Functions v3.0
## About this blog
Hey folks!, In this blog we will see about a library [AzureFunctions.Extensions.Middleware](https://github.com/Cloud-Jas/AzureFunctions.Extensions.Middleware) that I developed to make use of middleware pattern to address some of the cross-cutting concerns of our applications.
-------------------
## Middleware support in Azure Functions
Historically we always have known .NET Azure Functions have been in the in-process mode. Until the release of .NET 5, now there are two modes in which we can run Azure Functions
- Out-of-process([Isolated](https://docs.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide))
- In-process
In the out-of-process mode we have native support for middleware as per the design ( we have direct control over the execution of our function app, which runs as a separate process). But in the In-Process mode there is no middleware capability as we don't have full control over the application's startup and dependencies it consumes.
Find more details [here](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/net-on-azure-functions-roadmap/ba-p/2197916)

-------------------
## AzureFunctions.Extensions.Middleware
🛑 It is now obvious that we won't be having a direct way to access the pipeline of execution in the traditional In-Process mode which most of us are using in the production environments.
## Updates 3.0
* Separated concerns of Http and non-http triggers
* bug-fixes in accessing executionContext
* cleaner approach to access data for non-http triggers
> Note: Breaking change of class name changes
> * FunctionsMiddleware => HttpMiddleware
> * TaskMiddleware => NonHttpMiddleware
> * IMiddlewareBuilder => IHttpMiddlewareBuilder
> * ServerlessMiddleware => HttpMiddlewareBase
## Features
* Able to add multiple custom middlewares to the pipeline
* Able to access HTTP context inside the custom middleware
* Able to access ExecutionContext & data inside non-http triggers
* Able to inject middlewares in all the triggers available
* Able to bypass middlewares and return response
* Handle Crosscutting concerns of the application
* Logging
* Exception Handling
* CORS
* Performance Monitoring
* Caching
* Security
* OpenTelemetry
* Licenced under MIT - 100% free for personal and commercial use
## Supported Frameworks
- NetCoreApp 3.1
- NET 5.0
- NET 6.0
(back to top)
## Installation
### Install with Package Manager Console
`PM> Install-Package AzureFunctions.Extensions.Middleware`
## Usage
### Getting Started
# 1. HTTP Triggers
## 1.1 Add HttpContextAccessor in Startup.cs
Inorder to access/modify HttpContext within custom middleware we need to inject HttpContextAccessor to DI in Startup.cs file
```cs
builder.Services.AddHttpContextAccessor()
```
## 1.2. Add custom middlewares to the pipeline in Startup.cs
One or more custom middlewares can be added to the execution pipeline as below.
```cs
builder.Services.AddTransient((serviceProvider) =>
{
var funcBuilder = new HttpMiddlewareBuilder(serviceProvider.GetRequiredService());
funcBuilder.Use(new ExceptionHandlingMiddleware(serviceProvider.GetService>()));
funcBuilder.UseWhen(ctx => ctx != null && ctx.Request.Path.StartsWithSegments("/api/Authorize"),
new AuthorizationMiddleware(serviceProvider.GetService>()));
return funcBuilder;
});
```
### 1.2.1 Use()
- Use() middleware takes custom middleware as parameter and will be applied to all the endpoints
### 1.2.2 UseWhen()
- UseWhen() takes Func and custom middleware as parameters. If the condition is satisfied then middleware will be added to the pipeline
of exectuion.
## 1.3. Pass IHttpMiddlewareBuilder in Http trigger class
Pass IHttpMiddlewareBuilder dependency to the constructor of Http trigger class
```cs
private readonly ILogger _logger;
private readonly IHttpMiddlewareBuilder _middlewareBuilder;
public FxDefault(ILogger log, IHttpMiddlewareBuilder middlewareBuilder)
{
_logger = log;
_middlewareBuilder = middlewareBuilder;
}
```
## 1.4. Modify http triggers methods
All of our custom middlewares are added in the Startup.cs file and now we need to bind last middleware for our HttpTrigger method , use "_middlewareBuilder.ExecuteAsync(new HttpMiddleware(async (httpContext) =>{HTTP trigger code},executionContext)" to wrap the code.
> NOTE: pass optional parameter {executionContext} to use it in the custom middlewares , refer 1.5 to see how to make use of executionContext
```cs
public class FxDefault
{
private readonly ILogger _logger;
private readonly IHttpMiddlewareBuilder _middlewareBuilder;
public FxDefault(ILogger log, IHttpMiddlewareBuilder middlewareBuilder)
{
_logger = log;
_middlewareBuilder = middlewareBuilder;
}
[FunctionName("Function1")]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public async Task Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,ExecutionContext executionContext)
{
return await _middlewareBuilder.ExecuteAsync(new Extensions.Middleware.HttpMiddleware(async (httpContext) =>
{
_logger.LogInformation("C# HTTP trigger default function processed a request.");
string name = httpContext.Request.Query["name"];
string requestBody = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered default function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered default function executed successfully.";
return new OkObjectResult(responseMessage);
}, executionContext));
}
}
```
In the above example we have passed executionContext as parameter to HttpMiddleware. This will be made available to all the custom middleware that are registered in startup.cs
## 1.5 How to define Custom middlewares for http triggers?
All custom middleware of Http triggers should inherit from HttpMiddlewareBase and override InvokeAsync method . You will be able to access both HttpContext and ExecutionContext
> Note
> You have access to execution context in all the custom middlewares , only if you pass the executionContext as 2nd parameter in the HttpMiddleware wrapper (refer 1.4)
> To access it use {this.ExecutionContext} , refer below
```cs
public class ExceptionHandlingMiddleware : HttpMiddlewareBase
{
private readonly ILogger _logger;
public ExceptionHandlingMiddleware(ILogger logger)
{
_logger = logger;
}
public override async Task InvokeAsync(HttpContext context)
{
try
{
_logger.LogInformation($"{this.ExecutionContext.FunctionName} Request triggered");
await this.Next.InvokeAsync(context);
_logger.LogInformation($"{this.ExecutionContext.FunctionName} Request processed without any exceptions");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
context.Response.StatusCode = 400;
await context.Response.WriteAsync($"{this.ExecutionContext.FunctionName} request failed, Please try again");
}
}
}
```
# 2. Non-HTTP Triggers
## 2.1 Add custom middlewares to the pipeline in Startup.cs
One or more custom middlewares can be added to the execution pipeline as below for non-http triggers
```cs
builder.Services.AddTransient((serviceProvider) =>
{
var funcBuilder = new NonHttpMiddlewareBuilder();
funcBuilder.Use(new TaskExceptionHandlingMiddleware(serviceProvider.GetService>()));
funcBuilder.Use(new TimerDataAccessMiddleware(serviceProvider.GetService>()));
return funcBuilder;
});
```
### 2.1.1 Use()
- Use() middleware takes custom middleware as parameter and will be applied to all the endpoints
> NOTE: UseWhen is not available in non-http triggers
> However you could use ExecutionContext in each custom middleware to perform similar logic :). Refer the examples given below
## 2.2. Pass INonHttpMiddlewareBuilder in Non-Http trigger class
Pass INonHttpMiddlewareBuilder dependency to the constructor of Non-Http trigger class
```cs
private readonly ILogger _logger;
private readonly INonHttpMiddlewareBuilder _middlewareBuilder;
public TimerTrigger(ILogger log, INonHttpMiddlewareBuilder middlewareBuilder)
{
_logger = log;
_middlewareBuilder = middlewareBuilder;
}
```
## 2.3. Modify non-http triggers methods
All of our custom middlewares are added in the Startup.cs file and now we need to bind last middleware for our HttpTrigger method , use "_middlewareBuilder.ExecuteAsync(new NonHttpMiddleware(async (httpContext) =>{HTTP trigger code},executionContext,data)" to wrap the code.
> NOTE: pass optional parameters {executionContext,data} to use it in the custom middlewares , refer 1.5 to see how to make use of executionContext
```cs
public class TimerTrigger
{
private readonly ILogger _logger;
private readonly INonHttpMiddlewareBuilder _middlewareBuilder;
public TimerTrigger(ILogger log, INonHttpMiddlewareBuilder middlewareBuilder)
{
_logger = log;
_middlewareBuilder = middlewareBuilder;
}
[FunctionName("TimerTrigger")]
public async Task Run([TimerTrigger("*/10 * * * * *")] TimerInfo myTimer, ILogger log,ExecutionContext context)
{
await _middlewareBuilder.ExecuteAsync(new NonHttpMiddleware(async () =>
{
_logger.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
await Task.FromResult(true);
},context,myTimer));
}
}
```
In the above example we have passed both executionContext and timerinfo data as parameters to NonHttpMiddleware. This will be made available to all the custom middleware that are registered in startup.cs
## 2.4 How to define Custom middlewares for non-http triggers?
All custom middleware of Non-Http triggers should inherit from TaskMiddleware and override InvokeAsync method . You will be able to access both ExecutionContext and Data
> Note
> You have access to execution context and data in all the custom middlewares , only if you pass the executionContext and data as 2nd,3rd parameter in the NonHttpMiddleware wrapper respectively (refer 1.4)
> To access it use {this.ExecutionContext}/{this.Data} , refer below
```cs
public class TimerDataAccessMiddleware : NonHttpMiddlewareBase
{
private readonly ILogger _logger;
public TimerDataAccessMiddleware(ILogger logger)
{
_logger = logger;
}
public override async Task InvokeAsync()
{
if (this.ExecutionContext.FunctionName.Equals("TimerTrigger"))
{
try
{
var timerData = this.Data as TimerInfo;
_logger.LogInformation($"{this.ExecutionContext.FunctionName} Request triggered");
await this.Next.InvokeAsync();
_logger.LogInformation($"{this.ExecutionContext.FunctionName} Request processed without any exceptions");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
await this.Next.InvokeAsync();
}
}
```
## How to contribute?
If you wish to contribute to this open source or provide feedback, Feel free to reach out to me on below links
- [LinkedIn](https://www.linkedin.com/in/Divakar-Kumar)
- [GitHub](https://www.github.com/Divakar-Kumar)
- [Discord](https://discord.gg/QTeHPYT3)
-------------
## Github Source code
Leave a ⭐ in the below github repo if this library helped you at handling cross-cutting concerns of the application.
https://github.com/Cloud-Jas/AzureFunctions.Extensions.Middleware
-------------------
## Special thanks
- [Sriram](https://www.linkedin.com/in/sriram-ganesan-it/)
-------------------
## Join us
Please go ahead and join our discord channel (https://discord.gg/8Cs82yNS) to give some valuable feedbacks
## Sponsor