Creating your first C# Azure Function using Visual Studio

Azure Functions is an Azure cloud service that allows software developers to focus on writing code that reacts to events, without needing to concern themselves as much with the infrastructure that the code is running on. Azure Functions applications are serverless by nature and can scale to meet demand with minimal overhead and cost.

There are many possible use cases for Azure Functions and the flexibility that they provide, along with their ease of use, makes them a compelling option for everything from background tasks to Webhooks and API endpoints.

In this article, I am going to show you how to create your first C# Azure Function using Visual Studio and configure the function to trigger in response to a HTTP request.

Azure Functions benefits

Before I start walking through how you can get started with writing your first Azure Function, let me first provide a little bit more detail on Azure Functions and the potential benefits.

Further to my brief description at the beginning of the article, the text below provides a great overview of Azure Functions, taken from the Azure-Functions GitHub page.

Azure Functions is an event driven, compute-on-demand experience that extends the existing Azure application platform with capabilities to implement code triggered by events occurring in virtually any Azure or 3rd party service as well as on-premises systems. Azure Functions allows developers to take action by connecting to data sources or messaging solutions, thus making it easy to process and react to events. Azure Functions scale based on demand and you pay only for the resources you consume.

There are a couple of key takeaways to note from the above description.

Firstly, Azure Functions is ‘event driven’, meaning that code is executed in response to various events that are raised such as when a HTTP request is made, when a timer is fired, or when an item is added to a queue. The event-driven nature of Azure Functions makes it very versatile.

Secondly, Azure Functions ‘scale based on demand’, meaning that your functions can perform well under load since the required resources will be automatically provisioned whenever they are needed. There are also potential cost savings since you will only pay for the resources that are currently running.

Azure Functions increases developer productivity by removing the need to wire up the plumbing code that would otherwise be needed to connect to and react to events that take place within resources such as storage blobs and queues.

Azure architecture example featuring a Function App
Azure architecture example featuring a Function App (Source: Microsoft Docs)

While there are many different ways of triggering an Azure Function, one of the simplest triggers is the Http trigger which I’ll be focusing on in this article.

Creating a Function App

In the following sections, I will walk you through how to create a simple Azure Function that is executed whenever a HTTP request has been made.

Prerequisites

Before we begin, make sure you have installed Visual Studio. I will be using Visual Studio 2022 and .NET 6.0.

Make sure that you have selected the ‘ASP.NET and web development’ workload within the Visual Studio Installer interface to set up the necessary tooling and project templates.

This article assumes that you are running a modern Windows operating system such as Windows 10/11.

Project creation

The first thing we need to do is create an Azure Functions project, so let’s start by opening Visual Studio.

After Visual Studio has loaded you will be presented with the launch window that allows you to open an existing project or create a new one, as shown in the screenshot below.

Visual Studio - Launch window
Visual Studio – Launch window

Press the ‘Create a new project’ button within the launch window to get started.

Visual Studio - Create a new project
Visual Studio – Create a new project

After the ‘Create a new project’ page appears, type the text ‘functions’ into the search control. An item named ‘Azure Functions’ should appear under the list of available project templates, as shown above.

If there is more than one project template in the list, make sure that ‘Azure Functions’ is selected and press the ‘Next’ button.

Visual Studio - Configure your new project
Visual Studio – Configure your new project

On the ‘Configure your new project’ page, type something sensible into the ‘Project name’ field (e.g. ‘TodoFunctions’), update the Location and Solution name if required, and then press the ‘Create’ button.

Visual Studio - Create a new Azure Functions application
Visual Studio – Create a new Azure Functions application

On the ‘Create a new Azure Functions application’ page, make sure the ‘Http trigger’ item is selected, leave all of the other options at their default values, and then press the ‘Create’ button.

A new solution and Azure Functions project will now be created with a minimal set of files to start with, including a default function implementation.

Default Function exploration

In the following sub-sections, I will cover how the default function that is included in the Azure Functions project template works as an introduction to how Azure Functions are implemented.

Default Function definition

The Azure Functions project template includes a function class within a file named ‘Function1.cs’. The contents of this file are displayed below for reference.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
 
namespace TodoFunctions
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResultRun(
            [HttpTrigger(AuthorizationLevel.Function, "get""post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");
 
            string name = req.Query["name"];
 
            string requestBody = await new StreamReader(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);
        }
    }
}

Let’s break down the above code.

Run method signature

The Function1 class shown above features a single method named Run, this method contains the code that will execute whenever the Azure Function is called.

The Run method is decorated with the FunctionName attribute which allows us to customise the name of the function. However, the FunctionName attribute is also necessary in order for the method to be recognised as an Azure Function by the runtime.

The first parameter of the Run method is a HttpRequest which is in turn decorated with the HttpTrigger attribute. The HttpTrigger constructor accepts an AuthorizationLevel and allowed HTTP verbs. I’ll cover authorisation in my next article, but for now, we don’t need to worry about this, as authorisation isn’t enforced when debugging functions locally. The Route property is also specified for the attribute but is set to null. If specified, the Route property allows a custom route to be used instead of basing the route on the function name by convention.

The second parameter of the Run method is an ILogger which can be used to log output to the Console, Azure Application Insights, or other log stores.

Additional parameters can be injected into the Run method as needed. For example, you could add an IConfiguration parameter to get access to configuration values. This type of constructor injection works in a similar manner to a normal ASP.NET Core application.

Run method body

There is no magic going on in the code within the Run method, it is mostly using built-in .NET types to get its job done.

Firstly, the LogInformation method is used to log that the function has been called.

Next, the Query method of the HttpRequest object is used to retrieve the value of the ‘name’ query string parameter if specified.

The StreamReader class is used to read the Body of the HttpRequest asynchronously as a string and the JsonConvert library is then used to deserialise the request body string as a dynamic object.

Lastly, an OkObjectResult is returned with a response message string that is formatted depending on whether or not a ‘name’ has been specified in either the query string or the request body.

Trying out the default function

Now that we’ve covered what the default function does, let’s see if we can trigger a call to the function within our local development environment.

Press F5 within Visual Studio to debug the project.

The first time that you debug an Azure Function, a Windows Security Alert dialog that looks similar to the one shown below may appear.

If the Windows Security Alert dialog appears, I recommend that you keep the default settings selected and press the ‘Allow access’ button to prevent any issues when debugging.

After a few seconds, a Console window should appear that looks similar to the screenshot below.

Azure Function debugging
Azure Function debugging

When debugging an Azure Function locally, the Console window that appears is very helpful, displaying useful information such as the Function Runtime Version and all of the available Functions that can be called.

Log messages with their associated timestamps are also displayed so that we can easily see what’s going on.

For each function, the Console debugging window displays the allowable HTTP verbs, along with the URL that can be used to access the function.

In my case, the URL that can be used to call Function1 is as follows.

http://localhost:7208/api/Function1

If you open a web browser, such as Google Chrome, and paste the URL into the address bar, Function1 should be triggered and you will see the response message from the function rendered within your browser window.

Function1 response
Function1 response

In the above screenshot, notice that I have added the ‘name’ query string parameter to the end of the URL and specified ‘Jonathan’ as its value. This causes Function1 to display a personalised response message.

Sample Function creation

The default function we’ve looked at is a great start and is useful for getting familiar with how an Azure Function is composed. Next, let’s create a new function with our own implementation.

Sample Function definition

Within your Azure Functions project, add a new file named ‘GetTodos.cs’ and paste the following code into it.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
 
namespace TodoFunctions
{
    /// <summary>
    /// Get Todos Function Class.
    /// </summary>
    public static class GetTodos
    {
        #region Classes
 
        /// <summary>
        /// Represents a 'Todo' item.
        /// </summary>
        private class Todo
        {         
            public int    Id        { getset; }
            public int    UserId    { getset; }
            public string Title     { getset; }
            public bool   Completed { getset; }
        }
 
        #endregion
 
        #region Fields
 
        /// <summary>
        /// Holds an in-memory list of Todo items for simulation purposes.
        /// </summary>
        private static IEnumerable<Todo> _todos = new List<Todo>
        {
            new Todo { Id = 1, Title = "Buy milk", UserId = 1 },
            new Todo { Id = 2, Title = "Leave out the trash", UserId = 2 },
            new Todo { Id = 3, Title = "Clean room", UserId = 2 }
        };
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Gets a collection of Todos.
        /// </summary>
        /// <param name="request">The current HTTP Request</param>
        /// <param name="logger">The logger interface</param>
        /// <returns>A response body containing the Todos, if any were found</returns>
        [FunctionName(nameof(GetTodos))]
        public static async Task<IActionResultRun(
            [HttpTrigger(AuthorizationLevel.Function, "GET", Route = "todos")]
            HttpRequest request,
            ILogger     logger)
        {
            logger.LogInformation("'{0}' request received", nameof(GetTodos));
 
            IActionResult result = new NotFoundResult();
 
            // Check if a User ID was specified in the query string.
            bool userIdSpecified = int.TryParse(request.Query["userId"], out int userId);
 
            // Simulate an async database call.
            var todos = await Task.Run(() =>
                userIdSpecified ?
                    _todos.Where(t => t.UserId == userId:
                    _todos);
 
            // Return the Todos, if any are found.
            if (todos?.Any() == true)
            {
                logger.LogInformation("Found {0} todos", todos.Count());
 
                result = new OkObjectResult(todos);
            }
 
            logger.LogInformation("'{0}' request completed", nameof(GetTodos));
 
            return result;
        }
 
        #endregion
    }
}

The above code implements a simple REST API endpoint that returns a collection of ‘Todo’ items in JSON format. The list of todos that are returned will vary based on the value of a ‘userId’ query string parameter.

Let’s break down the above code for this function.

Sample class members

The GetTodos class is the container for the function’s Run method.

For the sake of simplicity, I have embedded a Todo class, which represents the shape of a todo item, within the GetTodos class.

I’ve also initialised a static IEnumerable collection of todos within the class which is used for simulation purposes instead of accessing the file system or a database.

Sample method signature

The Run method signature within GetTodos is very similar to the one within Function1 with just a few minor differences that are worth pointing out.

Firstly, I’m using the nameof expression instead of specifying a string directly within the FunctionName attribute.

Secondly, I’ve restricted the request to only allow HTTP GET requests.

Thirdly, I’ve specified a custom route via the Route property, meaning that the URL for the function will end with ‘/todos’ instead of ‘/GetTodos’.

Sample method body

In the method body, the LogInformation method is used to log that the function has been called and an IActionResult variable is set up with an initial value of NotFoundResult. This will result in a HTTP 404 response being returned to the client if we don’t have any todo items to return.

Next, the method checks for a query string parameter named ‘userId’ and stores its value in a userId variable.

The await Task.Run method simulates an asynchronous database query and uses LINQ to filter the results of the in-memory collection of todo items if a ‘userId’ has been specified.

If there are any todo items to return, the code logs how many todo items were found and then sets the IActionResult variable to an OkObjectResult that will serialise the todos collection to JSON in the response.

Lastly, the code logs that the request has been completed and returns the result.

Note that the code contained within the Run method is purely for demonstration purposes. When implementing a production-grade function, you’ll want to include as much validation and error handling as is needed for your scenario.

Trying out the sample function

As we did previously, press F5 within Visual Studio to debug the project.

You should now see a new function listed with the Console debug window named ‘GetTodos’.

Try copying the URL for the sample function into your web browser. For example, the URL below will call the ‘GetTodos’ function, passing in a ‘userId’ of 2.

http://localhost:7208/api/todos?userId=2

The result that is returned should look similar to the following.

[{"id":2,"userId":2,"title":"Leave out the trash","completed":false},{"id":3,"userId":2,"title":"Clean room","completed":false}]

You can also try calling the function from Postman which will make it much easier for you to test Azure Functions that support other HTTP verbs such as POST and DELETE.

I encourage you to try implementing functions that are relevant to your problem domain as the next step and consider experimenting with other simple triggers such as the Timer trigger.

In my next article, I will be covering how to publish functions that have been created in Visual Studio to Azure and look at how authorisation works for Azure Functions.

Summary

In summary, I trust that this article has helped you to get started with Azure Functions and has given you a reasonable idea of what they are like to work with.

There are many different ways to create Azure Functions; you can create functions using other languages such as Python and JavaScript, and you can even write and test functions directly within the Azure Portal. For projects that are likely to grow in size, I find that it’s often best to start from the IDE and as you’ll see in my next article, getting the functions published to Azure is quite straightforward.

Despite the fact that it’s quick to get started with, I’ve only scratched the surface of what’s possible with Azure Functions, so I definitely recommend checking out the official Microsoft Docs for a deeper dive into what’s possible. Developing a good understanding of Azure resources such as Azure Event Grid and Azure Service Bus will also be very beneficial (if you intend to use these) since one of the key benefits of Azure Functions is how easy it is to trigger function code based on events that occur within these and other resources.

If there are specific aspects of Azure Functions that you’d like me to cover in more depth, please feel free to drop me a message on my contact form or leave a comment and I’ll look into creating some more related content.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments

This site uses Akismet to reduce spam. Learn how your comment data is processed.