George Kosmidis

Microsoft MVP | Speaks of Azure & .NET | Founder of Munich .NET
Building tomorrow @
slalom
slalom

Create an Azure Function App for dotnet out-of-proc

by George Kosmidis / Published 6 months ago
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 versionIn-processOut-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.1n/a
Functions 1.x.NET Framework 4.8n/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:

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 explicit Microsoft.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!

This page is open source. Noticed a typo? Or something unclear?
Edit Page Create Issue Discuss
Microsoft MVP - George Kosmidis

My next events!

Online
Online event
Azure Architecture Icons - SVGs, PNGs and draw.io libraries