Azure Back to School 2020: Beginning with Azure Functions in Visual Studio
Azure Back to School
Hi there!
This article is my contribution to Azure Back to School 2020, a cool initiative by Dwayne Natwick. There’s different content for every day of September, so make sure to check out the other articles!
Tip: learn everything you need to know about Azure Functions with these courses.
A few requirements
You don’t need a lot of knowledge upfront. I’m assuming you know your way around Visual Studio and C#, and that you have some basic experience with Azure and the Azure portal. But that’s about it. You will learn the rest right here.
Visual Studio
Since we’re learning about using Azure Functions with Visual Studio, you should have Visual Studio installed. You need 2019 to be able to use the latest Functions SDK and .NET Core bits. You can get the community version for free here.
Make sure to include the Azure Development workload when installing Visual Studio, or add it later with the Visual Studio installer on your machine:
Azure account
You can run everything describe here locally, but to publish it to the cloud, you need an Azure account. If you don’t have one yet, you can start with a free one here.
We’re building a simple, low impact app, that will incur very little cost when running on Azure. But it goes without saying that I take no responsibility for any unexpected costs or other problems that might arise from (incorrectly) following the steps in this article.
What is Azure Functions?
Azure Functions is a serverless compute service that enables you to run a piece of code on-demand, in response to an HTTP request, a timer, or an event from another (Azure) service.
One Function App can contain multiple functions and can spin-up more resources or instances when needed.
Output can be directed to different kinds of data stores and other Azure services, like Cosmos DB and Event Hub.
I use them a lot because they are extremely scalable, and very easy to set up.
While it’s possible to create and edit functions directly in the Azure portal, I prefer to use Visual Studio, since it offers all the IDE benefits you’re used to, like running and debugging locally, managing NuGet packages, adding project references, creating unit tests, and source control integration.
About the demo
Throughout this article, we’ll be building a little demo Function App to show off two of the most used types of Azure functions:
- A timer triggered function that writes entries to a log file.
- An HTTP triggered function that exposes an URL that you can call to retrieve the log file entries.
You can build the project from scratch with the steps outlined below, or get the complete code from GitHub.
Creating the project
OK. Let’s get started!
Open Visual Studio and create a new Azure Functions project. To quickly find the right template, search for it in the search field:
Click Next and enter a project name and location. Let’s say we call it AzureFunctionsBlogApp.
Now click Create to go to the function settings. You can optionally choose to straightaway add a function to the new project. Take a moment to browse through the list to get an idea of the possibilities.
Pick Empty, to start with a blank project. Also make sure to choose the latest Azure Functions version (v3 with .NET Core).
The Functions runtime uses Azure storage to maintain some housekeeping data, like session info. As you can see on the right of the above screenshot, by default, it uses the local storage emulator, which is fine in our case. We’ll see how to set a real storage account when publishing the app to Azure.
Now go ahead and hit Create. Your project and solution should be created in Visual Studio.
Below the project name in Solution Explorer, expand Packages for a moment and notice the Microsoft.NET.Sdk.Functions package. This is the Azure Functions SDK and includes all the bits needed to create and build Function Apps.
Adding the timer function
With the Function App project in place, let’s add our first function. We will start with the timer triggered function that will write entries to a log file.
Right-click the project in Solution Explorer and select Add / New Azure Function…
Now enter the name OnWriteToLogTimer.cs and click Add.
This will take you to the function type dialog which has a bunch of options available.
Select Timer trigger and enter a schedule. The schedule takes a CRON expression and lets you specify when the function should be triggered (each hour, daily, weekly, …). It’s pretty flexible but can be a little tricky to work with. I always use this cool cheat sheet by Armin Reiter.
In this instance, I have entered */10 * * * * *. This will trigger the function every 10 seconds.
Now click OK to add the function and double-click it in Solution Explorer to go to the code, which is shown below.
using System; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; namespace AzureFunctionsBlogApp { public static class OnWriteToLogTimer { [FunctionName("OnWriteToLogTimer")] public static void Run([TimerTrigger("*/10 * * * * *")] TimerInfo myTimer, ILogger log) { log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); } } }
Notice the FunctionName attribute. It holds the name for the function, as shown in the Azure Portal when published, and in log files etc. I find it’s good practice to use the same name for this attribute and for the class holding the function code, but that’s not required.
There’s a static function called Run which is the entry point that is called each time the timer schedule is triggered (10 seconds in this case). It has a myTimes parameter that gives you some info on the timer schedule at runtime. The parameter has a TimerTrigger attribute that specifies the CRON expression for the timer. Don’t worry, we’ll see how to not make this hard-coded later on.
There’s also a log parameter that lets you record info and errors in the Azure instrumentation. You can see in the above code that log is used to write the current time every time the function is triggered.
Let’s start the app to make sure everything is working. Hit F5 to start it in debug mode.
The first time you do this, you might get a warning from your Windows Firewall, because the local Functions runtime is trying to make network calls. If that happens, set it to allow access for private networks.
After a few moments the runtime console should appear and start running the timer function. It will show that the function is called every 10 seconds:
Nice! At this point we have created the Azure Functions project and added a timer function.
Writing to the log file
Our timer function doesn’t do anything useful yet, so let’s add some logic. As described before, it will write to a log file, which we’ll retrieve later on with another function.
The code for this is pretty simple, and we could add it directly to the Function App project. But, for the sake of proper standards and testability, let’s add a second project that will act as a reusable class library.
Right-click the solution in Solution Explorer and choose Add / New project…
Search for the Class Library (.NET Standard) template. Although the Functions project uses .NET Core, we will use .NET Standard for the library. This makes it much more reusable among different types of .NET projects.
Now hit Next and give the class library the name AzureFunctionsBlogLib.
Click Create to add the new project to the solution.
Right-click the AzureFunctionsBlogApp project and choose Add / Project reference…
In the Reference Manager, select the new class library and hit OK to add it as a reference to the Functions project. Now we can use the class library from the Function App functions.
Add a new class to the class library and call it LogUtility. This class will hold some static functions to write and read the log file. The code is below and should be easy to understand.
using System; using System.IO; namespace AzureFunctionsBlogLib { public static class LogUtility { private const string logFileName = "log.txt"; public static void AppendToLogFile(string logFileDirectoryName, string functionName) { if (string.IsNullOrEmpty(logFileDirectoryName)) { throw new ArgumentNullException(nameof(logFileDirectoryName)); } if (string.IsNullOrEmpty(functionName)) { throw new ArgumentNullException(nameof(functionName)); } var fullLogFileName = Path.Combine(logFileDirectoryName, logFileName); File.AppendAllText( fullLogFileName, $"Function {functionName} was called @ {DateTime.Now}{Environment.NewLine}"); } public static string GetLogFileContent(string logFileDirectoryName) { if (string.IsNullOrEmpty(logFileDirectoryName)) { throw new ArgumentNullException(nameof(logFileDirectoryName)); } var fullLogFileName = Path.Combine(logFileDirectoryName, logFileName); var content = File.Exists(fullLogFileName) ? File.ReadAllText(fullLogFileName) : "(no content yet)"; return content; } } }
The AppendToLogFile function uses System.IO.File.AppendAllText to append a line with the given function name and the current date to the log file, called log.txt. If the file doesn’t exist, AppendAllText will automatically create it.
The GetLogFile function reads all content from the same log file with System.IO.File.ReadAllText. If the file doesn’t exist (if the timer function hasn’t been triggered yet), it will return a string indicating there’s no content yet.
Notice that both functions take a logFileDirectoryName parameter, which indicates the directory where the log file should be saved.
Now, to actually write to the log file, go back to the code for the OnWriteToLogTimer function, and change it to the following.
using System; using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; using AzureFunctionsBlogLib; namespace AzureFunctionsBlogApp { public static class OnWriteToLogTimer { [FunctionName("OnWriteToLogTimer")] public static void Run( [TimerTrigger("*/10 * * * * *")] TimerInfo myTimer, ILogger log, ExecutionContext executionContext) { log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}"); LogUtility.AppendToLogFile( logFileDirectoryName: executionContext.FunctionAppDirectory, functionName: executionContext.FunctionName); } } }
You’ll notice two changes.
Firstly, there’s a new parameter of type ExecutionContext. This is a handy class that exposes some info like the file system directory from where the Function App is running.
The second change is the call to the AppendToLogFile function in our class library. We pass it the Function App directory and the name of the executing function. As we saw in the class library code, this function name and the current time will be appended to the log file.
Now run the Function App again, let it run for a minute or so and stop it. The log file log.txt should be created and it should contain a few lines. When running locally, the Function App directory from the ExecutionContext will point to the project’s Debug directory. So you should be able to find it in <Your solution directory>\AzureFunctionsBlogApp\bin\Debug\netcoreapp3.1.
It will contain an entry for every 10 seconds:
Function OnWriteToLogTimer was called @ 8/24/2020 11:54:40 AM Function OnWriteToLogTimer was called @ 8/24/2020 11:54:50 AM Function OnWriteToLogTimer was called @ 8/24/2020 11:55:00 AM Function OnWriteToLogTimer was called @ 8/24/2020 11:55:10 AM Function OnWriteToLogTimer was called @ 8/24/2020 11:55:20 AM
Using config
Our timer function is working great, but there’s one thing that’s not ideal, and that’s the hard coded timer schedule in the TimerTrigger attribute. If we keep it like this, you would need to redeploy your function every time you want to change the schedule.
Luckily, there’s a better way, by using the config file. The Function App project has a local.settings.json file which contains settings that are applied when running the project locally. Open it and add the OnWriteToLogTimerSchedule entry with the CRON expression value as shown here:
{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet", "OnWriteToLogTimerSchedule": "*/10 * * * * *" } }
Now, to bind this value to our function, go back to the code for OnWriteToLogTimer and edit the TimerTrigger attribute to contain the config key surrounded by % %, like this:
public static void Run( [TimerTrigger("%OnWriteToLogTimerSchedule%")] TimerInfo myTimer,
Now the trigger schedule is retrieved at runtime from the config file, which makes it much easier to update.
Adding the HTTP trigger function
We’re making good progress! Our timer triggered function is done, and the log file is being written.
To showcase another popular type of Azure Function, let’s add an option to retrieve the log file entries via URL.
Add another function to the project, and call it OnGetLogHttpTrigger.cs.
This time, choose the Http trigger type, and select the Anonymous authorization level. This makes it easy for us to test the function URL without having to bother with security and tokens. Of course, in real-world applications you should always implement proper authorization.
Click OK to add the function. The code will look like this:
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 AzureFunctionsBlogApp { public static class OnGetLogHttpTrigger { [FunctionName("OnGetLogHttpTrigger")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "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); } } }
As you can see it looks for a name parameter in the function URL querystring and returns it in the response message through OkObjectResult, which sends back a HTTP 200 status.
Let’s verify this. Run the Function App again, and in the console, find the function URL and copy it:
Now, while the app is running, open a browser, paste in the URL and add a value for the name querystring parameter. You will get it back in the response:
This confirms that the HTTP trigger is working. Now, let’s change it to return the content from our log file. Modify the function code as follows:
using AzureFunctionsBlogLib; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.Extensions.Logging; using System.Threading.Tasks; namespace AzureFunctionsBlogApp { public static class OnGetLogHttpTrigger { [FunctionName("OnGetLogHttpTrigger")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log, ExecutionContext executionContext) { log.LogInformation("C# HTTP trigger function processed a request."); var logContent = LogUtility.GetLogFileContent( logFileDirectoryName: executionContext.FunctionAppDirectory); return new OkObjectResult(logContent); } } }
It uses LogUtility.GetFileContent from our class library to read the log file and return it.
You might also have notice a little optimization I did. I removed the “post” method from the HttpTrigger attribute, since we’re only using the HTTP GET method by calling it in the browser. It’s always best to not expose methods and options that are not needed:
public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
Now run the Function App again and go back to the function URL in your browser. No need for a querystring anymore. You should see the content from the log file. If you keep the app running and refresh the browser, a new line should appear every 10 seconds:
Publishing to Azure
Cool! Our first Function App is done. It’s all working fine locally, but of course, we want to see it running in the cloud. So let’s see how we can get it there.
Right-click AzureFunctionsBlogApp in Solution Explorer and click Publish…
A wizard appears to guide you through the process of setting up a new publish profile. Choose Azure and click Next.
You can host your app in different kinds of hosts. Select Azure Function App (Windows) for our .NET Core app and click Next.
The next screen lets you specify the Azure subscription that will hold the Function App. Select the Azure account you want to use with the account selector at the top of the dialog and choose the right subscription.
Next, use the button below to add a new Azure Function, but first make sure that Run from package file is unchecked. This is needed in our scenario, because otherwise the Function App would run in a read-only environment, and it wouldn’t be able to create the log file.
In the dialog that appears for the new Function App properties, give the Function App a unique name. AzureFunctionsBlogApp will likely be taken (by me), so make it something else.
Also choose or create a resource group and a storage account.
Location isn’t that important for our little demo, but for a real-world app, you should pick the one with the lowest latency to your (or your users) location.
Plan Type is an important choice. Consumption will bill you for the number of executions, while App Service lets you select a specific performance plan with a certain price, which you must pay regardless if you have functions running or not. There are more differences, as describe here. For our demo, Consumption is the best (and very cheap) choice.
Now click Create, select the new Function App from the resource picker and hit Finish:
Now we have the publish profile added, which points to an Function App in your Azure subscription, there’s one more thing to do.
Remember that we added a config setting for the timer function schedule in the config file local.settings.json. As the name of the file indicates, these settings are local, and won’t be published to Azure. There’s an easy way to configure the Azure (live) settings however, by clicking Manage Azure App Service settings in the publish dialog:
You’ll get a handy list of all local and remote settings. For OnWriteToLogTimerSchedule, insert the value from local to remote, or enter it by hand. Of course, you can specify a different interval for remote, if you think 10 seconds is a bit short.
Running in Azure
We have our app published in Azure, so let’s see if it’s working.
Go to the Azure portal and open your Function App. A quick way to find it, is to search for function in the top search bar:
There’s a bunch of stuff in the left panel, but we’re only interested in seeing if the functions are working. Click Functions in the left panel and you should see our two functions. Open OnWriteToLogTimer.
This takes you to the management for the timer triggered function, which should be running and writing entries to the log file every 10 seconds. To verify, click Code + Test in the left panel en expand Logs. You will see an execution of the function every 10 seconds, just like when we were running it locally.
The last thing to check is the HTTP triggered function. Go back to the function list and open OnGetLogHttpTrigger.
Under Code + Test, there’s a Get function URL button. This will give you the public URL pointing to the function. Click the button and copy the URL.
Now paste the URL in your browser, and you should see the log entries appear:
Where to go from here
We’ve seen quite a bit of stuff, but we’ve only scratched the surface of what Azure Functions can do! Here are some pointers for you to keep learning.
Bindings
Besides timer and HTTP triggered, there are many other types of functions. The most powerful are those with automatic bindings to other services, like Azure storage and IoT hub. Check out my other article, to see how easy it is to store form input in Cosmos DB with an output binding. It will also show you how to configure access restrictions to an Azure Function that has a public URL.
Durable functions
Our demo Function App runs on a Consumption Plan in Azure. This is cheap and very scalable, but it doesn’t keep state between executions.
With durable functions, you can write stateful workflows, while Azure does the heavy lifting of managing state and checkpoints in the background. It can get complex but it’s very powerful.
Secure config
You should always store sensitive information, like connection strings and security keys in a safe place. Read this article by Tobias Zimmergren to see how to reference Azure Key Vault from Azure Functions. And while you’re at it, check out his best practices.
Monitoring
Any real-world application should have a rigorous monitoring strategy. Like most Azure services, you can hook up Azure Functions to Application Insights.
And so much more!
Azure Functions has a lot more in store than what I can show you in one article. Go to the Microsoft Docs landing page to learn about the possibilities and make sure to follow the Azure Functions team on Twitter to stay on top of all updates.
The GitHub project page for the Azure Functions Build SDK is here.
Thanks for reading!
Cleanup
It should be obvious, but just to make sure…
Don’t forget to remove the Function App resources (app, resource group, storage, …) from your Azure account when you’re done playing with it, to prevent unexpected costs.