Create Http Triggered Azure Function in ASP.NET Core

Azure functions runs in a serverless environment and is automatically scaled if they are hosted in a consumption plan (hosting plan). A consumtion plan means that resources is allocated when they are needed and that you only pay when your functions is running.

Azure functions is microservices, independent microservices is a great way to scale your website and to improve modularity for your project. A http triggered Azure function runs when someone calls its url, you can call a function from your code, from an Azure Logic App or from Postman for example.

Azure functions is publicly available (AuthorizationLevel.Anonymous) as default, you can restrict access to a function by choosing another authorization level. Set the authorization level to Function (AuthorizationLevel.Function) to require a function key as a Code parameter in the request. You can create function keys in Azure Portal for each function.

Project templates and NuGet packages

Install Azure Functions and Web Jobs Tools under Tools/Extensions and Updates in Visual studio to get templates and tools for azure functions. Create a new project and use Azure Functions as the template. You will also need to install Microsoft.NET.Sdk.Functions and Microsoft.Azure.Functions.Extensions to be able to use dependency injection in the project.

Settings

Your project have a host.json file and a local.settings.json file. You always need a connection string to a storage account for AzureWebJobsStorage in your application settings. The contents of the host.json file is shown below, function timeout is set to 10 minutes. An ordinary azure function can not run longer than 10 minutes, create a durable function if you have long running tasks.

{
  "version": "2.0",
  "functionTimeout": "00:10:00",
  "logging": {
    "logLevel": {
      "default": "Information"
    }
  }
}

Startup

Our startup class is used to register options and repositories so that we can use dependency injection in our project.

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(Fotbollstabeller.Functions.Startup))]

namespace Fotbollstabeller.Functions
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            // Create options
            builder.Services.Configure<DatabaseOptions>(options =>
            {
                options.connection_string = Environment.GetEnvironmentVariable("SqlConnectionString");
                options.sql_retry_count = 3;
            });

            // Add repositories
            builder.Services.AddSingleton<IDatabaseRepository, MsSqlRepository>();
            builder.Services.AddSingleton<IGroupRepository, GroupRepository>();
            builder.Services.AddSingleton<IFinalRepository, FinalRepository>();
            builder.Services.AddSingleton<IXslTemplateRepository, XslTemplateRepository>();
            builder.Services.AddSingleton<IXslProcessorRepository, XslProcessorRepository>();

        } // End of the Configure method

    } // End of the class

} // End of the namespace

Function

This class only contains one function, you can add multiple functions in a class. You need a function key to call this function in production, the uri will look like: https://myfunction.com/api/updategroupsandfinals?Code=XXXXXXXXXXX. No function key is needed during development.

using System;
using System.IO;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;

namespace Fotbollstabeller.Functions
{
    public class UpdateGroupsAndFinals
    {
        #region Variables

        private readonly ILogger logger;
        private readonly IXslProcessorRepository xsl_processor;

        #endregion

        #region Constructors

        public UpdateGroupsAndFinals(ILogger<UpdateGroupsAndFinals> logger, IXslProcessorRepository xsl_processor)
        {
            // Set values for instance variables
            this.logger = logger;
            this.xsl_processor = xsl_processor;

        } // End of the constructor

        #endregion

        #region Function

        [FunctionName("UpdateGroupsAndFinals")]
        public IActionResult Update([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest request)
        {
            // Log the start of the function
            this.logger.LogInformation($"Application started at: {DateTime.UtcNow}");

            // Get header values
            string header = request.Headers["Host"];

            // Get query paramter
            string query = request.Query["Key1"];

            // Get form value
            string form_value = request.Form["FormKey1"];

            // Get the entire body
            string body = "";
            using (StreamReader reader = new StreamReader(request.Body))
            {
                body = reader.ReadToEnd();
            }

            // Do the work
            this.xsl_processor.UpdateGroups();
            this.xsl_processor.UpdateFinals();

            // Log the end of the function
            this.logger.LogInformation($"Application ended at: {DateTime.UtcNow}");

            return new OkObjectResult("Done");

        } // End of the run method

        #endregion

    } // End of the class

} // End of the namespace

Durable function

You will need a durable function if you have a long running task. To add a durable function: right-click your project file in Visual Studio, select Add/New Azure Function… and choose the Durable Functions Orchestration template. The uri to the method will look like: https://myfunction.com/api/DurableFunction_HttpStart.

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace Fotbollstabeller.Functions
{
    public class DurableFunction
    {
        #region Variables

        private readonly ILogger logger;
        private readonly IXslProcessorRepository xsl_processor;

        #endregion

        #region Constructors

        public DurableFunction(ILogger<DurableFunction> logger, IXslProcessorRepository xsl_processor)
        {
            // Set values for instance variables
            this.logger = logger;
            this.xsl_processor = xsl_processor;

        } // End of the constructor

        #endregion

        #region Functions

        [FunctionName("DurableFunction")]
        public async Task<List<string>> RunOrchestrator([OrchestrationTrigger] DurableOrchestrationContext context)
        {
            // Create a list with outputs
            List<string> outputs = new List<string>();

            // Log the start of the function
            this.logger.LogInformation($"Application started at: {DateTime.UtcNow}");

            // Call activities
            outputs.Add(await context.CallActivityAsync<string>("DurableFunction_Update", "Groups"));
            outputs.Add(await context.CallActivityAsync<string>("DurableFunction_Update", "Finals"));

            // Log the end of the function
            this.logger.LogInformation($"Application ended at: {DateTime.UtcNow}");

            // Return a list
            return outputs;

        } // End of the RunOrchestrator method

        [FunctionName("DurableFunction_Update")]
        public string Update([ActivityTrigger] string name)
        {
            // Do the work
            if(name == "Groups")
            {
                this.xsl_processor.UpdateGroups();
            }
            else if (name == "Finals")
            {
                this.xsl_processor.UpdateFinals();
            }
            
            return $"Done {name}";

        } // End of the Update method

        [FunctionName("DurableFunction_HttpStart")]
        public async Task<HttpResponseMessage> HttpStart([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")]HttpRequestMessage req,
            [OrchestrationClient]DurableOrchestrationClient starter)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync("DurableFunction", null);

            this.logger.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);
        }

        #endregion

    } // End of the class

} // End of the namespace

Publish application

Right-click your project file and click on Publish… to create Azure resources and a template to publish your application. Create or select an existing hosting plan for your application, you can have many applications in the same hosting plan. Choose a consumption plan if you want automatic scaling.

Leave a Reply

Your email address will not be published. Required fields are marked *