Azure Functions - Web API - Part 2

rubber band

If you are just starting with an Azure Functions API implementation, first take a look at the first post to get caught up. In this post, we will take a look at a more traditional example of a web API implementation.

A couple of things you will notice in the example below is the static keyword has been removed from the class and the method signatures. The reason is due to the need to inject the IPersonService reference for consumption. If you are familiar with traditional web API development with .NET this should look pretty familiar.

public class PersonFunction
{

    private readonly IPersonService personService;

    public PersonFunction(IPersonService personService)
    {
        this.personService = personService;
    }

    [FunctionName("personDefault")]
    public IActionResult GetDefaultPerson(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "person/default")] HttpRequest req,
        ILogger log)
    {
        return new OkObjectResult(new Person());
    }
}

First, notice there is no need to have an attribute decorating the class. The implementation does not register each class, Azure Functions interest is not at the class level, but at the method level. Next, there is a private read-only instance variable to be used to contain our instance of IPersonService for consumption within the individual functions. Finally, there is a method decorated with the FunctionName attribute and a decorated first argument with the HttpTrigger attribute. This method is very similar to the example in the first post, except for the Route parameter on the HttpTrigger attribute. This argument allows you the implementor to explicitly define the route to the function. This allows for one to mimic any path already in use. To avoid contract-breaking changes.

Now, most web API controllers I have ever seen have more than one endpoint. With Azure Functions, this can be done as well. All the implementor has to do is add another method to the class, decorated with the FunctionName attribute and the first argument decorated with the HttpTrigger attribute. To my knowledge, there is no limitation other than the readability of the file.

[assembly: FunctionsStartup(typeof(WebApi.Startup.Startup))]
namespace WebApi.Startup
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IPersonService, InMemoryPersonService>();
        }
    }
}

To configure dependency injection, a NuGet package is required. The name of the package is Microsoft.Azure.Functions.Extensions. With that installed, we have to register a hook into the Azure Function startup. This requires the assembly attribute decorating the namespace to notify the Azure Function service to execute the class upon starting the function. Then a class inheriting from FunctionStartup. Finally, we need to implement the abstract void Configure(IFunctionsHostBuilder builder) method. With these quick steps implemented, the addition of dependencies into the dependency injection container is very familiar. The same goes for consuming the dependencies registered in the container. Simply adding the type to the constructor will provide access to the dependency.

[FunctionName("getPersonById")]
public IActionResult GetPersonById(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "person/{id:guid}")] HttpRequest req,
    Guid id,
    ILogger log)
{
    if (Guid.Empty == id) return new BadRequestObjectResult("Id passed is default value");
    return new OkObjectResult(personService.GetById(id));
}

Of course, we will want to accept URI segments as arguments for the function as well. To accomplish this, we can leverage the Route parameter on the HttpTrigger to tokenize the route with a variable. The example takes in an argument of id which is of type Guid. Notice how we can define the type right in the token? To use the argument, all that needs to be done is append the strongly typed argument to the method signature. If there are multiple arguments passed in via URI segment, this design allows the function to be as flexible as needed to capture all the information from the request.

[FunctionName("upsertPerson")]
public IActionResult UpsertPerson(
    [HttpTrigger(AuthorizationLevel.Function, "put", Route = "person")] Person personToUpsert,
    ILogger log)
{
    return new OkObjectResult(personService.Upsert(personToUpsert));
}

Finally, an implementation of an endpoint that accepts a request body. The Azure Function team made this easy. In all the previous requests the first argument was of type HttpRequest. This would be sufficient to obtain the body of the request, but it's even easier than that. Create a type to model the deserialized body of the request, then use that model as the type of the argument decorated with the HttpTrigger attribute. The Azure Function will parse the request data, providing the implementor with quick and easy access to the data.

That is all there is to it! In all, implementing a web API using Azure Functions is quite fast. Each function can be grouped for cohesion and easily unit tested. What is not to love?

P.S. Everything you saw in this post and more can be found on my GitHub, including a PostMan collection for all the API endpoints in the project.

P.P.S. Check out the new link in the footer! Direct and easy access to GitHub!