C# and AWS Lambdas, Part 7 – .NET 5 Web API inside a Container inside a Lambda, with API Gateway in front

Full source code available here.

In my previous post I showed how to build a .NET 5 library inside a docker image and deploy it to an AWS Lambda. This post is a small extension on that.

I’m going to build a .NET 5 Web API application, turn it into a docker image, deploy it to an AWS Lambda, and connect an API Gateway to the Lambda to call the controller inside the application, inside the container, inside the Lambda!

The Web API application
This is the same as what I did in part 2 of this series.

Use the AWS template –

dotnet new serverless.AspNetCoreWebAPI --name Dotnet5DockerWebAPILambda

In LambdaEntryPoint.cs change the class so that it inherits from to Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction.

You can change what the controllers return if you like, in the attached code example I have –

public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult Get()
    {
        return Ok($"Hello World - {DateTime.Now}");
    }
    // snip...

The Dockerfile

FROM public.ecr.aws/lambda/dotnet:5.0 as base

FROM mcr.microsoft.com/dotnet/sdk:5.0-alpine-amd64 AS build
WORKDIR /source

COPY *.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish --no-restore -c Release -o /app/publish

FROM base AS final
WORKDIR /var/task
COPY --from=build /app/publish .
CMD ["Dotnet5DockerWebAPILambda::Dotnet5DockerWebAPILambda.LambdaEntryPoint::FunctionHandlerAsync"]

This Dockerfile uses the AWS .NET 5 base image from their Elastic Container Repository.

Note the CMD that specifies the entry point to the application.

The Lambda
Follow the procedure described in part 6 of this series.

Build the image.

docker build -t dotnet5dockerwebapilambda .

Create a repository to store the image.
Push the image to the repository.

Create the Lambda (same as in part 6).
Select the container image you uploaded.

You won’t be able to test this Lambda function as easily as in the previous post, I have not had luck with the AWS provided test Json files from the .NET project template.

Connecting the API Gateway
I gave an example of how to connect an API Gateway to a Lambda in the second post in this series, the procedure is the same .

Click Create API.

Top of the list should be HTTP API, click Build.

Hit Add integration, choose Lambda, and pick the Lambda function created earlier.
Select Version 2.0.
Give the API a name.
Hit next.

Change resource path to $default – this is a wildcard that will match any controller in the Web API application.

In Configure Stages don’t change anything for this simple example.
Hit next.

In Review and Create confirm that it looks correct and hit Create in the bottom right.

You should now have an API Gateway setup that looks something like –

Click on the Invoke URL and you should get a response like –

“Welcome to running ASP.NET Core on AWS Lambda”

Change the url to include the route of the controller and by adding /api/values, the output from that action method should look like –

“Hello World – 3/23/2021 10:22:42 PM”

The Base Image
You might have noticed that I used the same base image in this and the previous post, but the previous post was a .Net 5 library. This means that the base image can run both libraries and Web API, not ideal if all you want to run is the library because you are getting the full .Net 5 ASP.NET Runtime. See update 2 of part 6 of this series for some brief instructions on building a base image with the .NET 5 Runtime.

Full source code available here.

C# and AWS Lambdas, Part 6 – .NET 5 inside a Container inside a Lambda

Full source code available here.

A few months ago AWS released a feature allowing Lambdas to run container images, for larger applications this is easier to work with than a zip file or set of layers, it also lets you move your already containerized apps to Lambda with a small effort.

I was interested to see if I could get a .NET 5 “Hello World” application running in this manner. There is a blog on the AWS site explaining how to do this with Visual Studio and the AWS Toolkit, but primarily use VS Code so I could not leverage those tools, and it would be fun to figure out.

UPDATE 1 – when I published this blog post I was unaware of a pre-made .NET 5 Docker image for AWS Lambda so I built my own following instructions found on an AWS GitHub repo. Those instructions are below.
But it is much easier to use the AWS provided image available here – https://gallery.ecr.aws/lambda/dotnet and use public.ecr.aws/lambda/dotnet:5.0.

UPDATE 2 – the AWS Lambda image above seems to have the full .NET 5 ASP.NET Runtime as opposed to the .NET 5 Runtime, if all you are running in the container is a library, then having the full .NET 5 ASP.NET Runtime will be larger than you need. As of now, I don’t see an image on the AWS ECR page for the .NET 5 Runtime. But you can build it your self following the below instructions, with one change, on line 49 of this file https://github.com/aws/aws-lambda-dotnet/blob/087590ce99274e16e26d37e1dfd73b0b71d1230a/LambdaRuntimeDockerfiles/dotnet5/Dockerfile, change the linked tar.gz to the appropriate file in here https://versionsof.net/core/5.0/.

Building your own base image (optional)
You can’t use just any docker image for a .NET 5 application, it has to have special Lambda and AWS components included, here is how to build your own, if you want to, from the AWS code on GitHub.

But it is much easier to use the one image AWS provides – https://gallery.ecr.aws/lambda/dotnet
Now that you have a base image, let’s move to the application.

Building the hello world container
Create a new .NET Lambda using –

dotnet new lambda.EmptyFunction --name HelloWorldLambdaContainer

This crates a simple library that converts an input string to uppercase.
If you wanted to, you could build this, zip it and deploy it to a Lambda as I showed in part 1 of this series.

But I want to put this in a container.

You need Docker for the rest of this, there are plenty of tutorials out there including one from my friend Steve Gordon.

In the source code directory add a file named Dockerfile with the below content. Note that it’s using the image I put on Docker Hub.

FROM public.ecr.aws/lambda/dotnet:5.0 AS base

FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim as build

WORKDIR /source

COPY *.csproj .
RUN dotnet restore

COPY . .
RUN dotnet publish --no-restore -c Release -o /app/publish

FROM base AS final
WORKDIR /var/task
COPY --from=build /app/publish .
CMD ["HelloWorldLambdaContainer::HelloWorldLambdaContainer.Function::FunctionHandler"]

Everything is in place, build the image.

docker build -t helloworldlambdacontainer .

Over in AWS we need a repository to store the image.

Go to the Elastic Container Registry.

Create a repository.

Navigate into the repository and you will see a button labeled “View push commands”. Follow those instructions to upload the container image to repository.

That’s everything done here, over to the Lambda.

The Lambda
This is similar to what I showed in part one, but this time the function will be based on a container image.

Create a new Lambda function, select Container image from the options.

Give the function a name.

Hit Browse images, and select the container image from the repository created above.

It will take a few moments for AWS to create the function. Once it is up, open the test tool in the top right of the screen and click “Configure test events”.

Set the event name, and replace the body with this – “hello world”.

Hit the “Test” button in the top right.

You should see something like – “Execution result: succeeded(logs)”. Expand the Details and you will see “HELLO WORLD” and bunch of other information about the execution of the Lambda.

That is a .NET 5 library running inside container, running inside a Lambda, not bad!

But what if you could get Kestrel up and running and map API Gateway requests to it, that would be fun…and that’s in the next post of this series.

Full source code available here.

Polly and Blazor, Part 3 – Dependency Injection

Full source code available here.

This post continues to build on the two previous on using Polly with Blazor. The first was a simple example of using a Wait and Retry while updating the display as the policy retried, the second did the same but made use of the Polly Context. The Polly Context is something I have not made a lot of use of in the past, but I have a feeling that it will be very helpful with Blazor applications.

I’m keeping the examples similar, deliberately changing a single thing in each. In this post I will define the Wait and Retry Policy in the Startup, add it to the service collection and inject it into the Razor page.

One big difference to note is that I can no longer update the display from inside the OnRetry delegate of the Policy. Gone is the call to InvokeAsync(StateHasChanged) because this is dependent on the the ComponentBase.

In Startup.cs add the following, it defines the Wait and Retry policy, and its OnRetry delegate –

public void ConfigureServices(IServiceCollection services)
    {
    IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
    r => !r.IsSuccessStatusCode)
    .WaitAndRetryAsync(3,
        retryAttempt => TimeSpan.FromSeconds(retryAttempt), onRetry: (response, retryDelay, retryCount, context) =>
        {
            context["message"] = context["message"] + $"\nReceived: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds";
        });

    services.AddSingleton<IAsyncPolicy<HttpResponseMessage>>(_httpRequestPolicy);
    // snip...

In the Razor section of the Index.razor page add this –

@inject IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy

The policy will now be injected into the Razor page.

Using it is simple, see line 16 below –

@code {
   HttpClient httpClient = new HttpClient() {
        BaseAddress =  new Uri("http://localhost:5000")
    };

    private string status;
    private Context context = new Polly.Context("", new Dictionary<string, object>() {{"message",""}}); 
    private string responseCode;
    private string number;

    private async Task CallRemoteServiceAsync()
    {
        status = "Calling remote service...";
        string requestEndpoint = "faulty/";

        HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(ctx => httpClient.GetAsync(requestEndpoint), context);
        responseCode = httpResponse.StatusCode.ToString();
        number = await httpResponse.Content.ReadAsStringAsync();
        status = "Finished";
    }
}

Instead of injecting a Polly Policy you could inject a Polly Registry with more than one policy. I have written about the registry a few times on this blog.

Full source code available here.

Polly and Blazor, Part 2 – Using the Context

Full source code available here.

This post is a short follow up to the one where I used Polly Wait and Retry in Blazor. In that post I used variables defined in my C# method to pass information back to the calling code, to display on the screen. But here I’m going to show how the Polly Context can be used instead.

The setup and almost all the code is the same, so please take a look at that post.

Using the Context
The Razor section references the message in the Polly context rather than a defined variable for the message –

<p>Polly Summary : @context["message"]</p>

In the code block add a Polly Context and initialize the dictionary with a message, this is important because the Razor block references this message when the page loads.

@code {
    //snip...

    private Context context = new Polly.Context("", new Dictionary<string, object>() {{"message",""}});
    //snip... 

Then in the rest of the code block, update the message in the context with the information to display to the user.

private async Task CallRemoteServiceAsync()
{
    IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
        r => !r.IsSuccessStatusCode)
        .WaitAndRetryAsync(3,
            retryAttempt => TimeSpan.FromSeconds(retryAttempt * 2), onRetry: (response, retryDelay, retryCount, context) => {
                context["message"] = $"Recieved: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds\n";
                Console.WriteLine(context["message"]);
                InvokeAsync(StateHasChanged);
    });
    status = "Calling remote service...";
    string requestEndpoint = "faulty/";
    HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(ctx => httpClient.GetAsync(requestEndpoint), context);
    responseCode = httpResponse.StatusCode.ToString();
    number = await httpResponse.Content.ReadAsStringAsync();
    status = "Finished";
    context["message"] = "";
}

That’s it, another way to do the same thing.

In the next post I’ll show how to use a Polly Policy defined within the OnInitializedAsync block.

Full source code available here.

Polly and Blazor, Part 1 – Simple Wait and Retry

Full source code available here.

A few weeks ago I gave a talk on Polly at Dotnetconf (you can check it out here), at the end I got many questions about using Polly with Blazor, I had never tried it, but assumed it would work.

In this blog, part 1 of a few on Polly and Blazor, I’ll show you how to get a simple retry mechanism up and working in a Blazor Server App. I am not concerned with dependency injections, policy registries or anything other fancy things in this post, that will come later..

The setup
I have a .Net 5 Blazor Server app created with

dotnet new blazorserver

I stripped out the Blazor pages I didn’t need, and changed the code of the Index.razor file to make a HTTP call to a controller that generates errors 75% of time. I removed links to the other Blazor pages too.

Around the HTTP call I use the Polly Wait and Retry Policy to retry up three times, with a delay between each request. If you haven’t used Polly before, I have written a LOT about it, and have a Pluralsight course on it.

I made a few updates to the UI to show what the HttpClient and Polly are doing.

The Wait and Retry policy will retry after 2, 4 and 6 seconds. It prints out the to the console and updates the UI with the errors it gets from the remote system and details about the retry.

Because this is my first Blazor app, it took a little longer and may have some less than perfect approaches, but you should be able to take the retry idea and apply it to you application.

Before I show the code for retrying, let me show how I added a familiar Web API controller.

In Configure(..) method of Startup.cs I added endpoints.MapControllers(); to the below block so it looks like this –

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers(); // added this
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

Then I created a Controllers directory and added a new file FaultyController.cs.

Here is its code –

using Microsoft.AspNetCore.Mvc;

namespace WebApiDataDog.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class FaultyController : ControllerBase
    {
        private static int counter = 0;

        public FaultyController()
        {
        }

        [HttpGet]
        public ActionResult Get()
        {
            if (++counter % 4 == 0)
            {
                return Ok(counter);
            }
            else if(counter % 4 == 1)
            {
                return StatusCode(501);
            }
            else if(counter % 4 == 2)
            {
                return StatusCode(502);
            }
            else
            {
                return StatusCode(503);
            }
        }
    }
}

And here is my Razor page that uses the Polly Wait and Retry policy.

@page "/"
@using Polly

<h1>Polly Test</h1>
<p>This demo uses a HttpClient to call a controller that is setup to fail 75% of the time (first three request will fail, fourth will succeed). The Polly Retry policy will kick in a retry up to three time.</p>

<p>Status: @status</p>
<p>Polly Summary : @pollyMessage</p>

<p>Response Code : @responseCode</p>
<p>Response Body : @number</p>

<button class="btn btn-primary" @onclick="CallRemoteServiceAsync">Call remote service</button>

@code {
   HttpClient httpClient = new HttpClient() {
        BaseAddress =  new Uri("http://localhost:5000")
    };

    private string status;
    private string pollyMessage;
    private string responseCode;
    private string number;

    private async Task CallRemoteServiceAsync()
    {
        IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy = Policy.HandleResult<HttpResponseMessage>(
            r => !r.IsSuccessStatusCode)
            .WaitAndRetryAsync(3,
                retryAttempt => TimeSpan.FromSeconds(retryAttempt * 2), onRetry: (response, retryDelay, retryCount, context) => {
                    pollyMessage = $"Recieved: {response.Result.StatusCode}, retryCount: {retryCount}, delaying: {retryDelay.Seconds} seconds\n";
                    Console.WriteLine(pollyMessage);
                    InvokeAsync(StateHasChanged);
        });

        status = "Calling remote service...";
        string requestEndpoint = "faulty/";

        HttpResponseMessage httpResponse = await _httpRequestPolicy.ExecuteAsync(() => httpClient.GetAsync(requestEndpoint));
        
        responseCode = httpResponse.StatusCode.ToString();
        number = await httpResponse.Content.ReadAsStringAsync();
        status = "Finished";
        pollyMessage = "";
    }
}

As I said above, I’m going to write a few more posts on Polly and Blazor to show some other scenarios, like using the Polly Context to pass data around and dependency injection (hopefully).

Full source code available here.