Find all the code associated with this article on GitHub.
What is AWS Lambda?
Lambda is a service that lets you run code without thinking about the infrastructure – it will take care of scaling and offering high availability to your consumers, and you only pay for the time your code is running. The functions you create can be triggered by different events in other AWS services, or through an API gateway.
You can choose between a variety of runtime languages – Java, Ruby, Python, C#, PowerShell, or provide your custom setup. In this tutorial, we will be focusing on deploying and running a .NET/C# function.
.NET on AWS Lambda
For some languages, like Python or Node, you can write your function directly into Lambda’s code editor. With .NET, however, you are required to upload a project – as a .zip file, from S3, or directly deployed from Visual Studio/VS Code.
The first two options would work nicely in a Continuous Integration/Deployment setup, but for this tutorial we will be focusing on the manual deployment from the local environment.
AWS provides a package that needs to be used to create and build the right project type for Lambda. We’ll be looking into it in the next section.
Before diving into coding, you need to install the AWS Toolkit for Visual Studio. This offers you new project templates, such as the one for Lambda that we are going to use in this tutorial, as well as access to deploying your applications to different services – such as Elastic Beanstalk or, of course, Lambda. You can read about that in my article about using the AWS Parameter Store in .NET.
Even if you are not interested in the above article, you should still setup your AWS credentials in Visual Studio – it will be important for deploying the application to the Lambda service.
Developing the project
Once you have the toolkit installed and the credentials set up, we can start by creating the project. Start Visual Studio and select the ‘AWS Lambda Project(.NET Core -C#)’ type:
Then, select a name and a location for your project and solution:
On the final screen, choose the ‘Custom Runtime Function’ template, which will allow you to develop for the latest .NET Core version, 3.1:
After clicking the ‘Finish’ button, your project should be created. Inside, you should see a file called ‘Function.cs’. This contains the main entry point for the Lambda application, as well as the method that will be executed when the Lambda function is triggered.
What the ‘Main’ method does is to create a delegate to the ‘FunctionHandler’ – which will be executed on trigger – and start the bootstrap that will invoke the function. It also takes care of JSON serialization/deserialization, so the function receives the input in the expected format, and the returned value is automatically translated into JSON.
The ‘FunctionHandler’ method takes two arguments: the input you want from the users, and a ‘ILambdaContext’ instance – this provides information about the Lambda execution environment, such as the memory limit or the remaining running time, but you will not use these most of the time.
As far as functionality goes, you can use Lambda to develop any back-end features you would normally have in other environments, such as ASP.NET. You can access databases, call third-parties, or work with other AWS services. For this beginner-level tutorial, we will create a function that takes an integer as input and returns a string from a list.
Firstly, create a ‘Repository’ class that manages the list and what is returned from it:
In the ‘Function.cs’ file, there are a few changes that need to be made. The ‘FunctionHandler’ should take an ‘int’ as a parameter, instead of a ‘string’. For its implementation, it will create a new ‘Repository’ class and call the ‘GetValue’ method. Finally, we need to change the delegate from the ‘Main’ method to a ‘Func<int, ILambdaContext, string>’ instead of a ‘Func<string, ILambdaContext, string>’, since we changed the parameter type for the ‘FunctionHandler’.
This is what the final class looks like:
Before deploying the function, we need to create a IAM role that for it to assume – this will let the function create and access all the necessary resources. In the AWS Console, search for ‘IAM’ and open the service:
From the left-side menu, choose ‘Roles’ and click the blue ‘Create Role’ button. For the first step, pick ‘AWS Service’ and ‘Lambda’ – since we are creating a role that a Lambda function will use.
On the next page, you have to select the policies to attach to the role. What’s important here is for the function to be able to work with EC2 instances, since it will use them to execute when necessary. You can provide full acces to EC2:
Skip the tags (if you don’t want any), and finally add a name and description for your role, and click ‘Create role’:
To deploy your latest code to AWS Lambda from Visual Studio, right click on the project in the Solution Explorer and click ‘Publish to AWS Lambda..’.
Choose the region where you want your function deployed, give it a name, and make sure the ‘Release’ configuration is selected, as well as the 3.1 version of .NET Core. Then click the ‘Next’ button.
Here, you first need to pick the IAM role that the Lambda function will assume. Choose the role you created earlier. In the bottom left corner, you can also choose the subnets where the function will be deployed, as well as the associated security group. If you don’t have any preferences, leave them as default. Finally, click the ‘Upload’ button.
The function is now uploading and should be available shortly.
To test the function, go to the AWS console and search for the ‘Lambda’ service:
In the dashboard, you should see your newly created function:
Click on it and then press ‘Test’ in the top right. This will open a window that allows you to configure the data for a test. Give it a name and add a number for the input:
Click the ‘Create’ button, and then the ‘Test’ button in the top-right corner again. The function should be executed, and you should receive one of the messages from the repository:
Adding an API Gateway
Lambda accepts many triggers, from a variety of AWS Services. One of the most common ways to access your function, though, is through a REST API. Click on the ‘Add trigger’ button:
Select ‘API Gateway’ as the trigger, ‘Create a new API’ and ‘REST API’. Leave the security option to ‘Open’ if you don’t want to secure it for now:
You should now be redirected back to the view of your Lambda function, and you should see the API Gateway in the list of triggers. Click on it, and the endpoint should appear below:
Click on the name of the Gateway (in this case, GetString-API) to configure the endpoints. On the left side, you should see the endpoint, with the ‘ANY’ method under it. Click on the ‘Actions’ dropdown and select ‘Create Method’:
Select ‘POST’ as the HTTP method, and press the check button. You will now have to configure this; leave everything as default, but set the ‘Lambda Function’ field to your function’s name:
After clicking ‘Save’, click on the ‘Test’ link that appears in the top part of the view:
In the ‘Request Body’ area, type an integer and click on the ‘Test’ button. You should receive the response from the Lambda function:
But does it actually work?
The real use case is to be able to call the endpoint from an external source, like Postman. Copy the endpoint of your API Gateway from the Lambda function’s page (it should look something like ‘https://bqc77o6ht9.execute-api.eu-central-1.amazonaws.com/default/GetString’) and try running it from Postman. Weirdly enough, you should receive a 502 error.
To make it work as expected, we need to make a few changed to the Lambda function. Specifically, we need to change the parameter to an ‘APIGatewayProxyRequest’ object, which contains the HTTP request data, and change the response to follow the right format – which you can read more about here.
The first step is to install the ‘Amazon.Lambda.APIGatewayEvents’ NuGet package, so we have access to the proxy request class.
Then, let’s modify the ‘FunctionHandler’ method.
The first thing we do now is to deserialize the body of the request, in order to get the integer value that was provided by the user. You can use any data type to deserialize the body, but we are only sending an integer here.
Then, we get the value from the repository, just like before. Finally, we need to construct the response object. We don’t have any specific headers to return, but that object needs to be there in order for the Gateway to recognize it as a valid response.
In the ‘Main’ method, don’t forget to change the delegate’s first type to ‘APIGatewayProxyRequest’:
Deploy the function again, by following the same process as earlier. Once the deployment is finished, let’s look at the response from Postman:
The expected result is returned.
If you’ve followed along, congratulations! You’ve deployed your Lambda function and successfully integrated with an API Gateway.!