AWS Lambda is usually seen as a way for small, short functions to be run in the cloud. Usually small backend processes like resizing images or reading messages from a queue and processing them. But you can actually deploy an entire API on Lambda, and not only that, you can deploy an existing ASP.net Core API with very little extra effort.
It’s not always going to be the right architecture to be running a full API in Lambda, but if your API is being called infrequently and doesn’t need to be chewing resources 24/7, it might be a real cost saver.
Setup
If you haven’t done already, you need to install the AWS Tooling For Visual Studio 2017. Make sure VS2017 is closed while you install this. When opening Visual Studio again, you will see a window asking you to type in a few AWS credentials, follow the instructions to do so. This is mostly for deployment purposes from Visual Studio (Which this tutorial uses), but isn’t strictly required long term if you don’t want Visual Studio having access to your AWS account all the time.
For this tutorial, I’m creating a standard .net core API project in Visual Studio 2017 (The one that has the “valuescontroller” and nothing else). You can either create a new project to follow along or use your existing project, it’s up to you.
With your project open, you need to install the Amazon.Lambda.AspNetCoreServer nuget package. It’s currently in preview only at the moment so the command you need to run from your package manager console is the following :
Install-Package Amazon.Lambda.AspNetCoreServer -Pre
One of the most frustrating bugs/issues in Visual Studio is that you can’t add CLI Tooling that lives in Nuget via the nuget package manager (See bugs like the following : https://github.com/aspnet/Scaffolding/issues/422). So you need to manually open up your csproj file of your project. In the ItemGroup for your Packages, add the following line :
<PackageReference Include="Amazon.Lambda.Tools" Version="1.5.0" />
And in your ItemGroup for DotNetCliTools add the following :
<DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="1.5.0" />
My complete csproj looks like the following if you get lost. This is just the standard .net core api project with the above nuget packages installed.
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>netcoreapp1.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="0.10.1-preview1" /> <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" /> <PackageReference Include="Microsoft.AspNetCore" Version="1.0.4" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" /> <PackageReference Include="Amazon.Lambda.Tools" Version="1.5.0" /> </ItemGroup> <ItemGroup> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" /> <DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="1.5.0" /> </ItemGroup> </Project>
Code Changes
Add a class in your project named “LambdaFunction”, it should inherit from the abstract class “APIGatewayProxyFunction”. The code of this class should look like :
public class LambdaFunction : APIGatewayProxyFunction { protected override void Init(IWebHostBuilder builder) { builder .UseContentRoot(Directory.GetCurrentDirectory()) .UseStartup<Startup>() .UseApiGateway(); } }
The biggest thing people ask when they hear about Lambda is “How can I test that locally?”.
Our LambdaFunction above is our entry point for AWSLambda, but you will notice that it’s pretty damn close to how our program.cs file looks in an ASP.net core project. When you run locally, our program.cs bootstraps Kestrel/IIS and starts hosting on our local machine. When you run in AWS Lambda, it doesn’t call Program.cs, it instead calls the code above and bootstraps everything for us, but the underlying code of controllers, services, pipelines etc are all the same. That means for the most part, the site locally and inside Lambda should function more or less identically.
Add a new JSON file to your project and name it “aws-lambda-tools-defaults.json”. This file holds a bunch of defaults when publishing from Visual Studio so you don’t have to type them over and over. Open up the file and enter the following :
{ "profile": "default", "region": "us-west-2", "configuration": "Release", "framework": "netcoreapp1.0", "function-runtime": "dotnetcore1.0", "function-memory-size": 256, "function-timeout": 30, "function-handler": "AWSApiExample::AWSApiExample.LambdaFunction::FunctionHandlerAsync" }
The important thing to change here is the “function-handler”. The actual contents of this setting should be “{AssemblyName}::{LambdaFunctionName}::{FunctionHandler}”. In my case I named my project AWSApiExample, so you will need to swap this out for your project’s name. The “FunctionHandlerAsync” part is because our entrypoint that inherits from “APIGatewayProxyFunction” actually implements a methoid called “FunctionHandlerAsync” behind the scenes.
Another quick note is that I’m deploying this to the us-west-2 region, obviously change this if another region suits you better,
Deployment
Deployment could not be simpler! Right click your project and select “Publish To AWS Lambda”.
You should be given a screen to type in a few details, including naming your Function. Fill these out (Most should already be filled out), and be sure to tick the box that says it will save your settings. This actually just saves back to our json file from the last step, so you don’t have to pre-build that JSON file, but it’s much easier to copy and paste it in and just change the few details you need to. Click Next.
For permissions, if you have never used Lambda before (And don’t have a role for it), create a new role with the AWSLambdaFullAccess policy. Everything else on this page just leave default unless you know what you are doing!
Press upload and you should see your app be published up to AWS. Normally this would be the end of a normal lambda function publish, but now we need to setup our API Gateway. The API Gateway in Amazon simply acts as a proxy between the public and your Lambda. It can handle complex scenarios such as authorization, headers inspection etc, but we will actually be using it more or less as a straight pass through.
Open up your AWS Dashboard in your browser and head over to the API Gateway services screen. Go ahead and create a new API, name it whatever you like.
On the next screen, (Resources section of your new api), select the Actions dropdown and hit Create Resource. Here is where we basically create a wildcard pass through to our Lambda function. Tick the box that says “Configure as proxy resource” and leave everything else as is.
The next screen asks what you actually want to proxy to. Select Lambda Function Proxy, and select the region you deployed your Lambda function to. The most infuriating thing is that you have to type your function name here (Why can’t it just be a drop down!). Hopefully you remember what you deployed your function as (If not, in the AWS dashboard quickly pop over to the Lambda section and jog your memory).
Still in the Resources section. Select the Actions dropdown again and select “Deploy API”. A popup will come up, select new stage and type in “prod” as your stage name for now.
After hitting deploy, you will be given an “invoke” URL. I open my url and add on the end /api/values (Since I’m using the default .net Core API template). And what do you know!
So as we see, we can take an entire API and lift and shift it to Lambda. Again, I’m not really sure whether this makes total sense to do and I can’t see it being a hugely popular move, but it can be done (And fairly simple at that!).
Thanks for this tutorial….I love the ability to develop with a traditional .NET webapi but have the ability to call it with lambda. It may not be the common use case, but to me it’s much easier creating an api using controllers as we’ve all been used to versus a bunch of lambda functions. Your way is one single lambda function that can access your entire c# api – thank you!
Hi,
Thanks for the tutorial.
I would like to have once clarification. When you mentioned ” That means for the most part, the site locally and inside Lambda should function more or less identically.” is it always true? Because when I tried to migrate an exisiting WebAPI to AWS lambda, it worked fine when run using Kestral server locally, but failed with the AWS lambda with connection issues. “!System.AggregateException: One or more errors occurred. (Connection refused) —> System.Net.Http.HttpRequestException: Connection refused —“