Lambda Function URLs - triggering .NET 6 Lambda functions with a HTTPS Request

Want to learn more about AWS Lambda and .NET? Check out my A Cloud Guru course on ASP.NET Web API and Lambda.

Download full source code.

I love using .NET Lambda functions to handle incoming HTTP requests, but until now an API Gateway or an Application Load Balancer had to be configured and connected to the function. Not hard to do, especially if you use the deployment tools provided with the AWS Extensions for the .NET CLI. I have an example here that uses the API Gateway. As part of the process you have to create an S3 bucket, then the tooling creates a CloudFormation stack, deploys the Lambda, creates the API Gateway, roles, permissions, etc, and hooks everything up.

Last week, a new feature was released, Lambda Function URLs. You no longer have to create the S3 bucket, no CloudFormation stack, and no API Gateway. Read Alex Casalboni’s blog for more details on how it works and some use cases for this feature.

The Function URL is configured directly on the Lambda function, either from the AWS Console, the AWS CLI, PowerShell tools, or an IaC tool. In this post, you will use the AWS CLI.

Getting the tools

Install the latest tooling, this lets you deploy and run Lambda functions.

dotnet tool install -g Amazon.Lambda.Tools

Install the latest templates to get .NET 6 support.

dotnet new --install Amazon.Lambda.Templates

Get the latest version of the AWS CLI, from here.

Create a simple application for the Lambda function

From the command line, run -

dotnet new lambda.EmptyFunction --name FunctionUrlExample

Go to the directory .\FunctionUrlExample\src\FunctionUrlExample\.

The FunctionHandler method is very simple -

public string FunctionHandler(string input, ILambdaContext context)
{
    return input.ToUpper();
}

Altering the Lambda function to accept the Function URL request

When the Lambda function is triggered from the Function URL the incoming request will contain all the HTTP info you would expect, query-string, body, headers, method, etc, this will be passed to Lambda function as JSON. To accept this, you will change the first parameter in the FunctionHandler method from a string to an APIGatewayHttpApiV2ProxyRequest.

But first, add the Amazon.Lambda.APIGatewayEvents package to the project -

dotnet add package Amazon.Lambda.APIGatewayEvents

Add a using statement to the Function.cs file -

using Amazon.Lambda.APIGatewayEvents;

Change the FunctionHandler signature to -

public string FunctionHandler(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)

With this change, the incoming request will be deserialized into the request object. If you are responding to a GET or another verb that doesn’t use a body (I know, GET technically can have a body, but it usually doesn’t), you can extract the query string, path, etc from the request object. For example, try -

  • request.QueryStringParameters
  • request.PathParameters
  • request.RequestContext.Http.Method

But if you want to use the body of the request, another deserialization must be performed.

Deserializing the body of the request

The APIGatewayHttpApiV2ProxyRequest.Body contains the body of any request that supports a body (PUT, POST, PATCH, etc). But this is a string and has to be explicitly deserialized into a type you define.

In this example, you will deserialize a Person from the body of the request.

Create a new class called Person.

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
    public int Age { get; init; }
}

Back in Function.cs, add two using statements -

using System.Text.Json;
using System.Text.Json.Serialization;

And change the FunctionHandler method to this -

public string FunctionHandler(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context)
{
    var serializationOptions = new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true,
        NumberHandling= JsonNumberHandling.AllowReadingFromString
    };

    Person person = JsonSerializer.Deserialize<Person>(request.Body, serializationOptions); 

    return $"Hello {person.FirstName} {person.LastName}, you are {person.Age} years old.";
}

Setting serializationOptions is not necessary but makes it easier for your function to handle capitalization issues and the presence of numbers as strings in the request.

Deploy the function

Use the following to build the code and deploy the Lambda function -

dotnet lambda deploy-function FunctionUrlExample 
You will be asked - “Select IAM Role that to provide AWS credentials to your code:”, select “*** Create new IAM Role ***”

You will then be asked - “Enter name of the new IAM Role:”, put in “FunctionUrlExampleRole”.

Then you will be asked to - “Select IAM Policy to attach to the new role and grant permissions”, select “AWSLambdaBasicExecutionRole”, for me it is number 6 on the list.

Wait as the function and permissions are created.

Configuring the function for HTTP requests

Right now, the Lambda function is deployed and can be seen in the AWS Console, but it doesn’t have a Function URL.

You can add one from the AWS Console or the AWS CLI (or other tools). But I want to use the AWS CLI, a minimum version of “2.5.4” is required (see above on tooling).

From the command line, run -

aws lambda create-function-url-config --function-name FunctionUrlExample --auth-type NONE

You will see -

{
    "FunctionUrl": "https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda-url.us-east-1.on.aws/",
    "FunctionArn": "arn:aws:lambda:us-east-1:xxxxxxxxxxx:function:FunctionUrlExample",
    "AuthType": "NONE",
    "CreationTime": "2022-04-09T00:43:45.699805Z"
}

At this point, a URL is attached to the Lambda function, requiring no authentication, but you’re not quite finished yet.

Add a resource-based policy to allow the Function URL to invoke the Lambda function

One more thing to do - add a resource-based policy allowing the Lambda function to be called via the URL. See here for more.

aws lambda add-permission --function-name FunctionUrlExample --statement-id AuthNone --action lambda:InvokeFunctionUrl --principal * --function-url-auth-type NONE

You will get a response like -

{
    "Statement": "{"Sid":"AuthNone","Effect":"Allow","Principal":"*","Action":"lambda:InvokeFunctionUrl","Resource":"arn:aws:lambda:us-east-1:xxxxxxxxx:function:FunctionUrlExample","Condition":{"StringEquals":{"lambda:FunctionUrlAuthType":"NONE"}}}"
}

Now the Lambda function can be accessed from the URL with no authentication needed.

Testing it out

In the attached zip file, there is a file TestFunction.rest, which works with the Visual Studio Code extension Rest Client.

POST https://xxxxxxxxxxxxxxx.lambda-url.us-east-1.on.aws/ HTTP/1.1
content-type: application/json

{
    "firstname": "Alan",
    "lastname": "Adams",
    "age": "25"
}

You can of course use Curl, Fiddler, Postman, etc.

There it is, a publicly accessible HTTPS endpoint, backed by C# application, that can scale as needed, and be deployed in seconds. Not bad!

Download full source code.

comments powered by Disqus

Related