Create an Azure Function App for dotnet out-of-proc
Did you know? In-process C# class library Azure Functions will not be supported from .NET 7.0 on! And although you should always target LTS and that gives you almost two years to adapt, the hard stop could require quite a massive rewrite!
But why this change?
Previously, an Azure Function was only supporting a tightly integrated mode for .NET functions - it could only run as a class library in the same process as the host. This mode was providing a deep integration between the two contributing to speed, but also providing the engineers with goodies like sharing binding APIs and types. However, this integration required a tighter coupling between the host process and the .NET function which means both had to run on the same version of .NET.
The version constrain above wasn't usually a problem though. Most of the developers out there didn't even notice the limitation and even more, all(?) of us enjoyed sharing types with mama Azure Function. But don't getting me wrong here; if it happened you were on the wrong side of the story, you were in for a ride! Massive problems with almost no solution available, rendering Azure Function decisions obsolete.
And then came Microsoft. With the out-of-proc approach solved the blockers for these few poor teams but also decided to stop supporting in-proc at all - how many combination of an Azure Function should Microsoft support? I mean, for the in-proc workloads you would need a host that can run all different supported .NET versions a developer might choose and then one more for the out-of-proc? That was too much I guess.
In any case, we are now here and this change does not come without benefits!
Benefits of running out-of-process
- Process isolation lets you develop functions that use current .NET releases (such as .NET 7.0), not natively supported by the Functions runtime yet.
- Fewer conflicts: because the functions run in a separate process, assemblies used in your app won't conflict with different version of the same assemblies used by the host process.
- Full control of the process: you control the start-up of the app and can control the configurations used and the middleware started.
- Dependency injection: because you have full control of the process, you can use current .NET behaviors for dependency injection and incorporating middleware into your function app (OK that's huge)
Supported versions
Functions runtime version | In-process | Out-of-process |
---|---|---|
Functions 4.x | .NET 6.0 | .NET 6.0 .NET 7.0 .NET Framework 4.8 |
Functions 3.x | .NET Core 3.1 | .NET 5.0 |
Functions 2.x | .NET Core 2.1 | n/a |
Functions 1.x | .NET Framework 4.8 | n/a |
Creating a .NET 6 isolated project
Let's sample a dotnet-isolated function!
The following is a list of prerequisites you must have to run the code that follows. Please go through them and once done continue to the exciting part!
Prerequisites
Before you begin, you must have the following:- .NET 6.0 SDK
- Azure Functions Core Tools version 4.x.
- One of the following tools for creating Azure resources:
- Azure CLI version 2.4 or later.
- The Azure Az PowerShell module version 5.9.0 or later.
Let's create our function!
After making sure that all of the requirements above are met, navigate to the folder your wish to create your project and run:
func init YourFunctionProject --worker-runtime dotnet-isolated --target-framework net6.0
This will create a new folder named after your chosen function name (here YourFunctionProject
). Navigate into that folder and you should see the followings (among other):
- host.json file.
stores configuration settings that affect all functions in the project when running locally or in Azure. - local.settings.json file
stores app settings and connection strings that are used when running locally. This file contains secrets and isn't published to your function app in Azure. Instead, add app settings to your function app. - C# project file (.csproj)
defines the project and dependencies. - Program.cs
a file that's the entry point for the app. - .vscode folder
Yep, it's Visual Studio Code ready :)
In Azure Functions, a function project is a container for one or more individual functions that each responds to a specific trigger. All functions in a project share the same local and hosting configurations.
Creating an operation for our function
As noted, the function we just created is an empty container; it contains no trigger, no code, no nothing. If we want to do something meaningful we should add an operation to the project by using the following command, where the--name
argument is the unique name of the operation (here HttpExample
) and the --template
argument specifies the function's trigger (here HTTP).
func new --name HttpExample --template "HTTP trigger" --authlevel "anonymous"
Upon running the command, a file HttpExample.cs
will be created with the following sample code: using System.Collections.Generic;
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace LocalFunctionProj
{
public class HttpExample
{
private readonly ILogger _logger;
public HttpExample(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<HttpExample>();
}
[Function("HttpExample")]
public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
response.WriteString("Welcome to Azure Functions!");
return response;
}
}
}
How to do bindings with the new model
With the .NET worker, function apps migrated to a new binding model. In this model, no explicitMicrosoft.Azure.WebJobs
references are required. The attributes required to create Functions
, Triggers
, and Bindings
are now available in these Nuget packages: - Common types (FunctionAttribute):
Microsoft.Azure.Functions.Worker.Extensions.Abstractions
- Http bindings:
Microsoft.Azure.Functions.Worker.Extensions.Http
- Storage (Queue, Blob, Table) bindings:
Microsoft.Azure.Functions.Worker.Extensions.Storage
- Timer bindings:
Microsoft.Azure.Functions.Worker.Extensions.Timer
- Event Hubs bindings:
Microsoft.Azure.Functions.Worker.Extensions.EventHubs
- Event Grid bindings:
Microsoft.Azure.Functions.Worker.Extensions.EventGrid
- Service Bus bindings:
Microsoft.Azure.Functions.Worker.Extensions.ServiceBus
- Cosmos DB bindings:
Microsoft.Azure.Functions.Worker.Extensions.CosmosDB
- RabbitMQ bindings:
Microsoft.Azure.Functions.Worker.Extensions.RabbitMQ
- SignalR Service bindings:
Microsoft.Azure.Functions.Worker.Extensions.SignalRService
- Kafka bindings:
Microsoft.Azure.Functions.Worker.Extensions.Kafka
- Warmup trigger:
Microsoft.Azure.Functions.Worker.Extensions.Warmup
Output Bindings
In the new model, output bindings changed as well. You can now specify output bindings in two ways:Using Method Attributes (works if you only have one output binding)
In this model, the function return value is treated as the value for the Output Binding. An example:
public static class EventHubsFunction
{
[Function("EventHubsFunction")]
[EventHubOutput("MyEventHubName", Connection = "EventHubConnectionAppSetting")]
public static string Run([EventHubTrigger("src", Connection = "EventHubConnectionAppSetting")] string input,
FunctionContext context)
{
var logger = context.GetLogger("EventHubsFunction");
logger.LogInformation(input);
var message = $"Output message created at {DateTime.Now}";
return message;
}
}
Using Property Attributes (works with any number of output bindings)
In this model, the function return type can specify output bindings using property attributes.
/// <summary>
/// This class specifies output bindings in the properties of <see cref="MyOutputType"/>.
/// <see cref="MyOutputType"/> defines a Queue output binding, and an Http Response property.
/// By default, a property of type <see cref="HttpResponseData"/> in the return type of the function
/// is treated as an Http output binding. This property can be used to provide a response to the Http trigger.
/// </summary>
public static class MultiOutput
{
[Function("MultiOutput")]
public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
FunctionContext context)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.WriteString("Success!");
string myQueueOutput = "some output";
return new MyOutputType()
{
Name = myQueueOutput,
HttpReponse = response
};
}
}
public class MyOutputType
{
[QueueOutput("myQueue")]
public string Name { get; set; }
public HttpResponseData HttpReponse { get; set; }
}
Conclusion
That was it! Get some samples over here and start and dive into the new Azure Function approach!