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.