'Best way to inject instance of DynamoDBContext in .NET Core

Currently working on a web services project for my class and have decided to make a web API using .NET Core and DynamodDB.

I was just curious what the best way to inject the DynamoDBContext is?

I currently am doing it like this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
    services.AddAWSService<IAmazonDynamoDB>();
}

I got this piece of code above from the DynamoDB documentation. I add an instance of IAmazonDynamoDB to the project.

DynamoDBContext context;
public ValuesController(IAmazonDynamoDB context)
{
    this.context = new DynamoDBContext(context);
}

In the controller, I then inject the IAmazonDynamoDB instance, and use that to create an instance of DynamoDBContext.

Is there a way to create an instance of the context in the ConfigureServices method and add it to the project there, or is the way I am doing it currently fine?



Solution 1:[1]

Is there a way to create an instance of the context in the ConfigureServices method and add it to the project there, or is the way I am doing it currently fine?

Although your solution will work, it has a drawback. You're not using Dependency Injection for DynamoDBContext and create its instance in controller constructor through new operator. You'll face a problems when it comes to unit testing your code, because you have no way to substitute implementation of DynamoDBContext.

The proper way is to register DynamoDBContext in DI container and let the container itself create an instance when it's required. With such approach IDynamoDBContext gets injected into ValuesController:

public class ValuesController
{
    private readonly IDynamoDBContext context;

    public ValuesController(IDynamoDBContext context)
    {
        this.context = context;
    }

    // ...
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDefaultAWSOptions(Configuration.GetAWSOptions());
    services.AddAWSService<IAmazonDynamoDB>();
    services.AddTransient<IDynamoDBContext, DynamoDBContext>();
}

Solution 2:[2]

Basically you'd have to create an interface for your DynamoDB context

public interface IDynamoDbContext<T> : IDisposable where T : class
{
    Task<T> GetByIdAsync(string id);
    Task SaveAsync(T item);
    Task DeleteByIdAsync(T item);
}

Create a class implementing the interface

public class DynamoDbContext<T> : DynamoDBContext, IDynamoDbContext<T>
    where T : class
{
    public DynamoDbContext(IAmazonDynamoDB client)
        : base(client)
    {

    }

    public async Task<T> GetByIdAsync(string id)
    {
        return await base.LoadAsync<T>(id);
    }

    public async Task SaveAsync(T item)
    {
        await base.SaveAsync(item);
    }

    public async Task DeleteByIdAsync(T item)
    {
        await base.DeleteAsync(item);
    }
}

Inject it in your Startup like this

var client = Configuration.GetAWSOptions().CreateServiceClient<IAmazonDynamoDB>();
services.AddScoped<IDynamoDbContext<AwesomeClass>>(provider => new DynamoDbContext<AwesomeClass>(client));

The context will be passed in the DI system and you can use it where you like

private IDynamoDbContext<AwesomeClass> _awesomeContext;

public AwesomeDynamoDbService(IDynamoDbContext<AwesomeClass> awesomeContext)
{
    _awesomeContext= awesomeContext;
}

I faced a similar issue and wrote a blog post describing a good way to fix it! Hope it shares some light!

Solution 3:[3]

If you are using Lambda then you can try ti use below code

In your DynamoDB database class add constructor with dependency on IDynamoDBContext

public DynamoDbDatabase(IDynamoDBContext dynamoDbContext)
{            
    _dynamoDbContext = dynamoDbContext;                           
}

In Function.cs of your Lambda define mapping for dependency injection

private static IServiceProvider ConfigureServices()
{
    var serviceCollection = new ServiceCollection();            
    serviceCollection.AddSingleton<IDynamoDBContext,DynamoDBContext>(p => new DynamoDBContext(new AmazonDynamoDBClient()));  
    return serviceCollection.BuildServiceProvider(); 
}

Call above function on top of your lambda function handler

when your code runs it will automatically detect dependency and pass proper DynamoDBContext object when asked for IDynamoDBContext

Solution 4:[4]

This is how I am using it to make it work with both local & public (AWS hosted) DynamoDB.

appsettings.Development.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AWS": {
    "Profile": "default",
    "Region": "ap-south-1"
  },
  "DynamoDb": {
    "LocalMode": false,
    "LocalServiceUrl": "http://localhost:8001",
    "TableNamePrefix": ""
  }
}

Program.cs in .NET 6

// Get the AWS profile information from configuration providers
AWSOptions awsOptions = builder.Configuration.GetAWSOptions();

// Configure AWS service clients to use these credentials
builder.Services.AddDefaultAWSOptions(awsOptions);

var dynamoDbConfig = builder.Configuration.GetSection("DynamoDb");
var runLocalDynamoDb = dynamoDbConfig.GetValue<bool>("LocalMode");

#region DynamoDB setup
if (runLocalDynamoDb)
{
    builder.Services.AddSingleton<IAmazonDynamoDB>(sp =>
    {
        var clientConfig = new AmazonDynamoDBConfig { ServiceURL = dynamoDbConfig.GetValue<string>("LocalServiceUrl") };
        return new AmazonDynamoDBClient(clientConfig);
    });
}
else
{
    builder.Services.AddAWSService<IAmazonDynamoDB>();
}

builder.Services.AddSingleton<IDynamoDBContext, DynamoDBContext>((serviceProvider) =>
{
    IAmazonDynamoDB amazonDynamoDBClient = serviceProvider.GetRequiredService<IAmazonDynamoDB>();
    DynamoDBContextConfig dynamoDBContextConfig = new DynamoDBContextConfig
    {
        TableNamePrefix = dynamoDbConfig.GetValue<string>("TableNamePrefix")
    };
    return new DynamoDBContext(amazonDynamoDBClient, dynamoDBContextConfig);
});
#endregion

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 CodeFuller
Solution 2
Solution 3 awh112
Solution 4 Ankush Jain