Vaughan

Creating an AWS Lambda function written in C# .NET Core

.NET Core, AWS

With more and more companies moving to the cloud, the idea of serverless execution of your code has more and more appeal. AWS has Lambda and Azure has Functions. The idea is the same: You give them a method that you want to execute and the provider works out the scaling. As you get more requests, it will create more and more instances. The obvious benefit is that you don't need to worry about where or how your application is running. Its a cheap and easy way to execute code in the cloud.

If you are using visual studio and AWS then you most probably should use the AWS Toolkit extension. Its quite user friendly and lets you do things like create a Lambda project and deploy seamlessly to your account. To show an example of how easy it is, I created a lambda function that will listen to a kinesis stream, deserialize the JSON into a object and save it into a DynamoDB table.

I started with one of the example templates from the AWS Toolkit that logs out a Kinesis stream event. I then used the following two packages to inject in the DynamoDB instance that will do the saving.

AWSSDK.Extensions.NETCore.Setup
//Adds the general types that you use to get AWS configuration
//Adds the extension method AddAWSService

AWSSDK.DynamoDBv2
//This contains the interface and concrete type to work with
//the DynamoDB tables

Below is the code for the Lambda function. A few small things to note:

  1. You generally should use environment variables for configurable things like table names.
  2. Try to put as much initializing code in the constructor. Its better for performance as AWS can then share the initialization across calls.
  3. The ConfigureServices method is pretty much the same as it would be if an external service is connecting to AWS services
  4. You can import other projects and libraries as you would in other applications but there is a size limit to your application of 50MB.

public class Function
{
    private ServiceProvider _serviceProvider;
    private readonly string tableName;
    private readonly IAmazonDynamoDB _database;

    public Function()
    {
        ConfigureServices();
        tableName = Environment.GetEnvironmentVariable("TableName");
        _database = _serviceProvider.GetService<IAmazonDynamoDB>();
    }

    public async Task FunctionHandler(KinesisEvent kinesisEvent, ILambdaContext context)
    {
        context.Logger.LogLine($"Beginning to process {kinesisEvent.Records.Count} records...");

        foreach (var record in kinesisEvent.Records)
        {
            string recordData = GetRecordContents(record.Kinesis);
            context.Logger.LogLine($"Record Data:");
            context.Logger.LogLine(recordData);

            var response = await _database.PutItemAsync(new PutItemRequest(tableName, ToDictionary(recordData)));

            context.Logger.LogLine($"Saved:{response.HttpStatusCode}");
        }

        context.Logger.LogLine("Stream processing complete.");
    }

    private Dictionary<string, AttributeValue> ToDictionary(string model)
    {
        var person = JsonConvert.DeserializeObject<Person>(model);
        var bag = new Dictionary<string, AttributeValue>()
                    {
                        { "Name", new AttributeValue(person.Name)},
                        { "Age", new AttributeValue(person.Age.ToString())},
                        { "Children", new AttributeValue(person.Children)},
                    };

        return bag;
    }

    private void ConfigureServices()
    {
        var serviceCollection = new ServiceCollection();

        serviceCollection.AddDefaultAWSOptions(new Amazon.Extensions.NETCore.Setup.AWSOptions
        {
            Region = RegionEndpoint.EUWest1
        });
        serviceCollection.AddAWSService<IAmazonDynamoDB>();

        _serviceProvider = serviceCollection.BuildServiceProvider();
    }

    private string GetRecordContents(KinesisEvent.Record streamRecord)
    {
        using var reader = new StreamReader(streamRecord.Data, Encoding.UTF8);
        return reader.ReadToEnd();
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public List<string> Children { get; set; }
}

You can now test this with a test event either in AWS or locally with the toolkit. Below is an example of a test event.

{
  "Records": [
    {
      "kinesis": {
        "partitionKey": "partitionKey-03",
        "kinesisSchemaVersion": "1.0",
        "data": "eyJOYW1lIjoiSm9obiIsICJBZ2UiOjMwLCAiQ2hpbGRyZW4iOlsiUGV0ZXIiLCJQYXVsIl19",
        "sequenceNumber": "49545115243490985018280067714973144582180062593244200961",
        "approximateArrivalTimestamp": 1428537600
      },
      "eventSource": "aws:kinesis",
      "eventID": "shardId-000000000000:49545115243490985018280067714973144582180062593244200961",
      "invokeIdentityArn": "arn:aws:iam::EXAMPLE",
      "eventVersion": "1.0",
      "eventName": "aws:kinesis:record",
      "eventSourceARN": "arn:aws:kinesis:EXAMPLE",
      "awsRegion": "eu-west-1"
    }
  ]
}

Related Posts

BMC logoBuy me a coffee