How to have a REST with Azure serverless functions

Building REST API services with Azure Functions Serverless model

Serverless model is becoming more and more popular everyday and all major cloud providers like AWS, Azure and GoogleCloud are providing their own implementation of serverless cloud computing model. As more and more companies are shifting their infrastructure to cloud, serverless model becomes more and more popular mostly because it's cost effectiveness, easy setup and out of the box scaling and orchestration. According to rightscale.com, serveless model recorder 75% usage growth in 2018. 

Rightscale State Of The Cloud Report Serverless Use Grows

Serverless model is present on the cloud platform for quite some time, but it took time for companies to realize the benefits and cost reduction as well as security and policy concerns to completely rely and switch their infrastructure to the cloud.

Although from financial point of view and complexity of maintained and other aspects serverless looks like an ideal model to build your infrastructure on, it also has it's own down sides. Before we dig into the subject of creating and deploying your REST services to Azure serverless model, let's list some of the benefits and downsides of relying on serverless or often referred as FaaS (Function as a Service) cloud computing model to help you decide whether you want to read the rest of the article or not :)

Why Serverless/FaaS?

  • Easy management and scaling
    First of all let's clear out a bit the serverless cloud computing model. Serverless does not mean that there is no server behind it, not at all. The server exists but it is not directly exposed to the client. Also, maintenance and management of the server is done by the cloud platform itself, rather than by cloud platform user. Instead of you creating Virtual Machine or setting up Kubernetes cluster for running you application, you can have this exposed as a service and solely focus on your application business logic.
  • Easy CI/CD integration
    Servlerless model is quite often pretty well integrated with CI/CD pipeline, so big part of deployment is also well covered and easy to spin up. In mater of not even hours, but rather minutes you can have you code build, deployment, running and scaling in place without involving DevOps team because spinning up a FunctionApp on Azure is quite easy and does not require multiple teams to work on setting up the application running environment. It is enough for developer to have access to Azure cloud dashboard to have everything up and running in no-time.
  • Pricing models
    All the reasons listed above are directly or indirectly affecting the final cost of having you application in cloud, but that is not all. Azure allows you to run your FunctionApp in Pay-per-use pricing model which means you only pay when your code is actually doing something. Unlike any other model where you need to have you application up and running even when you do not have traffic on the application endpoint, with FunctionApp you only pay once your application endpoint is invoked. On to of that, Azure gives you 1,000,000 for free monthly. Imagine you having your payment enpoint in cloud running for free. Awesome, isn't it.
  • Vendor lock-in
    Now we got to bad sides of serverless. In my opinion, so far that major drawback of using serverless cloud model is vendor lock-in situation. Serverless implementations on cloud providers are so different that if you decide to go with Azure, you code won't be s easy to port later on on AWS or GoogleCloud.

In my opinion it is not a major issue, because companies also have their infrastructure in more than one cloud platform because of the specific requirements. You may wan to use services from more vendors, for example Google Maps or Google translate services and at the same time benefit from Azure Office365 and Azure AD tight integration in your company.

Note

Latest release of Azure FuntionApp V2 does not support direct model binding. It is an existing issue which does not exist with V1 and this issue is referenced in opened issue/bug in Github repository https://github.com/Azure/azure-functions-host/issues/3370

To summarize, if you are fine sticking with specific cloud provider with your application, serverless is definitely something you should consider.

Building Azure FunctionApp with VisualStudio 2019

Both Visual Studio2017 and Visual Studio 2019 IDE come with build in FunctionApp project templates. It makes it not so different that building any other project in Visual Studio. 

I am currently using Visual Studio 2019 preview 2 and Azure FunctionApp template is available from the start screen.

Azure Function 800

Currently, Azure supports FunctionApp V1 and FuncctionApp V2. The main difference is that FuntionApp V2 project is .NET Standard projects and builds to cross-platform library.

Model validation

Since FunctionApp is not state-full component as it is alive only when it is invoked (especially when used with Pay-per-use pricing model), it makes it perfect candidate to build your REST services on top of it.

FunctionApp is not typical MVC project, so many luxuries that you would have in typical WebAPI application are not there. One of them is out of the box model validation. Lucky for us, we can still reference System.ComponentModel.DataAnnotations namespace and use custom extension methods to validate the model passed to our REST service endpoint.

namespace AzureFunctionsREST.Extensions
{
    public static class HttpRequestExtensions
    {
        public static async Task<Byte[]> GetBodyAsync(this HttpRequest httpRequest)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                await httpRequest.Body.CopyToAsync(memoryStream);
                return memoryStream.ToArray();
            }
        }

        public static async Task<String> GetBodyStringAsync(this HttpRequest httpRequest)
        {
            using (var reader = new StreamReader(httpRequest.Body))
            {
                return await reader.ReadToEndAsync();
            }
        }

        public static async Task<T> BindModelAsync<T>(this HttpRequest httpRequest)
        {
            return JsonConvert.DeserializeObject<T>(await httpRequest.GetBodyStringAsync());
        }

        public static async Task<(T Model,bool IsValid, ICollection<ValidationResult> ValidationResult)> ValidateAsync<T>(this HttpRequest httpRequest)
        {
            var model = await httpRequest.BindModelAsync<T>();
            if (model != null)
            {
                var results = new List<ValidationResult>();
                Validator.TryValidateObject(model, new ValidationContext(model), results);
                return (Model: model, IsValid: false, ValidationResult: results);
            }
            throw new ArgumentException($"No model of type {typeof(T).FullName} found in the request payload");
        }
    }
}
    

Model validation is easy to perform with above declared extension method by simply using the namespace in Azure FunctionApp Function class. Model validation is still declared on the model with validation attributes although extension methods extends HttpRequest class. Here is a simple sample of model I will use later in the sample REST facade part of the sample FunctionApp

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace AzureFunctionsREST.Models
{
    public class Person
    {
        public Guid Id { get; set; }

        [Required(AllowEmptyStrings =false)]
        [MinLength(3)]
        [MaxLength(25)]
        public String FirstName { get; set; }

        [Required(AllowEmptyStrings = false)]
        [MinLength(4)]
        [MaxLength(50)]
        public String LastName { get; set; }

        [Required]
        public DateTime DateOfBirth { get; set; }

        [Range(50, 300)]
        public double Height { get; set; }
    }
}
    

Even without out of the box model validation, it is not hard to get to the point where you have functional model validation 

Although there are still a lot of limitations and lack of out of the box functionalities, we can expect more functionalities out of the box incorporate into FunctionApp types of projects as serverless and FaaS model become more and more popular over time. 

Building REST facade

Since we want to build REST facade it is clear that our Azure FunctionApp Function will be invoked via HTTP, so for that reason we need to create function that is invoked via Http trigger. Visual studio already has a template for functions with different trigger methods, so we are just going to do it from the new function menu in FunctionApp project.

Before we continue, let's quickly analyze the anatomy of MVC Web API project and Azure FunctionApp to see what analogy we can apply. The following is high level structure of both Web API (on the left) and Azure FunctionApp (on the right)

 Webapi Appfunction

You can clearly see that MVC Web API controllers are structurally the same as Azure FunctionApp. We can simply refer to our functions as web methods and our Azure FunctionApp will be it's container, similar to controller in MVC Web API project.

With this analogy in mind, let's see how would our Azure REST API look like.

namespace AzureFunctionsREST
{
    public static class PersonsService
    {
             
        [FunctionName("PersonsGet")]
        public static async Task<IActionResult> PersonsGet(
            [HttpTrigger(AuthorizationLevel.Function, "GET", Route = "Persons")] HttpRequest httpRequest,
            ILogger logger
            )
        {
            await Task.CompletedTask;

            return new OkObjectResult(new List<Person>()
            {
                new Person(){ Id=Guid.Parse("88468cad14064f23b9d54d6940db3073"), FirstName="John", LastName="Smith", DateOfBirth=DateTime.Parse("1984-10-31"), Height=180},
                new Person(){ Id=Guid.Parse("d8cb37491b5e4048888a973dd95cf326"), FirstName="King", LastName="Robert", DateOfBirth=DateTime.Parse("1986-06-15"), Height=180}
            });
        }

        [FunctionName("PersonsGetById")]
        public static async Task<IActionResult> PersonsGetById(
            [HttpTrigger(AuthorizationLevel.Function, "GET", Route = "Persons/{id}")] HttpRequest httpRequest,
            ILogger logger,
            String id
            )
        {
            await Task.CompletedTask;

            return new OkObjectResult(
                new Person() { Id = Guid.Parse("88468cad14064f23b9d54d6940db3073"), FirstName = "John", LastName = "Smith", DateOfBirth = DateTime.Parse("1984-10-31"), Height = 180 }
            );
        }

        //Model binding issue https://github.com/Azure/azure-functions-host/issues/3370
        [FunctionName("PersonsPost")]
        public static async Task<IActionResult> PersonsPost(
            [HttpTrigger(AuthorizationLevel.Function, "POST", Route = "Persons")] HttpRequest httpRequest,
            ILogger logger
            )
        {
            var validation = await httpRequest.ValidateAsync<Person>();
            if (validation.IsValid)
            {
                var person = validation.Model;
                //TODO: Model handling logic
            }
            return new OkResult();
        }

        [FunctionName("PersonsPatch")]
        public static async Task<IActionResult> PersonsPatch(
            [HttpTrigger(AuthorizationLevel.Function, "PATCH", "PUT", Route = "Persons")] HttpRequest httpRequest,
            ILogger logger
            )
        {
            var person = await httpRequest.BindModelAsync<Person>();
            //TODO: Model handling logic
            return new OkResult();
        }
    }
}
    

The trigger attribute applied to HttpRequest parameter of the method is constructed with three parameters which determine the behavior of the endpoint

  • Authorization
  • HTTP method filter
  • Route

Authorization is specifict to Azure functions and we can skip it for now as we only want to have the REST facade for now and we'll deal with he security later on in the article.

HTTP method and route arguments of the attribute constructor are what is important to us in order to have our FunctionApp endpoint respond in REST way to the invokers. Since REST relies on HTTP method, same route has to have different handlers for different HTTP methods. For that reason we have same route value for all functions inside our PersonsService class.

Decision which method we'll be triggered depends on HTTP method which is exactly what we wanted in the first place. With this configuration in place we can run the FunctionApp and see how it works. 

Debugging and testing with POSTMAN

Even if you do not have Azure account, you can still test you FunctionApp on your local, as Visual Studio is shipped with Microsoft Azure emulator to allow debugging with limited Azure environment on your local machine.

Azureemulator

At the end of trace in the console, emulator will list all function endpoints along with parameters and HTTP methods they are configured to accept. It is obvious that we have REST facade with our FunctionApp running on the localhost. We can simply now use POSTMAN or any other tool and send requests to our endpoints and use breakpoints in the function methods to confirm the method invocation.

I will simply test get to retrieve collection of Person model class instances

Azure Functionapp Postman

Security

Most of the time your endpoints will not be accesible for public but rather to only authenticated clients. Authentication method of the function is declared in the constructor of HttpTrigger attribute. There are two basic models for Azure functions endpoint authentication.

Function key authentication

First way of authenticating the endpoint is using Function authentication. If you are making server-side calls to your REST FunctionApp service or your client is secured client meaning shared security keys are ensured not be exposed publicly, using function authentication is just enough.

When you create your FunctionApp in Azure, you will get set of secret keys associated to FunctionApp. You can supply this key in the query of in header of your request to function endpoint. Since I already have AuthorizationLevel.Function set in the code in above snippet, once application is deployed to Azure, I can use it in postman to make the calls.

If you navigate to your FunctionApp in Azure portal and you select specific function, you can get the URL along with the secret key you need to invoke the function.

Azure Function Key Url

You noticed that there are 3 key options you can use

  • Default function key - each function has its own key, so you can share the URL with the key with your clients and they do not necessary have access to other functions
  • Master host key - key is common for all azure functions on your account
  • Default host key - key is common for all functions in the specific FunctionApp

This maps directly to enumeration values (Function, System, Admin) for constructor parameter of Microsoft.Azure.WebJobs.HttpTriggerAttribute applied to HttpRequest parameter of Http triggered Azure function

#region Assembly Microsoft.Azure.WebJobs.Extensions.Http, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// C:\Users\Dejan_2\.nuget\packages\microsoft.azure.webjobs.extensions.http\3.0.0\lib\netstandard2.0\Microsoft.Azure.WebJobs.Extensions.Http.dll
#endregion
namespace Microsoft.Azure.WebJobs.Extensions.Http
{
public enum AuthorizationLevel
{
Anonymous = 0,
User = 1,
Function = 2,
System = 3,
  Admin = 4
    }
}
    

Enumeration value User is used for OpenID connect authentication.

Now to test the authentication of the endpoint, let's first see is it still opened for public. We can simply check that by trying to call it from the POSTMAN without the secret key.

Unauth Azure Func

And now test with supplied key in the request URL

Keyauth Azure Func

Note

While debugging on localhost, authentication will be bypassed. You can only test the authentication once your FunctionApp is deployed to Azure

AzureAD authentication

As for authentication using AzureAD or other providers like Facebook, Google or Twitter that come out of the box as a setting on FunctionApp level in Azure dashboard

Azure Function Azuread

You can pretty easy rely on any of the supported external authentication providers to authenticate your calls to FunctionApp endpoints.

Summary

Now to summarize. Relying on serverless cloud model is great and cost effective but only if you are ready to pay the price of vendor lock-in. I am not saying that moving away from one cloud provider to another is not possible, but serverless model is so tight coupled with specific cloud provider. One reason is certainly getting the best from the cloud platform, but another reason is that cloud providers want you to stick with them for a long time.

If you are ready to have commitment to certain cloud provider, then serverless is something you should definitely think about as it can significantly reduce the amount of work and money.

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article