{"id":95401,"date":"2023-01-25T16:56:47","date_gmt":"2023-01-25T16:56:47","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=95401"},"modified":"2023-01-25T16:56:47","modified_gmt":"2023-01-25T16:56:47","slug":"aws-lambdas-with-c","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/cloud\/aws\/aws-lambdas-with-c\/","title":{"rendered":"AWS Lambdas with C#"},"content":{"rendered":"<p>Serverless computing is pushing C# to evolve to the next level. This is exciting because you pay-per-use and only incur charges when the code is running. This means that .NET 6 must spin up fast, do its job, then die quickly. This mantra of birth and rebirth pushes developers and the underlying tech to think of innovative ways to meet this demand. Luckily, the AWS (Azure Web Services) serverless cloud has excellent support for .NET 6.<\/p>\n<p>In this article, I will take you through the development process of building an API on the serverless cloud with C#. This API will be built to serve pizzas, with two endpoints, one for making pizza and the other for tasting fresh pizzas. I expect some general familiarity with AWS and serverless computing. Any previous experience with building APIs in .NET will also come in handy.<\/p>\n<p>Feel free to follow along because I will guide you through this step-by-step. If you get lost, the full sample code can be <a href=\"https:\/\/github.com\/beautifulcoder\/net-aws-lambda\">found on GitHub<\/a>.<\/p>\n<h2>Getting the AWS CLI Tool<\/h2>\n<p>First, you will need the following tools:<\/p>\n<ul>\n<li>AWS CLI tool<\/li>\n<li>.NET 6 SDK<\/li>\n<li>Rider, Visual Studio 2022, Vim, or an editor of choice<\/li>\n<\/ul>\n<p>The AWS CLI tool can be obtained from the <a href=\"https:\/\/aws.amazon.com\/cli\/\">AWS documentation<\/a>. You will need to create an account then set up the CLI tool with your credentials. The goal is to configure the <em>credentials<\/em> file under the AWS folder and set an access key. You will also need to set the region depending on your physical location. Because this is not an exhaustive guide on getting started, I will leave the rest up to you the reader.<\/p>\n<h2>Create a New Project<\/h2>\n<p>I will pick the .NET 6 CLI tool because it is the most accessible to everyone. You can open a console via Windows Terminal or the CMD tool. Before you begin, verify that you have the correct version installed on your machine.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet --version<\/pre>\n<p>This outputs version 6.0.42 on my machine. Next, you will need the AWS dotnet templates:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet new -i Amazon.Lambda.Templates\r\n&gt; dotnet tool install -g Amazon.Lambda.Tools<\/pre>\n<p>If you have already installed these templates, simply check that you have the latest available:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet tool update -g Amazon.Lambda.Tools<\/pre>\n<p>Then, create a new project and a test project with a region.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet new serverless.AspNetCoreMinimalAPI -n Pizza.Api \\\r\n    --profile default --region us-east-1\r\n\r\n&gt; dotnet new xunit -n Pizza.Api.Tests<\/pre>\n<p>Be sure to set the correct region, one that matches your profile in the AWS CLI tool.<\/p>\n<p>The dotnet CLI generates a bunch of files and puts them all over the place. I recommend doing a bit of manual clean up and follow the folder structure below in Figure 1.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"248\" height=\"402\" class=\"wp-image-95402\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/01\/graphical-user-interface-text-application-descr-1.png\" alt=\"Graphical user interface, text, application\n\nDescription automatically generated\" \/><\/p>\n<p><strong>Figure 1. Folder structure<\/strong><\/p>\n<p>You may also create the solution file Pizza.Api.sln via:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet new sln -n Pizza.Api\r\n&gt; dotnet sln add src\\Pizza.Api\\Pizza.Api.csproj\r\n&gt; dotnet sln add test\\Pizza.Api.Tests\\Pizza.Api.Tests.csproj<\/pre>\n<p>This allows you to open the entire solution in <a href=\"https:\/\/www.jetbrains.com\/help\/rider\/Installation_guide.html\">Rider<\/a>, for example, to make the coding experience much richer.<\/p>\n<p>The template generated nonsense like a Controllers folder and HTTPS redirect. Simply delete the Controllers folder in the <code>Pizza.Api<\/code> folder and delete the <code>UnitTest1.cs<\/code> file that is in the <code>Pizza.Api.Tests<\/code> folder.<\/p>\n<p>In the Program.cs file delete the following lines of code.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">builder.Services.AddControllers();\r\n\r\napp.UseHttpsRedirection();\r\napp.UseAuthorization();\r\napp.MapControllers();<\/pre>\n<p>The HTTPS redirect is a pesky feature that only applies to local to make life harder for developers. On the AWS cloud, the AWS Gateway handles traffic before it calls into your lambda function. Gutting dependencies also helps with cold starts. Trimming middleware like Controllers and Authorization keeps the request pipeline efficient because you get billed while the code is running. One technique to keep costs low, for example, is to use <a href=\"https:\/\/aws.amazon.com\/cognito\/\">Cognito<\/a> instead of doing auth inside the lambda function.<\/p>\n<h2>Run Your Lambda on Local<\/h2>\n<p>Luckily, .NET 6 makes this process somewhat familiar to .NET developers who are used to working with on-prem solutions. Be sure to have (or create) the following <code>launchSettings.json<\/code> under a <strong>Properties<\/strong> folder in the project:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">{\r\n  \"$schema\": \"https:\/\/json.schemastore.org\/launchsettings.json\",\r\n  \"profiles\": {\r\n    \"Pizza.Api\": {\r\n      \"commandName\": \"Project\",\r\n      \"dotnetRunMessages\": true,\r\n      \"launchBrowser\": false,\r\n      \"applicationUrl\": \"http:\/\/localhost:5095\",\r\n      \"environmentVariables\": {\r\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\r\n      }\r\n    }\r\n  }\r\n}<\/pre>\n<p>Now, go back to the console and run the app under the <code>..\\Pizza.Api\\src\\Pizza.Api<\/code> folder:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet watch<\/pre>\n<p>There should be an output telling you it is now running under the port number 5095.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"943\" height=\"350\" class=\"wp-image-95403\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/01\/word-image-95401-2.png\" \/><\/p>\n<p>This watcher tool automatically hot reloads, which is a nice feature from Microsoft, this is to keep up with code changes on the fly without restarting the app. This is mostly there for convenience, so I recommend keeping an eye out to make sure the latest code is actually running.<\/p>\n<p>Then, use CURL to test your lambda function on local, make sure the -i flag remains lowercase or try &#8211;include:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; curl -X GET -i -H \"Accept: application\/json\" http:\/\/localhost:5095<\/pre>\n<p>With the AWS tools, developers who are familiar with .NET should start to feel more at home. This is one of the niceties of the ecosystem, because the tools remain identical on the surface.<\/p>\n<p>The one radical departure so far is using the minimal API to host an endpoint and I will explore this topic next.<\/p>\n<h2>Make a Pizza<\/h2>\n<p>To make a pizza, first, you will need to install the DynamoDB NuGet package in the project. (If you are not acquainted with Amazon DynamoDB, you can get more information <a href=\"https:\/\/docs.aws.amazon.com\/amazondynamodb\/latest\/developerguide\/Introduction.html\">here<\/a>)<\/p>\n<p>In the same <code>Pizza.Api<\/code> folder, install the dependency:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet add package AWSSDK.DynamoDBv2<\/pre>\n<p>Also, this project requires a <a href=\"https:\/\/hexdocs.pm\/slugify\/Slug.html\">Slugify<\/a> dependency. This will convert a string, like a pizza name, into a unique identifier we can put in the URL to find the pizza resource in the API.<\/p>\n<p>To install the <code>slugify<\/code> dependency:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet add package Slugify.Core<\/pre>\n<p>With lambda functions, one goal is to keep dependencies down to a minimum. You may find it necessary to copy-paste code instead of adding yet another dependency to keep the bundle size small. In this case, I opted to add more dependencies to make writing this article easier for me.<\/p>\n<p>Create a <code>Usings.cs<\/code> file in the <code>Pizza.Api<\/code> directory in <code>src<\/code> and put these global usings in:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">global using Amazon;\r\nglobal using Amazon.DynamoDBv2;\r\nglobal using Amazon.DynamoDBv2.DataModel;\r\nglobal using Pizza.Api;\r\nglobal using Slugify;<\/pre>\n<p>Now, in the <code>Program.cs<\/code> file wire up dependencies through the IoC container:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">builder.Services.AddSingleton&lt;IAmazonDynamoDB&gt;( _ =&gt;\r\n  new AmazonDynamoDBClient(RegionEndpoint.USEast1));\r\nbuilder.Services.AddSingleton&lt;IDynamoDBContext&gt;(p =&gt;\r\n  new DynamoDBContext(p.GetService&lt;IAmazonDynamoDB&gt;()));\r\nbuilder.Services.AddScoped&lt;ISlugHelper, SlugHelper&gt;();\r\nbuilder.Services.AddScoped&lt;PizzaHandler&gt;();<\/pre>\n<p>Note your region can differ from mine. I opted to use the DynamoDB object persistence model via the Amazon.DynamoDBv2.DataModel namespace to keep the code minimal. This decision dings cold starts a bit, but only a little. Here though, I am paying the cost of latency to gain developer convenience.<\/p>\n<p>The DynamoDB object persistence model requires a pizza model with annotations so it can do the mapping between your C# code and the database table.<\/p>\n<p>Create a <code>PizzaModel.cs<\/code> file, and put this code in:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">namespace Pizza.Api;\r\n\r\n[DynamoDBTable(\"pizzas\")]\r\npublic class PizzaModel\r\n{\r\n  [DynamoDBHashKey]\r\n  [DynamoDBProperty(\"url\")]\r\n  public string Url { get; set; } = string.Empty;\r\n\r\n  [DynamoDBProperty(\"name\")]\r\n  public string Name { get; set; } = string.Empty;\r\n\r\n  [DynamoDBProperty(\"ingredients\")]\r\n  public List&lt;string&gt; Ingredients { get; set; } = new();\r\n\r\n  public override string ToString() =&gt;\r\n    $\"{Name}: {string.Join(',', Ingredients)}\";\r\n}<\/pre>\n<p>Given the table definition above, create the DynamoDB table via the AWS CLI tool:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; aws dynamodb create-table --table-name pizzas \\\r\n      --attribute-definitions AttributeName=url,AttributeType=S \\\r\n      --key-schema AttributeName=url,KeyType=HASH \\\r\n      --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \\\r\n     --region us-east-1 --query TableDescription.TableArn --output text<\/pre>\n<p>The url field is the hash which uniquely identifies the pizza entry. This is also the key used to find pizzas in the database table.<\/p>\n<p>Next, create the <code>PizzaHandler.cs<\/code> file, and put in a basic scaffold:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">namespace Pizza.Api;\r\n\r\npublic class PizzaHandler\r\n{\r\n  private readonly ISlugHelper _slugHelper;\r\n  private readonly IDynamoDBContext _context;\r\n  private readonly ILogger&lt;PizzaHandler&gt; _logger;\r\n\r\n  public PizzaHandler(\r\n    IDynamoDBContext context,\r\n    ISlugHelper slugHelper,\r\n    ILogger&lt;PizzaHandler&gt; logger)\r\n  {\r\n    _context = context;\r\n    _slugHelper = slugHelper;\r\n    _logger = logger;\r\n  }\r\n\r\n  public async Task&lt;IResult&gt; MakePizza(PizzaModel pizza)\r\n  {\r\n    throw new NotImplementedException();\r\n  }\r\n\r\n  public async Task&lt;IResult&gt; TastePizza(string url)\r\n  {\r\n    throw new NotImplementedException();\r\n  }\r\n}<\/pre>\n<p>This is the main file that will serve pizzas. The focus right now is the <code>MakePizza<\/code> method.<\/p>\n<p>Before continuing, create the <code>PizzaHandlerTests.cs<\/code> file under the test project:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">namespace Pizza.Api.Tests;\r\n\r\npublic class PizzaHandlerTests\r\n{\r\n  private readonly Mock&lt;IDynamoDBContext&gt; _context;\r\n  private readonly PizzaHandler _handler;\r\n\r\n  public PizzaHandlerTests()\r\n  {\r\n    var slugHelper = new Mock&lt;ISlugHelper&gt;();\r\n    _context = new Mock&lt;IDynamoDBContext&gt;();\r\n    var logger = new Mock&lt;ILogger&lt;PizzaHandler&gt;&gt;();\r\n\r\n    _handler = new PizzaHandler(\r\n      _context.Object,\r\n      slugHelper.Object,\r\n      logger.Object);\r\n  }\r\n}<\/pre>\n<p>The test project should have its own Usings.cs file, add these global entries:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">global using Moq;\r\nglobal using Amazon.DynamoDBv2.DataModel;\r\nglobal using Microsoft.Extensions.Logging;\r\nglobal using Slugify;<\/pre>\n<p>Also, install the <a href=\"https:\/\/docs.educationsmediagroup.com\/unit-testing-csharp\/moq\/quick-glance-at-moq\">Moq<\/a> dependency under the <code>Pizza.Api.Tests<\/code> project:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet add package Moq<\/pre>\n<p>You will also need the Slugify and Amazon.DynamoDBv2 packages seen in the <code>Pizza.Api<\/code> project as well. First, I like to unit test my code to check that my reasoning behind the code is sound. This is a technique that I picked up from my eight-grade teacher: \u201ctest early and test often\u201d. The faster the feedback loop is between code you just wrote and a reasonable test, the more effective you can be in getting the job done.<\/p>\n<p>Inside the <code>PizzaHandlerTests<\/code> class, create a unit test method:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">[Fact]\r\npublic async Task MakePizzaCreated()\r\n{\r\n  \/\/ arrange\r\n  var pizza = new PizzaModel\r\n  {\r\n    Name = \"Name\",\r\n    Ingredients = new List&lt;string&gt; {\"toppings\"}\r\n  };\r\n\r\n  \/\/ act\r\n  var result = await _handler.MakePizza(pizza);\r\n\r\n  \/\/ assert\r\n  Assert.Equal(\"CreatedResult\", result.GetType().Name);\r\n}<\/pre>\n<p>There is a little quirkiness here because the <code>Results<\/code> class in minimal API is actually hidden behind private classes. The only way to get to the result type is via reflection, which is unfortunate because the unit test is not able to validate the strongly typed class. Hopefully in future LTS (Long Term Support) releases the team will fix this odd behaviour.<\/p>\n<p>Now, write the MakePizza method in the PizzaHandler to pass the unit test:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">public async Task&lt;IResult&gt; MakePizza(PizzaModel pizza)\r\n{\r\n  if (string.IsNullOrWhiteSpace(pizza.Name)\r\n    || pizza.Ingredients.Count == 0)\r\n  {\r\n    return Results.ValidationProblem(new Dictionary&lt;string, string[]&gt;\r\n    {\r\n      {nameof(pizza), new [] \r\n                {\"To make a pizza include name and ingredients\"}}\r\n    });\r\n  }\r\n\r\n  pizza.Url = _slugHelper.GenerateSlug(pizza.Name);\r\n\r\n  await _context.SaveAsync(pizza);\r\n  _logger.LogInformation($\"Pizza made! {pizza}\");\r\n\r\n  return Results.Created($\"\/pizzas\/{pizza.Url}\", pizza);\r\n}<\/pre>\n<p>With minimal API, you simply return an <code>IResult<\/code>. The <code>Results<\/code> class supports all the same behaviour you are already familiar with from the <code>BaseController<\/code> class. The one key difference is there is a lot less bloat here which is ideal for a lambda function that runs on the AWS cloud.<\/p>\n<p>Finally, go back to the <code>Program.cs<\/code> file and add new endpoints right before the <code>app.Run<\/code>.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">using (var serviceScope = app.Services.CreateScope())\r\n{\r\n  var services = serviceScope.ServiceProvider;\r\n  var pizzaApi = services.GetRequiredService&lt;PizzaHandler&gt;();\r\n\r\n  app.MapPost(\"\/pizzas\", pizzaApi.MakePizza);\r\n  app.MapGet(\"\/pizzas\/{url}\", pizzaApi.TastePizza);\r\n}<\/pre>\n<p>One nicety from minimal API is how well this integrates with the existing IoC container. You can map requests to a method, and the model binder does the rest. Those of you familiar with Controllers should see code that reads identical in the <code>PizzaHandler<\/code>.<\/p>\n<p>Then, make sure the dotnet watcher CLI tool is running the latest code and test your endpoint via CURL:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; curl -X POST -i -H \"Accept: application\/json\" ^\r\n    -H \"Content-Type: application\/json\" ^\r\n    -d \"{\\\"name\\\":\\\"Pepperoni Pizza\\\",\\\"ingredients\\\":[\\\"tomato sauce\\\",\\\"cheese\\\",\\\"pepperoni\\\"]}\" http:\/\/localhost:5095\/pizzas<\/pre>\n<p>Feel free to play with this endpoint on local. Notice how the endpoint is strongly typed, if you pass in a list of ingredients as raw numbers then validation fails the request. If there is data missing, validation once again kicks the unmade pizza back with a failed request.<\/p>\n<p>You may be wondering how the app running on local is able to talk to DynamoDB. This is because the SDK picks up the same credentials used by the AWS CLI tool. If you can access resources on AWS, then you are also able to point to DynamoDB using your own personal account with C#.<\/p>\n<h2>Taste a Pizza<\/h2>\n<p>With a fresh pizza made, time to taste the fruits of your labor.<\/p>\n<p>In the <code>PizzaHandlerTests<\/code> class, add this unit test:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">[Fact]\r\npublic async Task TastePizzaOk()\r\n{\r\n  \/\/ arrange\r\n  _context\r\n    .Setup(m =&gt; m.LoadAsync&lt;PizzaModel?&gt;(\"url\", default))\r\n    .ReturnsAsync(new PizzaModel());\r\n\r\n  \/\/ act\r\n  var result = await _handler.TastePizza(\"url\");\r\n\r\n  \/\/ assert\r\n  Assert.Equal(\"OkObjectResult\", result.GetType().Name);\r\n}<\/pre>\n<p>This only checks the happy path; you can add more tests to check for failure scenarios and increase code coverage. I\u2019ll leave this as an exercise to you the reader, if you need help, please check out the GitHub repo.<\/p>\n<p>To pass the test, put in place the <code>TastePizza<\/code> method inside the <code>PizzaHandler<\/code>:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">public async Task&lt;IResult&gt; TastePizza(string url)\r\n{\r\n  var pizza = await _context.LoadAsync&lt;PizzaModel?&gt;(url);\r\n\r\n  return pizza == null\r\n    ? Results.NotFound()\r\n    : Results.Ok(pizza);\r\n}<\/pre>\n<p>Then, test this endpoint via CURL:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; curl -X GET -i -H \"Accept: application\/json\" http:\/\/localhost:5095\/pizzas\/pepperoni-pizza<\/pre>\n<h2>Onto the Cloud!<\/h2>\n<p>With a belly full of pizza, I hope nobody feels hungry, deploying this to the AWS cloud feels seamless. The good news is that the template already does a lot of the hard work for you so you can focus on a few key items.<\/p>\n<p>First, tweak the <code>serverless.template<\/code> file and set the memory and CPU allocation. Do this in the JSON file:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">{\r\n  \"Properties\": {\r\n    \"MemorySize\": 2600,\r\n    \"Architectures\": [\"x86_64\"]\r\n  }\r\n}<\/pre>\n<p>This sets a memory allocation of 2.5GB, with a x86 processor. These allocations are not final because you really should do monitoring and tweaking to figure out an optimal allocation for your lambda function. Increasing the memory blindly does not guarantee best results, luckily there is a nice <a href=\"https:\/\/docs.aws.amazon.com\/lambda\/latest\/operatorguide\/computing-power.html\">guide from AWS<\/a> that is very helpful.<\/p>\n<p>Before you can deploy, you\u2019ll need to create an S3 bucket which is where the deploy bundle will go. Note that you may need to provide your own name for the S3 bucket:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; aws s3api create-bucket --acl private --bucket pizza-api-upload \\\r\n    --region us-east-1 --object-ownership BucketOwnerEnforced\r\n&gt; aws s3api put-public-access-block --bucket pizza-api-upload \\\r\n    --public-access-block-configuration \"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true\"<\/pre>\n<p>Then, deploy your app to the AWS cloud via the dotnet AWS tool:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; dotnet lambda deploy-serverless --stack-name pizza-api --s3-bucket pizza-api-upload<\/pre>\n<p>Unfortunately, the <code>dotnet lambda<\/code> deploy tool does not handle role policies for DynamoDB automatically. Login into AWS, go to IAM, click on roles, then click on the role the tool created for your lambda function. It should be under a logical name like <code>pizza-api-AspNetCoreFunctionRole-818HE2VECU1J.<\/code><\/p>\n<p>Then, copy this role name, and create a file <code>dynamodb.json<\/code> with the access rules:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">{\r\n  \"Version\": \"2012-10-17\",\r\n  \"Statement\": [\r\n    {\r\n      \"Action\": [\r\n        \"dynamodb:DescribeTable\",\r\n        \"dynamodb:GetItem\",\r\n        \"dynamodb:PutItem\",\r\n        \"dynamodb:UpdateItem\"\r\n      ],\r\n      \"Effect\": \"Allow\",\r\n      \"Resource\": \"*\"\r\n    }\r\n  ]\r\n}<\/pre>\n<p>Now, from the Pizza.Api project folder, grant role access to DynamoDB via this command:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">&gt; aws iam put-role-policy --role-name pizza-api-AspNetCoreFunctionRole-818HE2VECU1J \\\r\n      --policy-name PizzaApiDynamoDB --policy-document file:\/\/.\/dynamodb.json<\/pre>\n<p>Be sure to specify the correct role name for your lambda function.<\/p>\n<p>Finally, taste a pre-made pizza via CURL. Note that the dotnet lambda deploy tool should have responded with a URL for your lambda function. Make sure the correct <code>GATEWAY_ID<\/code> and <code>REGION<\/code> go in the <code>URL<\/code>.:<\/p>\n<pre class=\"lang:ps theme:powershell-ise \">&gt; curl -X GET -i ^\r\n  -H \"Accept: application\/json\" https:\/\/GATEWAY_ID.execute-api.REGION.amazonaws.com\/Prod\/pizzas\/pepperoni-pizza<\/pre>\n<p>I recommend poking around in AWS to get more familiar with lambda functions.<\/p>\n<p>The API Gateway runs the lambda function via a reverse proxy. This routes all HTTPS traffic directly to the kestrel host, which is the same code that runs on local. This is a bit more costly because the lambda function routes all traffic, but the developer experience is greatly enhanced by this. Go to S3 and find your pizza-api-upload bucket, notice the bundle size remains small, around 2MB. The dotnet AWS tool might be doing some trimming to keep cold starts low. Also, look at <a href=\"https:\/\/aws.amazon.com\/cloudwatch\/\">CloudWatch<\/a> and check the logs for your lambda function. You will find cold starts in general are below .5 sec, this is great news! In .NET 6, the AWS team has been able to make vast improvements which I believe will continue in future LTS releases. Lastly, note the VM that executes the lambda runs on Linux, this is another area of improvement that is also possible in .NET 6.<\/p>\n<h2>Conclusion<\/h2>\n<p>AWS lambda functions with C# are now production ready. The teams from both Microsoft and AWS have made significant progress in .NET 6 to make this dream a reality.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Serverless computing is pushing C# to evolve to the next level. This is exciting because you pay-per-use and only incur charges when the code is running. This means that .NET 6 must spin up fast, do its job, then die quickly. This mantra of birth and rebirth pushes developers and the underlying tech to think&#8230;&hellip;<\/p>\n","protected":false},"author":274017,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[137092,53],"tags":[],"coauthors":[41241],"class_list":["post-95401","post","type-post","status-publish","format-standard","hentry","category-aws","category-featured"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/95401","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/274017"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=95401"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/95401\/revisions"}],"predecessor-version":[{"id":95407,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/95401\/revisions\/95407"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=95401"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=95401"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=95401"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=95401"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}