Skip to content

cythral/lambdajection

Repository files navigation

Lambdajection

Nuget Nuget GitHub GitHub Workflow Status Sponsor on Github Donate on Paypal

Write elegant and testable AWS Lambdas using .NET Core and Microsoft's Dependency Injection + Configuration extensions. No longer do you need to write your own boilerplate to achieve this - just write your Lambda code and service configuration!

Lambdajection aims to:

  • Facilitate rapid and secure lambda development using C#.
  • Increase Lambda testability by enabling use of Microsoft's dependency injection, configuration and logging extensions.
  • Achieve faster startup times (and lower costs) by using compile-time code generation rather than reflection wherever possible.
  • Optimize for container reuse by reusing service collections and services between invocations.
  • Be highly extensible and configurable.

Community contribution/pull requests are welcome and encouraged! See the contributing guide for instructions. Report issues on JIRA - you can report anonymously or include github username/contact info in the ticket summary.

Table of Contents

1. Installation

1.1. Metapackage

See the packages section for a list of available packages.

dotnet add package Lambdajection

1.2. Templates

ℹ Templates will be available starting in v0.5.0-beta1

dotnet new -i Lambdajection.Templates

1.3. Development Builds

Development builds are generated for PRs and uploaded to GitHub Packages. To use them, update the user config file for nuget (varies by OS - see this article) and add this to the packageSourceCredentials section of that file:

<github>
    <add key="Username" value="USERNAME" />
    <add key="ClearTextPassword" value="TOKEN" />
</github>

Replace USERNAME with your username, and TOKEN with a personal access token from GitHub that has permissions to read packages. It is important that this goes in the user config file rather than the project one, so that you do not accidentally leak your personal access token to the world.

Then, in your project's nuget.config file, add the following to your packageSources section:

<add key="github" value="https://nuget.pkg.github.com/cythral/index.json" />

Finally, you may use development builds by adding the package and version to your .csproj, for instance:

<PackageReference Include="Lambdajection" Version="0.3.0-gc2ca768d3f" />

2. Packages

Lambdajection Nuget Nuget
Lambdajection.Attributes Nuget Nuget
Lambdajection.Core Nuget Nuget
Lambdajection.Generator Nuget Nuget
Lambdajection.Encryption Nuget Nuget
Lambdajection.Templates Nuget Nuget

3. Templates

ℹ Templates will be available starting in v0.5.0-beta1

Lambdajection .NET templates are available for your convenience. Run dotnet new [template-name] --help to see a list of available options for each template.

Lambdajection Project

dotnet new lambdajection

Creates a new C# project with Lambdajection installed, plus boilerplate for a Lambda Handler and Startup class.

Options Class

dotnet new lambdajection-options

Creates a new Options class to be injected into your Lambda as an IOption<>.

4. Usage

4.1. Lambda Handler

Writing the lambda is simple: Just define a public, partial class that contains a Handle method and annotate the class with the Lambda attribute. The Lambda attribute requires that you specify a startup class - more on this in the next step. You are not limited to an request/input parameter of type object - this can be any serializable value or reference type. Same goes for the return value, however the return value must be enclosed in a Task.

using System.Threading.Tasks;

using Amazon.Lambda.Core;

using Lambdajection.Attributes;

namespace Your.Namespace
{
    [Lambda(typeof(Startup))]
    public partial class YourLambda
    {
        private IRegisteredService yourService;

        public YourLambda(IRegisteredService yourService)
        {
            this.yourService = yourService;
        }

        public Task<object> Handle(object request, ILambdaContext context)
        {
            return new {
                foo = request
            };
        }
    }
}

4.2. Serialization

If your Lambda targets the netcoreapp3.1 framework, then by default, the serializer is set to DefaultLambdaJsonSerializer from the Amazon.Lambda.Serialization.SystemTextJson package. With any TFM, you may specify the serializer you want to use by setting the Lambda attribute's Serializer argument:

[Lambda(typeof(Startup), Serializer = typeof(Serializer))]
public partial class Lambda
{
    ...

See a full example of serializer customization.

4.3. Startup Class

The startup class configures services that are injected into the Lambda's IoC container / service collection.

  • Use the ConfigureServices method to add services to your lambda.
    • Use IServiceCollection.UseAwsService<IAmazonService> to inject AWS Clients and Client Factories into the lambda. See the example here.
  • Optionally use the ConfigureLogging method to configure additional log settings.
using System;

using Lambdajection.Core;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Your.Namespace
{
    public class Startup : ILambdaStartup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            this.Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // configure injected services here
            services.AddScoped<IRegisteredService, DefaultRegisteredService>();

            // Add AWS Services by their interface name - the default
            services.UseAwsService<IAmazonService>();
        }

        public void ConfigureLogging(ILoggingBuilder logging)
        {
            // this method is optional
            // logging comes preconfigured to log to the console
        }
    }
}

4.4. Customizing Configuration

By default, configuration is environment variables-based. If you would like to use a file-based or other configuration scheme, you may supply a custom configuration factory to the Lambda attribute:

[Lambda(typeof(Startup), ConfigFactory = typeof(ConfigFactory))]
public partial class Lambda
{
    ...

A custom config factory might look like the following:

using System.IO;

using Lambdajection.Core;

using Microsoft.Extensions.Configuration;

namespace Your.Namespace
{
    public class ConfigFactory : ILambdaConfigFactory
    {
        public IConfigurationRoot Create()
        {
            return new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true)
                .Build();
        }
    }
}

See the full example here.

4.5. Adding Options

You can add an options section by defining a class for that section, and annotating it with the LambdaOptions attribute. If any options are in encrypted form, add the Encrypted attribute to that property. When the options are requested, the IDecryptionService singleton in the container will be used to decrypt those properties. The default decryption service uses KMS to decrypt values.

  • The Encrypted attribute, IDecryptionService and DefaultDecryptionService are all provided by the Lambdajection.Encryption package.
  • Option classes must be in the same assembly as your lambda.
  • You can replace the default decryption service with your own IDecryptionService by injecting it as a singleton in your Startup class' ConfigureServices method.
  • See the example for using Encrypted properties.
using Lambdajection.Encryption;

namespace Your.Namespace
{
    [LambdaOptions(typeof(LambdaHandler), "SectionName")]
    public class ExampleOptions
    {
        public string ExampleValue { get; set; }

        [Encrypted]
        public string ExampleEncryptedValue { get; set; }
    }
}

4.6. Initialization Services

Initialization services can be used to initialize data or perform some task before the lambda is run. Initialization services should implement ILambdaInitializationService and be injected into the container as singletons at startup.

4.7. Disposers

ℹ Disposers will be fully supported starting in v0.5.0-beta1

Disposers can be used to run object finalization and mark for garbage collection. Lambdajection supports Lambdas that implement either IDisposable, IAsyncDisposable or both. If you implement both, DisposeAsync will be preferred.

4.8. Handler Scheme

When configuring your lambda on AWS, the method name you'll want to use will be Run (NOT Handle). For context, Run is a static method generated on your class during compilation. It takes care of setting up the IoC container, if it hasn't been setup already.

So, going off the example above, the handler scheme would look like this:

Your.Assembly.Name::Your.Namespace.YourLambda::Run

5. Examples

6. Acknowledgments

  1. CodeGeneration.Roslyn - Used for compile-time code generation using attributes.
  2. Simple Lambda Dependency Injection in AWS Lambda .NET Core by Gary Woodfine - primary inspiration for this project.

7. Donations

If you use this project and find it useful, please consider donating. Donations are accepted via Github Sponsors and PayPal.

8. Contributing

Issues and feature requests may be reported anonymously on JIRA Cloud. Pull requests are always welcome! See the contributing guide for more information.

9. Security

Security issues can be reported on our JIRA. See our security policy for more information.

10. License

This project is licensed under the MIT License.

Sponsor this project

  •  

Packages

 
 
 

Contributors 3

  •  
  •  
  •