Create a complete Azure Function project in .NET 5 using VSCode

Manuel Pinto
9 min readAug 31, 2021

--

We all know functions must be simple, single purpose pieces of code. Simple, however, does not mean sloppy and should be treated with the same standards as any other big sized app. Templates can get you started but once you try applying best practices and clean approaches like unit testing and/or dependency injection these will not work out of the box and require additional work. This guide goes through the transformation of a template based function into complete app with modern standards following a practical example of creating a stock market data provider.

If using .NET6 please see updated article for .NET6 with extra features.

Steps covered in the guide:

  • Create function from template using VSCode
  • Test function using Postman
  • Add support for Dependency Injection
  • Add multiple functions to the same project
  • Add Unit Tests and run them in VSCode

The Application

In order to test the different capabilities of a function we are going to create a practical example that will query an external API in order to get and return some data. The final code used is available in github. The data in the example will be stock related information where the Open and Close price for a given stock symbol will be returned. There are multiple sources of stock data, we will use the free-tier API of finhub where the data is available through a single call as illustrated in the following diagram for the Open case.

Diagram with the flow of the Function call to return the Open stock price for a given symbol

The function will then be responsible to call the external API, collect and transform the data so that only the Open stock price is returned to the client. As simple as this application looks there are non trivial topics involved like asynchronous calls or the unit testing of the result mapping logic.

Create an Azure function using Visual Studio Code

Open Visual Studio Code in your repo path. Using the generation wizard helps set up the initial structure that we can use as starting point. (Microsoft tutorial for creating a function and and triggering the deployment to Azure)

Install Azure Functions SDK and VS Code extension

This extension contains the required dependencies for generating an azure function project. If you are interested in a command line approach please see this guide.

Create new function project using template

Move to the Azure Function extension tab and click on the little thunder to start the template project generation.

Accept the prompt and move on to the wizard.

At this stage the wizard will ask you for the different settings of your function. These will be the chosen settings for this guide.

  • Language: C#
    Use whatever language you would like to work with.
  • .NET Runtime: .NET 5 isolated
    At time of writing, the .NET 5 is the latest available.
  • Template: HttpTrigger
    Azure functions can have multiple triggers (see this). We will be using the HttpTrigger template that triggers the execution when the function specific endpoint is hit.
  • Function Name: GetOpenStockPriceForSymbol
    As example let us create a function that returns the Open price for a given stock symbol.
  • Namespace: “Example.Function
    Something meaningful to your function space.
  • Access Rights: Anonymous
    Access authentication is something that you should pay special attention in order to develop a secure function. For the sake of simplicity, Anonymous will accept every request made to the public endpoint.

Hit F5 and to make sure that the application has everything required to run. If you are interested in knowing what commands are being used in the background to run the function, have a look at .vscode/tasks.json.

If you see this message in the terminal it means you are set and your function is exposed locally.

Go ahead, open the browser to the provided function URL.

Awesome! Now let’s see an easier way to consume and test the endpoints.

Use Postman to send requests to the function API

Postman is a popular tool for consuming API endpoints. Let us create a func-ex-base-url var with the localhost address in the Local Env. This is helpful to test locally and easily change to a web test/prod environment.

…and voilà the same result we had in the browser means we are ready to go!

Add support for Dependency Injection

Let’s dive in to the function code.

  • Function name comes from the [Function] attribute
  • Function is declared as static

Constructor dependency injection will not work in a static class.

  • Logger dependency is gathered through the FunctionContext

Let us create the following code architecture with multiple dependencies that implement the application.

The first obvious dependency we will need in the Function is the interface IStockDataProvider which itself is dependent on the FinhubHttpClient and FinhubDataMapper. There is also an HttpHelper used only within the Function.

Enable constructor Dependency Injection.

(see MS documentation on DI)

In Program.cs, register the Interfaces and the HttpClient.

.ConfigureServices(s =>                
{
s.AddScoped<IFinhubDataMapper, FinhubDataMapper>();
s.AddScoped<IStockDataProvider, FinhubProvider>();
s.AddScoped<IHttpHelper, HttpHelper>();
s.AddHttpClient<FinhubHttpClient>();
})

The final Program.cs should look something like this:

Refactor folder structure

Let us refactor the file structure by moving the code within a /src folder with shared content in /Function.Domain/ and Functions in /Functions/ folder. This will help us separate Functions from shared code and Unit tests in different projects

└── src
├── Function.Domain
│ ├── Models
│ │ ├── FinhubStockData.cs
│ │ └── StockData.cs
│ ├── Providers
│ │ ├── FinhubProvider.cs
│ │ └── IStockDataProvider.cs
│ └── Services
│ │ ├── FinhubDataMapper.cs
│ │ ├── HttpClients
│ │ │ └── FinhubHttpClient.cs
│ │ └── IFinhubDataMapper.cs
│ └── Helpers
│ ├── HttpHelper.cs
│ └── IHttpHelper.cs
├── Functions
│ └── GetOpenStockPriceForSymbol.cs
├── Program.cs
├── azure-function-example-csharp.csproj
├── host.json
└── local.settings.json

VSCode may complain about not finding the project file as these have moved. Please adjust the path in .vscode/tasks.json, ex:

"options": {
"cwd": "src/"
},

At this stage your function should work the same as before adding the DI. Give it a try with postman.

Inject dependencies to the main function

To finalise the application, the only thing missing is to add the logic and injected properties to the main function file

Differences:

  • Class and function are no longer static
    This is required to add constructor Dependency Injection. The Dependency IStockDataProvider is added to the constructor of the function class to be used within the main method.
  • Asynchronous calls
    The function return type is now of “async Task<HttpResponseData>” this enables us to call an external API in an asynchronous manner.
  • Route template and URL parameter parsing
    If not provided (i.e = null) route template will assume the function name as we have seen before. In this case and to illustrate how the route template can be modified and URL parameters can be parsed to the function code, let us use the following route attribute.
Route = "stock-price/open/{symbol}"

This overrides the api endpoint to /api/stock-price/open/{symbol} and by using brackets {} in the route we can map it to variables like the symbol string defined as parameter of the Run method.

Its time to give it a try! If you are following this example using finhub, make sure to add your account token to the local.settings.json

"Values": {
(...)
"finhub_api_baseUrl": "https://finnhub.io/api/v1/",
"finhub_api_token": "<your_secret_key>"
}

these will be used by the FinhubHttpClient.cs

Modify your Postman call to use the new endpoint.

Lets try with Apple stock (symbol = AAPL)

…and there it is!

Support multiple functions

Now that we have separated shared code in the Domain folder, and Functions in another, adding a new function is as simple as creating a new class in the /Functions folder. Let us create a function for getting the Close price.

This function will be very similar to to the previous one, make sure to modify the Route parameter as functions need to have a unique route and the Close price maps to the PreviousClose value in the StockData model.

Starting the project will now expose the two new functions.

Hit the new endpoint

the previously close price is returned!

Add Unit tests

Our API wouldn't be complete without the base of the testing pyramid blocks, the Unit tests! Remember that these should ideally be done before the implementation of the Interfaces (see TDD).

The tests should be stored in a different project and due the way we’ve been structuring our app this is something that is really easy to do. .Net provides templates for the most used testing frameworks. For this example we will create unit test project using xUnit. (see “$dotnet new -l” for a list of included templates)

Create folder for project ex: {function-root}/tests/unit-tests navigate to the folder and run the command

/tests/unit-tests % dotnet new xunit

This will create the .csproj with the required xUnit dependencies and a sample test class called UnitTest1.cs.
The classes that we will be interested in unit testing are the ones for shared code in the Function.Domain namespace sitting in /src/Function.Domain. This code has its own dependencies part of another project (marketsim-func-finhub.csproj) so in order to access these classes we need to add a reference to the main function project. This can easily be done by navigating to the folder where the unit test project sits and add reference via the command

tests/unit-tests % dotnet add reference ../../src/azure-function-example-csharp.csproj

Examine the unit-tests.csproj and confirm that the reference was added

<ItemGroup>
<ProjectReference Include=”..\..\src\azure-function-example-csharp.csproj” />
</ItemGroup>

We have now the tools to start unit testing our application. This example does not have very complex logic units but for the sake of simplicity let us focus on the mapper class (FinhubDataMapper) responsible for mapping the results from the Finhub API to a more human readable naming to be returned by the Function.

To test this logic we just need to

  • Arrange the FinhubStockData model
  • Act by inputting this model in the system under testing (FinhubDataMapper.MapToStockData)
  • Assert that the output model is correctly mapped

Run the tests

Tests can be run in the command line with the command

tests/unit-tests % dotnet test

This should run and return the test results.

Run the tests in VSCode

If you want to trigger/debug specific tests, and have a more visually appealing way to check the status of your tests there are some VSCode add-ons that can help us achieve this. .NetCore test Explorer is a basic but capable one.

Once installed, change the test discovery path to the test folder (.vscode/settings.json) as by default it will look for projects in the root folder.

“dotnet-test-explorer.testProjectPath”:”**/*-tests.@(csproj|vbproj|fsproj)”

Once discovery is complete, the tests should now be available in the ‘Testing’ tab.

Function ready!

You are all set, this concludes the guide. Use the github repo to browse the files used in this example. Hope this will help you get started with a cleaner approach to function development :)

--

--