DynamoDb, Reading and Writing Data with .Net Core – Part 1

Full source code available here.

A few weeks ago I started playing with DynamoDb in a .NET application. I read through the AWS documentation but felt it was incomplete and a little out of date. This made it quite hard to figure out the “right” way of using the AWS DynamoDb libraries. For example, there is no good example of using dependency injection to pass a DynamoDb client into a controller, and no guidance on whether that client should be request, transient of singleton scoped.
https://github.com/localstack/awscli-local
In this and following posts I’m going to describe approaches that work, they may not be “right” either.
This post is will show how to read and write from DynamoDb using the document interface and pass in a DynamoDb client to controller

Getting Started

Getting started with AWS can feel like a bit of a hurdle (especially due to credential complications), and a good way to get your feet wet is to use localstack – https://github.com/localstack/localstack, consider installing https://github.com/localstack/awscli-local too.

I’m not going to describe how to get localstack running, I’m going to assume you have done that or you have an AWS account and you know how to set the required credentials.

Once you have localstack installed or you AWS account working, run the following to create the DynamoDB table.

aws --endpoint-url=http://localhost:4569 dynamodb create-table --table-name People  --attribute-definitions AttributeName=PersonId,AttributeType=N --key-schema AttributeName=PersonId,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

You can add data to the table with the following –

aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"1"},"State":{"S":"MA"}, "FirstName": {"S":"Alice"}, "LastName": {"S":"Andrews"}}'
aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"2"},"State":{"S":"MA"}, "FirstName": {"S":"Ben"}, "LastName": {"S":"Bradley"}}'
aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"3"},"State":{"S":"MA"}, "FirstName": {"S":"Colin"}, "LastName": {"S":"Connor"}}'

To see all the items you just stored –

aws --endpoint-url=http://localhost:4569 dynamodb scan --table-name People

The Web API application

I’m going to show you how to retrieve and item by an id and to store a new item using the document interface, in the next post I’ll show how to do the same with the object interface.

But first things first, configuration.

Open the appsettings.json file and replace what’s there with –

{
  "AWS": {
    "Profile": "localstack-test-profile",
    "Region": "us-east-1",
    "ServiceURL": "http://localhost:4566"
  }
}

Add the AWSSDK.DynamoDBv2 nuget package to the project.

In Startup.cs add a using Amazon.DynamoDBv2;

In ConfigureServices() add the following –

public void ConfigureServices(IServiceCollection services)
{
    var awsOptions = Configuration.GetAWSOptions();

    services.AddDefaultAWSOptions(awsOptions);
    services.AddAWSService<IAmazonDynamoDB>();
    services.AddControllers();
}

Inside the controller I’m going to use an AmazonDynmoDbClient to execute calls against DybamoDb, but the SDK doesn’t seem to provide a way to inject one directly, so I came up with a workaround.

//snip...
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
//snip.. 

public class PersonController : ControllerBase
{
    private readonly AmazonDynamoDBClient _amazonDynamoDbClient;
    public PersonController(IAmazonDynamoDB amazonDynamoDB)
    {
        _amazonDynamoDbClient = (AmazonDynamoDBClient) amazonDynamoDB;
    }
    //snip..

Line 8 declares an AmazonDynamoDBClient.
Line 9, the constructor takes an IAmazonDynamoDB as a parameter.
Line 11 casts this IAmazonDynamoDB to an AmazonDynamoDBClient.
Again, I don’t know if this is the best approach, but it works fine.

Here is how to get an item from the people table.

[HttpGet("{personId}")]
public async Task<IActionResult> Get(int personId)
{
    Table people = Table.LoadTable(_amazonDynamoDbClient, "People");
    var person =  JsonSerializer.Deserialize<Person> ((await people.GetItemAsync(personId)).ToJson());
    return Ok(person);
}

This is how you add an item to the People table.

[HttpPost]
public async Task<IActionResult> Post(Person person)
{
    Table people = Table.LoadTable(_amazonDynamoDbClient, "People");
    
    var document = new Document();
    document["PersonId"] = person.PersonId;
    document["State"] = person.State;
    document["FirstName"] = person.FirstName;
    document["LastName"] = person.LastName;
    await people.PutItemAsync(document);
    
    return Created("", document.ToJson());
}

I’m not a fan of all the hardcoded attributes for properties of the person, but this is a way of doing it.
Another approach is to use the object model, and I’ll show that in a later blog post.

To exercise the GET method open your browser and go to http://localhost:5000/people/1.

To exercise the POST you can use Fiddler, Curl, Postman or the very nice extension for REST Client extension for Visual Studio Code.

If you are using that extension, here is the request –

POST http://localhost:5000/person HTTP/1.1
content-type: application/json

{
    "personId": 6,
    "state": "MA",
    "firstName": "Frank",
    "lastName" : "Freeley"
}

Full source code available here.

Dependency Inject a Service from Startup back to Program in .Net Core 3.1

Full source code available here.

Over the past couple of years I wrote a few posts about Dependency Injection in .Net Core 2.1, and this week I received comments from a reader telling me that some of the changes in .Net Core 3.1 mean that some of the approaches no longer work. There have been breaking changes https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1#hosting-generic-host-restricts-startup-constructor-injection

I wanted to see what would still work so I tried a few things.

You can no longer DI from Program in Startup.

But you can add a transient service and/or a singleton service to the ServiceCollection and use it within Program, and the rest of the application. You can also add a scoped service to the ServiceCollection and use it within the Program, it’s a little different from using transient and singleton so I’ll cover it in the next post.
Here’s how to use transient and singletons inside Program.
Create two services, creatively named ServiceOne and ServiceTwo and have them implement interfaces.

public class ServiceOne : IServiceOne
{
	public static int staticCounter;

	public ServiceOne()
	{
		staticCounter++;
	}
   //snip…

public class ServiceTwo : IServiceTwo
{
	public static int staticCounter;

	public ServiceTwo()
	{
		staticCounter++;
	}
	//snip…

I added a static counter to make it easy to see how many times the constructor is called.
For the transient one I expect it to increment every time the service is injected, for the singleton I expect it to remain at 1 for the lifetime of the application.

In Program.cs I split up CreateHostBuilder(args).Build call from the subsequent .Run().

public static void Main(string[] args)
{
	IHost host = CreateHostBuilder(args).Build();
	DoSomethingWithTheTransientService(host.Services);
	DoSomethingWithTheSingletonService(host.Services);
	host.Run();
}

The CreateHostBuilder() method is not changed –

public static IHostBuilder CreateHostBuilder(string[] args) =>
	Host.CreateDefaultBuilder(args)
	.ConfigureWebHostDefaults(webBuilder =>
	{
		webBuilder.UseStartup<Startup>();
	});

And I have two methods that use the ServiceCollection to access the registered services –

private static void DoSomethingWithTheTransientService(IServiceProvider serviceProvider)
{
	Console.WriteLine("Calling the transient service");

	var serviceOne = serviceProvider.GetService<IServiceOne>();
	Console.WriteLine(serviceOne.StaticCounter());
	Console.WriteLine(serviceOne.GetHashCode());
}

private static void DoSomethingWithTheSingletonService(IServiceProvider serviceProvider)
{
	Console.WriteLine("Calling the singleton service");

	var serviceTwo = serviceProvider.GetService<IServiceTwo>();
	Console.WriteLine(serviceTwo.StaticCounter());
	Console.WriteLine(serviceTwo.GetHashCode());
}

To make what’s happening more obvious I added the services to the constructor call of the WeatherForecastController() and added the counter and hash codes to the data returned by the action method.

public class WeatherForecastController : ControllerBase
{
	private IServiceOne _serviceOne;
	private IServiceTwo _serviceTwo;
	public WeatherForecastController(IServiceOne serviceOne, IServiceTwo serviceTwo)
	{
		_serviceOne = serviceOne;
		_serviceTwo = serviceTwo;
	}

For completeness, here is the ConfigureServices method in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IServiceOne, ServiceOne>();
    services.AddSingleton<IServiceTwo, ServiceTwo>();
    services.AddControllers();
}

Put some breakpoints in Program Main(), DoSomethingWithTheTransientService(), DoSomethingWithTheSingletonService() and in the WeatherForecastController.Get().

Start the application and browse to http://localhost:5000/weatherforecast to see what happens.

I’m going to follow up on this post with a version that shows how to use scoped dependencies in Startup .NET 5.

Full source code available here.

Fluent Validation in ASP.NET Core 3.1

Full source code available here.

This is an update to a post I wrote in 2017 talking about Fluent Validation in ASP.NET Core.

The example is the same but there has been few updates. The first is how you setup the FluentValidation in Startup.cs, and the second is that you don’t need a ActionFilterAttribute anymore. I have included an example of how to call the action method using Fiddler.

Step 1

Firstly add the package to your Web Api project.
Install-Package FluentValidation.AspNetCore.
If you are going to keep your validators in the Web Api project that is all you need to add.

But if you put the validators in a different project you need to add the FluentValidation package to that project
Install-Package FluentValidation.

Step 2

In startup.cs add –

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers()
        .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonModelValidator>());}
Step 3

Add a model and a validator.

public class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class PersonModelValidator : AbstractValidator<PersonModel>
{
    public PersonModelValidator()
    {
        RuleFor(p => p.FirstName).NotEmpty();
        RuleFor(p => p.LastName).Length(5);
    }
}

Example Usage

In Fiddler, Postman or any other tool you POST to localhost:5000/person with the following header –

Content-Type: application/json

And body –

{
    "firstName": "",
    "lastName": "This is too long"
}

Here is how the request looks in Fiddler –

The response will look like this –

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|84df05e2-41e0d4841bb61293.",
    "errors": {
        "LastName": [
            "'Last Name' must be 5 characters in length. You entered 16 characters."
        ],
        "FirstName": [
            "'First Name' must not be empty."
        ]
    }
}

Easy to read that there are two errors.

Full source code available here.

The terminal shell path “dotnet” is a directory – Visual Studio Code

I have been using Visual Studio Code in Ubuntu Linux for a while, but it was not an easy process to get it working the first time.

I just got around to installing .NET Core 3.1 and hit a familiar problem –

The terminal shell path "dotnet" is a directory

This happens when I try to build inside VS Code using a build task, I point this out because building from a terminal inside VS Code works fine.

Here is the output –

> Executing task: dotnet build /home/bryan/dev/blog/SomeProject/SomeProject.csproj /property:GenerateFullPaths=true /consoleloggerparameters:NoSummary <

The terminal shell path "dotnet" is a directory

Terminal will be reused by tasks, press any key to close it.

If you google this you will find some suggested fixes but they didn’t work for me.

Here is what does work for me –

1. Install the SDKs to a directory NOT named dotnet, my SDK’s are in $HOME/msdotnet
2. Update your PATH to include this directory – PATH=$PATH:$HOME/msdotnet
3. Update your DOTNET_ROOT to this – DOTNET_ROOT=$HOME/msdotnet

That’s what works for me.

Whenever I need to install a new SDK I have do follow the manual steps outlined
here, making sure to unpack the SDK to $HOME/msdotnet.

Hope this works for you, please leave a comment if it does or even if it doesn’t.

Passing Configuration Options Into Middleware, Services and Controllers in ASP.NET Core 3.1

Full source code here.

I recently hit a problem where I needed to reload configuration settings as they changed, fortunately this is relatively straightforward when using the IOptionsMonitor, in .NET Core.

Add a few lines to ConfigureServices, pass the configuration options as a scoped service to the controller or into another service and everything works.

Here is all you need in the ConfigureServices method –

public void ConfigureServices(IServiceCollection services)
{
	services.AddOptions();
	services.Configure<WeatherOptions>(Configuration.GetSection("WeatherOptions"));
	services.AddScoped<ISomeService, SomeService>();

Here’s the service that takes the IOptionsMonitor

public class SomeService : ISomeService
{
	private readonly WeatherOptions _weatherOptions;

	public SomeService(IOptionsMonitor<WeatherOptions> weatherOptions)
	{
		_weatherOptions = weatherOptions.CurrentValue;
	}
// snip..

And here is the controller taking the IOptionsMonitor and the service that also uses the options, this is of course contrived and you almost certainly would not want to take both constructor parameters –

public class WeatherForecastController : ControllerBase
{
	private readonly WeatherOptions _weatherOptions;
	private readonly ISomeService _someService;
	public WeatherForecastController(IOptionsMonitor<WeatherOptions> weatherOptions, ISomeService someService)
	{
		_someService = someService;
		_weatherOptions = weatherOptions.CurrentValue;
	}

But what if you also had to pass those configuration settings into a piece of middleware that executes on each request and if they configuration values change you want to be reflected in the execution of the middleware. This is not so easy because the constructor of a middleware class can only have singletons injected into it, same applies to the Configure method Startup.

Fortunately, the Invoke method can have a scoped service injected into it, see here for more.

public class SomeMiddleware
{
	private readonly RequestDelegate _next;

	public SomeMiddleware(RequestDelegate next)
	{
		_next = next;
	}

	public async Task InvokeAsync(HttpContext context, ISomeService someService)
	{
		someService.DoSomething("Middleware - ");
		await _next(context);
	}
}

To use the middleware, add a single line to the Configure method –

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	app.UseMiddleware<SomeMiddleware>();
	//snip..

That’s it.

Full source code here.