Amazon API Gateway for the .NET Developer - How To Build HTTP APIs

Amazon API Gateway is a fully managed service that helps developers build and maintain APIs at any scale. The Gateway act as a ‘front door’ to your business data and backend services.

API Gateway is often mentioned along with Serverless technologies, especially AWS Lambda since it makes it very easy to expose the Function over an HTTP endpoint.

HTTP APIs are the latest set of services provided which are designed for low-latency, cost-effective integrations with different AWS services. Compared to the previous generation REST APIs it has a lot of advantages. However, there are also certain missing features. So before you dive in make sure HTTP APIs are the right choice for you (which most of the time will be ).

In this post, let’s learn how to set up an HTTP API in Amazon API Gateway connect it to an AWS Lambda Function built on .NET Core. We will explore the different commonly required features that you are used to when building APIs.

Creating API Gateway

First, head over to the AWS Portal under Amazon API Gateway to create a new HTTP API.

Add new HTTP API from AWS Console under Amazon API Gateway

Add new HTTP API from AWS Console under Amazon API Gateway (aws-console-api-gateway-create-http-api.jpg)

Give an API name of your choice. I’ll choose youtube-demo. Skip through the Routes and Stages for now, and we will add them in soon. Once you click ‘Review and Create’, a new API Gateway Endpoint is created, with a default stage ($default). The default stage also has an associated Invoke URL that is assigned.

To set up Routes, we first need backend services to which we can integrate them. So let’s get started building some backend services to provide us with data.

Backend Services - AWS Lambda

API Gateways support multiple integration types that can serve the data. Below are the ones supported at the time of writing this article.

Supported Integration types when building HTTP APIs on Amazon API Gateway

Supported Integration types when building HTTP APIs on Amazon API Gateway (api-gateway-http-api-supported-integrations.jpg)

For this example, we will integrate with AWS Lambda Functions to send and receive data from our API endpoints.

New to AWS Lambda?

Learn how to get started with AWS Lambda with .NET Core by building out a simple function and deploying to AWS. We will also see how to set up local development environment using the AWS Toolkit.

To quickly create and deploy AWS Lambda Function use the templates provided with the AWS Toolkit for Visual Studio (Other IDE’s also have their implementation of this toolkit).

Let’s create a new function to return us some WeatherData as shown below (it will be familiar if you have played around with ASP NET Core default templates).

public class Function
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public List<WeatherForecast> FunctionHandler(
                    APIGatewayProxyRequest input, ILambdaContext context)
    {
        Console.WriteLine(JsonSerializer.Serialize(input));
       
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            City = $"{cityName}-test",
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToList();
    }
}

Since this function takes in input from API Gateway, it uses APIGatewayProxyRequest from the Amazon.Lambda.APIGatewayEvents nuget package. You can run the Function code locally using Mock Lambda Test Tool as you have seen earlier here.

To quickly deploy this function to our AWS account, use the ‘Publish To AWS Lambda..’ option from right-clicking the project in Visual Studio. The AWS Toolkit provides this option. In a real-world application, you can set up a build-deploy pipeline to automatically deploy any time a change is made to your source code repository.

Publish Lambda from Visual Studio

Once deployed to the AWS account, we can use this Function to serve our API Gateway endpoint.

Routes and Integration

Routes direct API requests to backend resources.

It consists of the HTTP Method and the resource path. This combination is used to map what backend service or integration the route gets matched to. The HTTP method is any HTTP verb or ANY, which matches all methods.

You can also add in $default route, which acts as a fallback route for requests that don’t match any other requests.

GET Endpoint

To create our first GET endpoint, choose Create and enter the route details under Routes in the AWS Console. Below I am creating a GET /weather-forecast route.

Create a new route in API Gateway for HTTP API

Create and Attach Integration

We can create and attach a back-end integration for this route. You can either navigate to the route from the Routes or Integrations menu option under the sidebar.

Create a Lambda integration for HTTP API Route

Choose the Lambda function we deployed earlier. Click Create and it will create the integration and add relevant permissions for the API Gateway to invoke the AWS Lambda Function.

The API is all set up. Navigate to /weather-forecast on the base URL to the API, and it will show the weather data response from our lambda function.

Query Parameters

As with any other URLs, we can pass in parameters to the API in the URL as query parameters. These are passed on to the backend integration.

In the case of lambda integration, we can retrieve the query parameters from QueryStringParameters property in the APIGatewayProxyRequest instance.

I’ve updated the function handler to retrieve the cityName. If passed in as part of the query, it returns the weather data for that city.

public List<WeatherForecast> FunctionHandler(APIGatewayProxyRequest input, ILambdaContext context)
{
    Console.WriteLine(JsonSerializer.Serialize(input));
    string cityName = null;
    input.QueryStringParameters?.TryGetValue("cityName", out cityName);
    cityName = cityName ?? "Brisbane";

    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        City = cityName,
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToList();
}

Once deployed to AWS Function, you can pass in the city name parameter along with the API Request → GET /weather-forecast?cityName=Sydney

Path Parameters & Parameter Mapping

Similar to query parameters, we can also define Path Parameters when defining API Gateway Route URLs. For example, we can embed in the cityName as part of the URL path when creating the route endpoint.

E.g. if the URL Route is GET /weather-forecast/{cityName}, we need to enforce that the cityName is passed when invoking the URL, and it’s no longer optional. Only when provided, will it map to the appropriate backend integration.

Path parameters are available from the PathParameters property in the APIGatewayProxyRequest instance.

With the currently deployed code we have in AWS Lambda, it will be broken if we switch to path parameters. We have to update the code to look in the PathParameters property instead of QueryStringParameters. However, we can also do parameter mapping in API Gateway to map the path parameter as a query string parameter (and also a lot more).

Parameter mapping can transform requests before being sent to back-end integrations or modify responses before being sent back to the requesting client.

To map the query string parameter from the path parameter,

Manage existing integration to add parameter mapping.

Add a new parameter mapping for All incoming requests. The mapping appends a new query string parameter (cityName) and sets it to the value from the path parameter with the name cityName.

Set up request mapping to map query parameter to a path parameter.

With this mapping in place, even with the existing function code looking at QueryStringParameters it will work if we use path parameters in the URL. The API Gateway, when it sees the request, will modify it to add in the query parameter, set it to the value from the path parameter, and send it to the Lambda integration.

POST Endpoint

To create a POST /weather-forecast/{cityName} , add a new Route with method type as POST and specify the endpoint path very similar to how we create the GET endpoint.

We will need to publish a new lambda function for the backend service. Below I have the sample code for the POST endpoint.

It retrieves the cityName from the PathParameters property and sets it on the WeatherForecast object in the request body. In a real-world application, you would be saving this data to a database, raising events, etc.

public APIGatewayProxyResponse FunctionHandlerPost(
    APIGatewayProxyRequest input, ILambdaContext context)
{
    var data = JsonSerializer.Deserialize<WeatherForecast>(input.Body);

    string cityName = "Brisbane";
    input.PathParameters?.TryGetValue("cityName", out cityName);

    data.City = cityName;
    return new APIGatewayProxyResponse()
    {
        Body = JsonSerializer.Serialize(data),
        StatusCode = (int)HttpStatusCode.OK
    };
}

If you are using Publish from Visual Studio, make sure to update the function name and also the Handler function in the publish options dialog.

Once successfully published, we can create and set up a new lambda integration for the new route, as shown below.

Setting up Lambda integration for POST endpoint

Setting up Lambda integration for POST endpoint api-gateway-http-api-create-lambda-integration-post.jpg

Once set up, we can successfully send POST requests to the new endpoint and see it in action.

Stages

Stages in API Gateway are a logical reference to the lifecycle of your API. You can use this to differentiate between different development lifecycle (dev, test, staging, prod, etc.).

When creating the API Gateway, a $default stage is automatically created. This stage is served from the base of the APIs URL. For a new stage, it will have the base URL followed by the stage name - https://{api_id}.execute-api.{region}.amazonaws.com/{stageName}

Under Deploy → Stages, you can create a new stage for the API.

Create a new Stage in API Gateway

Create a new Stage in API Gateway api-gateway-http-api-create-stage.jpg

By default, this maps to the same integrations since we have hard-wired the lambda functions when creating the Routes and the integrations.

Different Backend Integration Based on Stage

When using Stages, we want our backend services to be different based on the stage - lambda function, database, etc., per environment is mainly preferred.

First, set up another lambda function for the GET endpoint for the test environment → get-weather-forecast-test . I have the lambda function for demo purposes, append ‘-test’ to the cityName when it returns the list of weather data.

To map different lambda functions based on the stage, we first need to create a Stage Variable. It can be done under the Stages, editing a stage, and adding a new variable. Below I’ve added a new variable env with the value ‘-test’.

Add a new Stage variable under the test stage

We can use this new stage variable when defining integration. Edit the existing lambda integration, and instead of explicitly selecting the lambda function from the drop-down, we can specify the ARN to the lambda. We can use the stage variable to build up the lambda function name.

In the below example, I have used the following format to pick the appropriate get-weather-forecast lambda function based on the stage.

arn:aws:lambda:ap-southeast-2:aws-account-id:function:get-weather-forecast${stageVariables.env}

In our example, since we have the env variable set to ‘-test’ for the test stage, it will pick up the get-weather-forecast-dev lambda function for that stage. Since there is no env variable for the default stage, it will pick up the get-weather-forecast lambda.

Use stage variables when specifying lambda integration for AWS API Gateway route integration.

With the lambda function automatically determined based on the stage, we need to give the API Gateway the appropriate permissions to Invoke the lambda function. Since the lambda functions are not explicitly assigned, we must manually set this permission in the Lambda.

Navigate to the Lambda function; choose Add Permission under Configuration → Permissions → Resource-Based Policy → Policy statement. To give access to API Gateway, select AWS Service and select Gateway API. Give a unique StatementId and specify the Source ARN and the Action.

The Source ARN needs the API Gateway endpoint details that will be accessing this lambda function. Based on the stage and the Action type, you can specify it as below.

arn:aws:execute-api:ap-southeast-2:AWS-ACCOUNT-ID:API-ID///weather-forecast/{cityName}

The first wildcard * stands for stage and the second for the HTTP VERB.

Add Invoke function permissions to the AWS Lambda for the API gateway to be able to invoke

The API Gateway now has the appropriate rights to invoke the Lambda function. If you invoke the URL on the test stage, it will invoke the get-weather-forecast-test lambda function after mapping the appropriate stage variable. When default stage is invoked, it will call the get-weather-forecast lambda function.

We can successfully switch between the backend services based on the stage it’s deployed to. These backend services can then talk to their instances of databases, storage, and other services.

You can find the source code for the lambda functions here.

Photo by AltumCode on Unsplash

Buy Me A Coffee