Using MediatR with .NET Lambda Functions and Function URLs

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.

In a previous post, I showed how to use the MediatR library with minimal API endpoints. It makes it very easy to keep business logic out of endpoints and similar constructs.

In this post, I will show how to use it with AWS Lambda functions that are triggered by Function URLs.

When using Function URLs, a single Lambda function can be triggered by GET, PUT, POST, DELETE, etc, HTTP methods.

Using MediatR, you can invoke an appropriate handler based on the HTTP method.

If you are not familiar with Lambda Function URLs, read this blog post, it will explain how to create the project, deploy the function, and add a Function URL to it. I won’t be covering those steps in this post. If you have never used Function URLs before, I suggest you follow all the steps in that post first, see that it works, then come back here.

1. Create a Lambda project, with a Function URL

Follow the blog post referred to above, but name the project “FunctionUrlWithMediatR” instead of “SimpleLambdaWithFunctionUrl”.

2. Add NugGet packages

At this point, you should have a project with very little code in it, but it works when invoked with a GET request.

Add two more NuGet packages to your project:

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
dotnet add package Microsoft.Extensions.DependencyInjection

3. Update Program.cs

Open the Function.cs file and replace the contents with the following code -

 1using System.Text.Json;
 2using Amazon.Lambda.APIGatewayEvents;
 3using Amazon.Lambda.Core;
 4using MediatR;
 5using Microsoft.Extensions.DependencyInjection;
 6
 7[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
 8
 9namespace FunctionUrlWithMediatR;
10
11public class Function
12{
13    readonly ServiceProvider _serviceProvider;
14    
15    public Function()
16    {
17        var services = new ServiceCollection();
18        services.AddMediatR(typeof(Function));
19        _serviceProvider = services.BuildServiceProvider();
20    }
21    
22    public async Task<APIGatewayHttpApiV2ProxyResponse> FunctionHandler(APIGatewayHttpApiV2ProxyRequest incomingHttpRequest, ILambdaContext context)
23    {
24        var mediator = _serviceProvider.GetService<IMediator>();
25        string textToReturn = incomingHttpRequest.RequestContext.Http.Method switch
26        {
27            "GET" => await mediator.Send(new GetRequest(incomingHttpRequest)),
28            "PUT" => await mediator.Send(new PutRequest(incomingHttpRequest)),
29            "POST" => await mediator.Send(new PostRequest(incomingHttpRequest)),
30            "DELETE" => await mediator.Send(new DeleteRequest(incomingHttpRequest)),
31            _ => default
32        };
33
34        var apiResponse = new APIGatewayHttpApiV2ProxyResponse()
35        {
36            StatusCode = 200,
37            Body = JsonSerializer.Serialize(textToReturn)
38        };
39        return apiResponse;
40    }
41}
  • Lines 1-5, the necessary using statements.
  • Line 13-20, set up the dependency injection container and add MediatR to it.
  • Line 25, get the MediatR service from the container.
  • Lines 26-33, use the switch statement to send the appropriate MediatR request based on the HTTP method of the incoming request.
  • Lines 35-40, create the APIGatewayHttpApiV2ProxyResponse and return.

4. Create the MediatR requests and handlers

Create MediatR request and handler for the GET method.

 1using Amazon.Lambda.APIGatewayEvents;
 2using MediatR;
 3
 4namespace FunctionUrlWithMediatR;
 5
 6public class GetRequest : IRequest<string>
 7{
 8    public APIGatewayHttpApiV2ProxyRequest IncomingHttpRequest { get; set; }
 9}
10
11public class GetHandler : IRequestHandler<GetRequest, string>
12{
13    public Task<string> Handle(GetRequest request, CancellationToken cancellationToken)
14    {
15        return Task.FromResult($"You sent a GET request to {request.IncomingHttpRequest.RawPath}");
16    }
17}   

Do the same for “PUT”, “POST”, and “DELETE”. The attached zip file has the full source code.

5. Update the deployed function

If you followed the steps in the previous post, you should be able to update your deployed function with the following command -

dotnet lambda deploy-function FunctionUrlWithMediatR

6. Test the function

Now you can make GET, PUT, POST, and DELETE requests to the Function URL and see the appropriate response.

In the attached zip file, there are sample requests in the Test.http, you can use this with the Visual Studio Code extension REST Client, and with the HTTP Client tool with Rider. You will need to change to URL to match your Function URL.

A quick note on cold starts

If you have a .NET Lambda function that is called infrequently, it is more likely to suffer from cold starts. But if you have a Lambda function that handles more requests, it is less likely to suffer from cold starts.

If you find yourself in this scenario, consider using the above approach to combine related pieces of business logic into a single Lambda function while keeping the code clean. This might help reduce the number of cold starts.

Download full source code.

comments powered by Disqus

Related