Creating Azure Functions with .NET 6 in Isolated Process

Azure Functions enables us to run small chunks of code in the cloud and pay on demand, only when the code runs. Running small solutions can even be free. The development environment is flexible, you can develop your functions directly through the Azure Portal or locally in Visual Studio.

There is a strong tendency to move web applications to the cloud. You could say that serverless computing is becoming the new normal. Why not? It’s quick, cheap, practical, and you can run the app anywhere without the painstaking process of managing the accompanying infrastructure.

This article focuses on continuous integration through Visual Studio as larger companies would do it for a bigger solution.

How to create an Azure function in 5 easy steps

What is new in the Azure Functions process?

Azure Functions code has always been tightly linked with the .NET version of Azure Functions Runtime. Unfortunately, you couldn’t write your code using the latest version of .NET until the Azure Functions team at Microsoft updated their Azure Functions .NET Runtime.

To avoid this inconvenience, Microsoft introduced the Isolated Process model. It allowed us to write the Azure Functions code using any version of .NET, saving developers from future headaches and giving them the necessary flexibility.

Azure Functions process

Phases of support for in-process and isolated process through .NET releases

Running .NET functions in an out-of-process model has many benefits. It gives you complete control of managing the app’s start-up, configuration and the middlewares. Assemblies from your app don’t conflict with different versions of the identical assemblies used by host processes.  

As shown in the image above, isolated processes are becoming the recommended way of creating Azure Functions. According to the announcements, in-process functions are to be fully retired in favor of isolated functions, however not before .NET 7 (scheduled to ship in November 2022) becomes available.

How to create an Azure project

If you haven’t already, update .NET to version 6. You can do that by updating to the new shiny Visual Studio 2022 with built-in SDK or downloading it from here: .NET 6.0 SDK.

In Visual Studio 2022, Azure Functions Core Tools, necessary for developing Azure Functions, are available by adding the Azure Cloud Tool in the installation process of VS. But if you’re not using Visual Studio to create an Azure Functions project, you will need to have Azure Functions Core Tools installed. They include the commands to create functions, deploy function projects and connect to Azure.

Furthermore, Azure Functions interlock with Azure Storage services. To run functions, we have to provide a general-purpose Azure Storage account that will keep track of function execution state and triggers. However, if we want to run them locally, without specifying a storage account, the Azure storage emulator will be used. The emulator is included in VS22 by default, so installing it separately is unnecessary. To explore storage account data, we can use the Azure Storage Explorer.

Creating a separate Azure Functions project

There are two ways of creating Azure functions: through your IDE or the Command Prompt. In this article, we are going with the latter. We can run the command through the terminal window or the Command Prompt to create an Azure Functions project. Navigate to the desired folder and run the following command. You can give it any name you want:

	func init ProjectName

This command will create a new folder with the project name to nest your functions.

After that, you may get a prompt message to choose the runtime for your Azure Functions project. Since we are using .NET with an isolated process, we will select dotnet (isolated process).

After scaffolding, you should see something like this in your desired folder:

Creating a separate Azure Functions project

host.json

The global configuration file host.json includes settings that affect all functions deployed under the same function app. Host.json gets published when the functions are deployed. Additional information about settings available in the host.json file can be found on this link.

	{
    "version": "2.0",
    "logging": {
        "applicationInsights": {
            "samplingSettings": {
                "isEnabled": true,
                "excludedTypes": "Request"
            } 
        } 
    } 
}

local.settings.json

App settings, connection strings, and settings utilized by local development are all stored in the local.settings.json file. This file is only used when projects are run locally.

	{
    "IsEncrypted": false,
    "Values" : {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "MyConnection": "<connection-string-to-your-databse>",
        "MyOptions:Option1": "value",
        "MyOptions:Option2": "value"
    },
    "ConnectionStrings": {
        "DefaultConnection": "UseDevelopmentStorage=true"
    } 
}

UseDevelopmentStorage=true is equivalent to the connection string for the emulator. If you want to use a value from the local.settings.json file in the function entrance, you can use "%MyOptions.Option1%". This is only applicable in the Values field. You don’t need to use the percentage characters if you want to read a connection string from the Values field. The Connection Strings field will read: "ConnectionStrings:DefaultConnection".

Program.cs

Dependency registration, options configuration, and db context configuration are all set in the Program.cs class. From there, we use dependency injection the same way we use it in any class.

	public class Program
 {
     public static void Main()
     {
         var host = new HostBuilder()
             .ConfigureFunctionsWorkerDefaults()
             .ConfigureServices(services =>
             {
                 services.AddDbContext(options 
                   => Options.UseSqlServer(
                          Environment.GetEnvironmentVariable("MyConnection")));
                 services.AddOptions()
                     .Configure((settings, configuration)
                          => configuration.GetSection("MyOptions").Bind(settings));
                 services.AddScoped();
                 services.AddScoped();    
             }).Build();
         host.Run();
     }
 }

How to create an Azure function

Now that we set everything up, we can create our first Azure function. Again, we come back to the Command Prompt or the terminal window, navigate to the folder we created, and run the command:

	func new

You will get a prompt message to choose the function template.

Azure Functions - How to create an Azure function

There are a couple of templates, so here is a short review of a few of the most commonly used functions.

Queue Trigger in Azure Functions

A function is triggered when a new message in the queue appears. The function “listens” to the queue storage and when a new message comes in, the function logic is executed.

Azure Queue Storage is a service used for storing a large number of messages in the cloud, which applications would later process at their own pace. Primarily, it helps ensure the delivery of system messages between and within applications. The maximum message size is 64 kB.

You should know that Queue Storage does not support data ordering, so the order of enqueuing is not guaranteed. However, it helps with scaling applications and in managing sudden traffic bursts by offloading background and non-interactive workloads. Developers can also use Queue Storage for decoupling application components.

	public static class QueueTrigger
 {
    [Function("QueueTrigger")]
    public static void Run(
      [QueueTrigger("myqueue-items", Connection = "")]  string myQueueItem,  ILogger log)
    {
        var logger = context.GetLogger("QueueTrigger");
        logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
    }
 }

The class and the method don’t have to be static.

Http Trigger in Azure Functions

HTTP triggers convert the incoming HTTP request message into an HttpRequestData object which is then passed to the function. This object contains data from the request such as Headers, Cookies, Identities, URL, and optionally the message Body. It represents the HTTP request object and not the request itself.

	
public static class HttpTrigger
 {
     [Function("HttpTrigger")]
     public static HttpResponseData Run(
         [HttpTrigger(AuthorizationLevel.Function,"get", "post")] HttpRequestData req,
         ILogger log)
     {
         var logger = executionContext.GetLogger("HttpTrigger");
         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;
     }
 }

Blob Trigger in Azure Functions

The function runs whenever a new file is created or updated in a blob container. The blob contents are input to the function.

Blob Containers are used for storing unstructured data (text or binary), so, really, any data (audio, video, backups, archiving, text…). Blobs are then grouped into “containers” and tied to storage accounts. They are massively scalable.

	
public static class BlobTrigger
{
    [Function("BlobTrigger")]
    public static void Run(
         [BlobTrigger("samples-workitems/{name}", Connection = "")] string myBlob,                            
         string name, 
         ILogger log)
    {
        var logger = context.GetLogger("BlobTrigger");
        logger.LogInformation($"C# Blob trigger function Processed blob\n Name: {name} \n   
               Data: {myBlob}");
    }
}

Timer Trigger in Azure Functions

This function enables us to schedule a recurring function execution at a specified time. It uses CRON expressions to provide timing information. The CRON expression consists of six parts – second, minute, hour, day, month, and day of the week and each part is separated by space. Find out more about CRON expressions and their usage.

Triggering is always cyclic; for example, the 0 0 0 * * * CRON expression triggers the function once every day. There is also a neat Cron expression generator, which makes things easier.

	
public static class TimerTrigger
{
    [Function("TimerTrigger")]
    public static void Run([TimerTrigger("0 */5 * * * *")]  
        MyInfo myTimer, 
        ILogger log)
    {
        log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
        log.LogInformation($"Next timer schedule at: {myTimer.ScheduleStatus.Next}");
    }
}

public class MyInfo
{
    public MyScheduleStatus ScheduleStatus { get; set; }
    public bool IsPastDue { get; set; }
}

public class MyScheduleStatus
{
    public DateTime Last { get; set; }
    public DateTime Next { get; set; }
    public DateTime LastUpdated { get; set; }
}

The example above includes a separate class that contains more info about the execution schedule, including previous and next execution times.

How to run Azure functions

We did everything up to this point in the terminal or the Command Prompt. Yes, you guessed it, we will run the functions in the same place. We should navigate to the file where our functions reside and enter the command:

	func host start

This command starts all the functions you have in the project.

If you want to run a specific function, enter:

	func host start --functions NameOfTheFunction

You can debug your function as you debug any .NET application.

Azure Functions – a cloud-based pioneer

Congratulations, you now know how to create and run Azure Functions in .NET 6! Azure Functions provides a sleek and powerful way of creating lightweight applications. This serverless solution allows us to maintain less infrastructure, write less code, and save on costs. Thanks to continuous improvements and feature enrichment, we will see more and more of them in the future.

If you want to dig deeper into the conversation and get more information about application development, let’s keep talking!