Design Patterns: Asp.Net Core Web API, services, and repositories
Part 11: Integration testing

In the previous article, we completed the last piece of the Ninja API. In this article, we will glue all of these pieces together by:
- Creating integration tests to integrate the Ninja subsystem confidently
- Connecting the Ninja API to Azure Table Storage
- Leveraging the new Asp.Net Core 2.0 default configuration
The series (shared section)
In the series, we will create an Asp.Net Core 2.0 Web API, and we will focus on the following major concerns:
- The web part; the HTTP request and response handling.
- The business logic; the domain.
- The data access logic; reading and writing data.
During the article, I will try to include the thinking process behind the code.
Technology-wise, we will use Asp.Net Core, Azure Table Storage and ForEvolve Framework to build the Web API.
To use the ForEvolve Framework (or let’s say toolbox), you will need to install packages from a custom NuGet feed.
If you dont know How to use a custom NuGet feed in Visual Studio 2017, feel free to take a look at this article.
If you do, the ForEvolve NuGet feed URI is https://www.myget.org/F/forevolve/api/v3/index.json
.
We will also use XUnit and Moq for both unit and integration testing.
Table of content
I will update the table of content as the series progress.
“Prerequisites”
In the series, I will cover multiple subjects, more or less in details, and I will assume that you have a little idea about what a Web API is, that you know C# and that you already have a development environment setup (i.e.: Visual Studio, Asp.Net Core, etc.).
The goal
At the end of this article series, you should be able to program an Asp.Net Core Web API in a structured and testable way using the explained techniques (design patterns). These design patterns offer a clean way to follow the Single Responsibility Principle.
Since design patterns are language-agnostic, you can use them in different applications and languages. In an Angular application, you will most likely use Dependency Injection for example.
This is one of the beauties of design patterns; they are tools to be used, not feared!
Asp.Net Core 2.0
At the time of the writing, Asp.Net Core 2.0 was still in prerelease, and I updated the code samples to use the release version.
You will need the .NET Core 2.0.0 SDK and Visual Studio 2017 update 3 or the IDE/code editor of your choosing.
Integration of the Ninja
We now have a fully unit tested Ninja sub-system but, we still need to integrate it together if we want it to work.
We could compare this to LEGO® blocks. We first created the blocks, but we still have no castle built.
Let’s first take a look at our original diagram:

Controller
to the data source, fully decoupled.We modified the NinjaRepository
a little, adding two new dependencies:
-
INinjaMappingService
that help us map our ninja to data-entities. -
ITableStorageRepository<NinjaEntity>
that handles the Azure Table data access.
The new diagram looks like this:

Controller
to the data source, fully decoupled, including NinjaRepository
's dependencies.If we add the indirect TableStorageRepository<NinjaEntity>
dependency, we end up with:

Controller
to the data source, fully decoupled, including NinjaRepository
's dependencies and the ITableStorageRepository<NinjaEntity>
implementation.Integration tests
The first thing we will do is plan our integration by creating some integration tests. The goal behind integration testing is to make sure that all of our units work well together: that our software behaves as expected.
In this article, we will test the Ninja subsystem as a whole, but the data source.
Instead of using a real Azure Storage Table, we will Mock
the ForEvolve.Azure.Storage.Table.ITableStorageRepository<NinjaEntity>
interface, so the tests run faster and require less setup.
By doing this, it will also be easier to assess success and failure.
The
ITableStorageRepository<NinjaEntity>
interface is not part of our system so we can assume that it is working as expected. More on that, we do not need to test it out, it is not part of the NinjaApi code.We could test against a real Azure Storage table or even against the local emulator, but it would have required more setup, which is the principal point I wanted to avoid.
If I find the time, I would like to do that kind of end to end testing (and write about it) in a CI/CD pipeline using Postman/Newman against a real staging Azure Web App. But unfortunately not today.
That said, integration testing does not imply testing the whole system; for example, you could verify the integration of only two components together if you’d feel the need to. It is all about combining the units and testing their interaction; making sure the system is working as intended.
If you remember my LEGO® block example, it is now time to take those blocks and build a castle with them.
Integration testing is an essential part of testing your software and should not be overlooked. I find integration testing (especially end-to-end testing) to be more concrete than unit testing since it allows you to make sure the system behaves as expected, from a user point of view (ex.: make sure that a call to the API does what it should). It is easier to see the benefit of it than unit tests.
While unit testing is necessary, especially for testing complex features, like algorithms implementation, domain logic, etc. I believe that integration testing is even more important since it focuses on testing the interaction of those units together. Of course combining both make sure that units, subsystems and the whole application works as expected, which is even better.
Another point before jumping into the code: while using dependency injection help us decouple our units, by moving the system composition responsibility outside of individual components, it also creates that centralized “sewing machine” that combines the units (at the composition root).
To increase the quality of our software, that composition must also be tested. We can see this as another significant role of integration testing.
I don't want to abuse on linking books, but talking about DI reminded me of this book that I read many years ago.
If you are looking for a book, this is a great read on dependency injection. It explains the concepts behind DI very well. The book does not focus on .Net Core, but understanding the concepts is still pretty important and remains the same no matter what technology you are using.
I understand that there is a lot of information on the internet nowadays, but sometimes, books and papers still feel great.
For those allergic to paper, there is still the electronic version .
I highly recommend this one.
ITableStorageRepository
Even if we (will) use a Mock, we still have to make sure that an implementation of ITableStorageRepository<NinjaEntity>
is returned for our real system.
I could also have phrased this: “especially since we used a Mock”.
Basically, we want to make sure that the units we are not directly testing (the mocked part) are correctly configured with our DI container.
Here is what we will not use in our integration tests (these comes from the ForEvolve.Azure
package):
- When asking for the
ITableStorageRepository<NinjaEntity>
service, the system should return aTableStorageRepository<NinjaEntity>
instance. - To build that instance, we will also need an
ITableStorageSettings
implementation, which will be an instance of theTableStorageSettings
class.
The ITableStorageSettings
is a simple interface:
public interface ITableStorageSettings : IStorageSettings
{
string TableName { get; set; }
}
public interface IStorageSettings
{
CloudStorageAccount CreateCloudStorageAccount();
}
The implementations looks like this:
public class TableStorageSettings : StorageSettings, ITableStorageSettings
{
public string TableName { get; set; }
}
public abstract class StorageSettings : IStorageSettings
{
public string AccountName { get; set; }
public string AccountKey { get; set; }
public bool UseHttps { get; set; } = true;
public CloudStorageAccount CreateCloudStorageAccount()
{
return new CloudStorageAccount(new StorageCredentials(
AccountName,
AccountKey
), UseHttps);
}
}
See ForEvolve.Azure.Storage source code for more info.
The TableStorageSettings
class add one property to the three inherited from StorageSettings
.
For a connection to Azure Table to work, we will (only) need those three properties:
AccountName
AccountKey
TableName
You could also use the
DevelopmentTableStorageSettings
class to test against the emulator during development instead of theTableStorageSettings
class.
Now that we took a little look at the ForEvolve.Azure.Storage
namespace, let’s configure our integration tests.
At the root of ForEvolve.Blog.Samples.NinjaApi.IntegrationTests
, let’s create an appsettings.json
file with the following content:
{
"AzureTable": {
"TableName": "MyTableName"
}
}
Once this is done, make sure the file is copied to the build directory by using the property tab of Visual Studio (or Right Click > Properties
) or by editing the ForEvolve.Blog.Samples.NinjaApi.IntegrationTests.csproj
file.
Why?
The reason is simple (but maybe not that obvious): we are hosting
ForEvolve.Blog.Samples.NinjaApi.dll
in-memory, but we are not copying its settings file. As we will explore later, we are loading settings from multiple sources, and theTableName
come from theappsettings.json
file.
Ok, now we have some specs and some required configurations, it’s time write those tests.
In the StartupTest+ServiceProvider
class of the ForEvolve.Blog.Samples.NinjaApi.IntegrationTests
project, we will add those two tests, enforcing the rules that have previously been stated:
[Fact]
public void Should_return_TableStorageSettings()
{
// Arrange
var serviceProvider = Server.Host.Services;
// Act
var result = serviceProvider.GetService<ITableStorageSettings>();
// Assert
var settings = Assert.IsType<TableStorageSettings>(result);
Assert.NotNull(settings.AccountKey);
Assert.NotNull(settings.AccountName);
Assert.Equal("MyTableName", settings.TableName);
}
As you can see, we want to make sure that AccountKey
, AccountName
, and TableName
are set.
I am testing
AccountKey
andAccountName
only againstNotNull
because I do not want to hard code any credentials in the project. We will set that up later using secrets.
[Fact]
public void Should_return_TableStorageRepository_of_NinjaEntity()
{
// Arrange
var serviceProvider = Server.Host.Services;
// Act
var result = serviceProvider.GetService<ITableStorageRepository<NinjaEntity>>();
// Assert
Assert.IsType<TableStorageRepository<NinjaEntity>>(result);
}
Once again, this is a pretty simple test enforcing the expected type of the ITableStorageRepository<NinjaEntity>
service.
If we run those tests, they will fail. We will make them pass later.
NinjaControllerTest
Now that we made sure the ITableStorageRepository<NinjaEntity>
implementation is tested, we can jump right to the NinjaControllerTest
class.
The test initialization, shared by all tests, goes as follow:
public class NinjaControllerTest : BaseHttpTest
{
protected Mock<ITableStorageRepository<NinjaEntity>> TableStorageMock { get; }
protected string ClanName1 => "Iga";
protected string ClanName2 => "Kōga";
public NinjaControllerTest()
{
TableStorageMock = new Mock<ITableStorageRepository<NinjaEntity>>();
}
protected override void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton(x => TableStorageMock.Object)
.AddSingleton<IEnumerable<Clan>>(x => new List<Clan> {
new Clan{ Name = ClanName1 },
new Clan{ Name = ClanName2 }
});
}
// ...
}
In the NinjaControllerTest
class, the Mock<ITableStorageRepository<NinjaEntity>>
will play the role of the Azure Storage.
To do so, it needs to be registered in the IServiceCollection
, overriding the default.
If we want to control the test clans (IEnumerable<Clan>
), we also need to override that default as well.
The test clans
I concur that the
IEnumerable<Clan>
values are the same as the live data. That said, live data could change in the future voiding our tests. Having predictable input and output in automatic tests is important. To conclude, I could have usedClan Foo
andClan Bar
instead ofIga
andKōga
(but hey! I did thorough research to come up with those names).
ConfigureServices
The code of
NinjaControllerTest.ConfigureServices(...)
run before the execution ofStartup.ConfigureServices(...)
which allows us to modify the application’s composition beforehand. More on that, this is possible because we used theTryAddSingleton<...>(...)
extension method (inStartup.cs
). Without the use of that extension, an exception would be thrown at runtime.
That said, do you remember our little refactoring about the extraction of the EnforceNinjaExistenceAsync
method?
If you do, here is an extension method to help set it up in both UpdateAsync
and DeleteAsync
(making sure the validation pass):
public static class TableStorageMockExtensions
{
public static NinjaEntity SetupEnforceNinjaExistenceAsync(this Mock<ITableStorageRepository<NinjaEntity>> tableStorageMock, string clanName, string ninjaKey)
{
var entity = new NinjaEntity(); // Only need to not be null
tableStorageMock
.Setup(x => x.ReadOneAsync(clanName, ninjaKey))
.ReturnsAsync(entity);
return entity;
}
}
Then these few helpers have been created along the way:
public class NinjaControllerTest : BaseHttpTest
{
// ...
protected NinjaEntity CreateEntity(string clanName)
{
return CreateEntities(1, clanName).First();
}
protected IEnumerable<NinjaEntity> CreateEntities(int amountOfNinjaToCreate, string clanName)
{
for (int i = 0; i < amountOfNinjaToCreate; i++)
{
var ninja = new NinjaEntity
{
Level = i,
Name = $"Ninja {i}",
PartitionKey = clanName,
RowKey = $"NinjaKey {i}"
};
yield return ninja;
}
}
protected void AssertNinjaEntityEqualNinja(NinjaEntity entity, Ninja ninja)
{
Assert.Equal(entity.PartitionKey, ninja.Clan.Name);
Assert.Equal(entity.RowKey, ninja.Key);
Assert.Equal(entity.Name, ninja.Name);
Assert.Equal(entity.Level, ninja.Level);
}
}
These will help us compare NinjaEntity
to Ninja
as well as to create NinjaEntity
classes.
All of our tests will be executed over HTTP against a real running instance of our Web API, so we can’t just compare mocked references anymore.
Now that we covered all the shared code, it is time to jump to the tests themselves. We will proceed test method by test method.
ReadAllAsync
We want to make sure that the ninja returned by the Web API are the one returned by our mocked database, the TableStorageMock
’s ReadAllAsync()
method.
public class ReadAllAsync : NinjaControllerTest
{
[Fact]
public async Task Should_return_all_ninja_in_azure_table()
{
// Arrange
var superClanNinja = CreateEntities(amountOfNinjaToCreate: 2, clanName: ClanName1);
var otherClanNinja = CreateEntities(amountOfNinjaToCreate: 2, clanName: ClanName2);
var all = superClanNinja.Union(otherClanNinja).ToArray();
var expectedNinjaLength = 4;
TableStorageMock
.Setup(x => x.ReadAllAsync())
.ReturnsAsync(all);
// Act
var result = await Client.GetAsync("v1/ninja");
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja[]>();
Assert.NotNull(ninja);
Assert.Equal(expectedNinjaLength, ninja.Length);
Assert.Collection(ninja,
n => AssertNinjaEntityEqualNinja(all[0], n),
n => AssertNinjaEntityEqualNinja(all[1], n),
n => AssertNinjaEntityEqualNinja(all[2], n),
n => AssertNinjaEntityEqualNinja(all[3], n)
);
}
}
ReadAllInClanAsync
We want to make sure that the ninja returned by the Web API are the one returned by our mocked database, the TableStorageMock
’s ReadPartitionAsync()
method.
As a reminder, our ninja’s partition key is the clan’s name, which leads us to request the whole partition to query all ninja of a single clan. This kind of design can also come in handy if we want to delete a whole clan (by simply deleting the partition).
public class ReadAllInClanAsync : NinjaControllerTest
{
[Fact]
public async Task Should_return_all_ninja_in_azure_table_partition()
{
// Arrange
var expectedClanName = ClanName2;
var expectedNinja = CreateEntities(amountOfNinjaToCreate: 2, clanName: expectedClanName).ToArray();
var expectedNinjaLength = 2;
TableStorageMock
.Setup(x => x.ReadPartitionAsync(expectedClanName))
.ReturnsAsync(expectedNinja);
// Act
var result = await Client.GetAsync($"v1/ninja/{expectedClanName}");
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja[]>();
Assert.NotNull(ninja);
Assert.Equal(expectedNinjaLength, ninja.Length);
Assert.Collection(ninja,
n => AssertNinjaEntityEqualNinja(expectedNinja[0], n),
n => AssertNinjaEntityEqualNinja(expectedNinja[1], n)
);
}
}
ReadOneAsync
We want to make sure that the ninja returned by the Web API is the one returned by our mocked database, the TableStorageMock
’s ReadOneAsync()
method.
public class ReadOneAsync : NinjaControllerTest
{
[Fact]
public async Task Should_return_one_ninja_from_azure_table()
{
// Arrange
var expectedNinja = CreateEntity(ClanName1);
var clanName = expectedNinja.PartitionKey;
var ninjaKey = expectedNinja.RowKey;
TableStorageMock
.Setup(x => x.ReadOneAsync(clanName, ninjaKey))
.ReturnsAsync(expectedNinja);
// Act
var result = await Client.GetAsync($"v1/ninja/{clanName}/{ninjaKey}");
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja>();
Assert.NotNull(ninja);
AssertNinjaEntityEqualNinja(expectedNinja, ninja);
}
}
CreateAsync
We want to make sure that the ninja sent to our Web API is the one received by our mocked database, the TableStorageMock
’s InsertOrReplaceAsync()
method.
public class CreateAsync : NinjaControllerTest
{
[Fact]
public async Task Should_create_the_ninja_in_azure_table()
{
// Arrange
var ninjaToCreate = new Ninja
{
Name = "Bob",
Level = 6,
Key = "12345",
Clan = new Clan { Name = ClanName1 }
};
var ninjaBody = ninjaToCreate.ToJsonHttpContent();
var mapper = new Mappers.NinjaEntityToNinjaMapper();
NinjaEntity createdEntity = null;
TableStorageMock
.Setup(x => x.InsertOrReplaceAsync(It.IsAny<NinjaEntity>()))
.ReturnsAsync((NinjaEntity x) => {
createdEntity = x;
return x;
});
// Act
var result = await Client.PostAsync("v1/ninja", ninjaBody);
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja>();
Assert.NotNull(ninja);
Assert.NotNull(createdEntity);
AssertNinjaEntityEqualNinja(createdEntity, ninja);
}
}
UpdateAsync
We want to make sure that the ninja sent to our Web API is the one received by our mocked database, the TableStorageMock
’s InsertOrMergeAsync()
method.
public class UpdateAsync : NinjaControllerTest
{
[Fact]
public async Task Should_update_the_ninja_in_azure_table()
{
// Arrange
var ninjaToUpdate = new Ninja
{
Clan = new Clan { Name = ClanName1 },
Key = "Some UpdateAsync Ninja Key",
Name = "My new name",
Level = 1234
};
var ninjaBody = ninjaToUpdate.ToJsonHttpContent();
NinjaEntity updatedEntity = null;
TableStorageMock
.Setup(x => x.InsertOrMergeAsync(It.IsAny<NinjaEntity>()))
.ReturnsAsync((NinjaEntity n) =>
{
updatedEntity = n;
return n;
});
TableStorageMock
.SetupEnforceNinjaExistenceAsync(ClanName1, ninjaToUpdate.Key);
// Act
var result = await Client.PutAsync("v1/ninja", ninjaBody);
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja>();
Assert.NotNull(ninja);
Assert.NotNull(updatedEntity);
AssertNinjaEntityEqualNinja(updatedEntity, ninja);
}
}
DeleteAsync
We want to make sure that the ninja sent to our Web API is the one received by our mocked database, the TableStorageMock
’s DeleteAsync()
method.
public class DeleteAsync : NinjaControllerTest
{
[Fact]
public async Task Should_delete_the_ninja_from_azure_table()
{
// Arrange
var ninjaToDelete = CreateEntity(ClanName1);
var clanName = ninjaToDelete.PartitionKey;
var ninjaKey = ninjaToDelete.RowKey;
TableStorageMock
.SetupEnforceNinjaExistenceAsync(clanName, ninjaKey);
TableStorageMock
.Setup(x => x.DeleteOneAsync(clanName, ninjaKey))
.ReturnsAsync(ninjaToDelete);
// Act
var result = await Client.DeleteAsync($"v1/ninja/{clanName}/{ninjaKey}");
// Assert
result.EnsureSuccessStatusCode();
var ninja = await result.Content.ReadAsJsonObjectAsync<Ninja>();
Assert.NotNull(ninja);
AssertNinjaEntityEqualNinja(ninjaToDelete, ninja);
}
}
As expected, since we only added tests with no code in the API, when we run all tests, our new integration tests fail. However, we now know what we need to do: make them pass!
We could create more integration tests, but the level of test coverage is pretty high as it is (around 90% based on VS Test Coverage results).
Moreover, most code blocks that are not covered are the guard clauses, exceptions constructors, and the Startup
class.
I am confident that these will not break anything at runtime (if you want 100% code coverage, I leave you to it).
Startup dependencies
Now that our integration tests are ready, it is time to register our dependencies with the DI Container.
I divided the dependencies into three groups:
- Mappers
- Ninja
- ForEvolve.Azure
Mappers
In a previous article, we created three mappers to help us copy Ninja
to NinjaEntity
and vice versa.
To make that subsystem work, in Startup.ConfigureServices
, we will add the following:
services.TryAddSingleton<IMapper<Ninja, NinjaEntity>, NinjaToNinjaEntityMapper>();
services.TryAddSingleton<IMapper<NinjaEntity, Ninja>, NinjaEntityToNinjaMapper>();
services.TryAddSingleton<IMapper<IEnumerable<NinjaEntity>, IEnumerable<Ninja>>, EnumerableMapper<NinjaEntity, Ninja>>();
These define our three mappers:
-
Ninja
toNinjaEntity
-
NinjaEntity
toNinja
-
IEnumerable<NinjaEntity>
toIEnumerable<Ninja>
Ninja
Here, to attack the core Ninja’s services, in Startup.ConfigureServices
, we will add the following:
services.TryAddSingleton<INinjaService, NinjaService>();
services.TryAddSingleton<INinjaRepository, NinjaRepository>();
services.TryAddSingleton<INinjaMappingService, NinjaMappingService>();
I grouped the
INinjaMappingService
in the ninja’s section since it is more of a core service than a mapper (it is a Façade to our mapping subsystem remember?). That also means that it is the access point to the mapping subsystem. Which leads me to say that putting theINinjaMappingService
in either group would be fine.
ForEvolve.Azure
This section is a little less strait-forward since we need to access configurations and use secrets. Once you know all of this, it is as easy as the previous steps; let’s cover all that up!
Configurations
By default, Asp.Net Core 2.0 do most of the configuration plumbing for us.
The only thing we will need to do is get the configurations injected in the Startup
class by adding a constructor and a property (for future references).
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
Once this is done, we can access our configurations, as easily as by using the Configuration
property.
Great job on this one and cheers to the Asp.Net Core team! Asp.Net was never as clean as Asp.Net Core, and Asp.Net Core 1.0 was not close to what Asp.Net Core 2.0 is now.
Keep up the good work!
ConfigureServices
Now that we have access to Configuration
, in Startup.ConfigureServices
, we will add the following:
services.TryAddSingleton<ITableStorageRepository<NinjaEntity>, TableStorageRepository<NinjaEntity>>();
services.TryAddSingleton<ITableStorageSettings>(x => new TableStorageSettings
{
AccountKey = Configuration.GetValue<string>("AzureTable:AccountKey"),
AccountName = Configuration.GetValue<string>("AzureTable:AccountName"),
TableName = Configuration.GetValue<string>("AzureTable:TableName")
});
We could replace the binding of ITableStorageSettings
with the following line of code and Asp.Net Core will parse all settings automatically:
services.TryAddSingleton<ITableStorageSettings>(x =>
Configuration.GetSection("AzureTable").Get<TableStorageSettings>());
However, in the case of this article, I find it more explicit to bind each property individually, clearly illustrating the required data. Also, there were only three properties to bind which was trivial.
That said, I pointed this out so you can use this alternate method in your own applications (if you didn't know).
Most of the classes and interfaces are taken from the ForEvolve.Azure
assembly and will help us access Azure Table Storage.
The main class is TableStorageRepository<NinjaEntity>
, bound to ITableStorageRepository<NinjaEntity>
, that is injected in the NinjaRepository
.
However, to create the TableStorageRepository
, we need to provide an ITableStorageSettings
, as talked about earlier.
The default TableStorageSettings
implementation should do the job but, we needed to configure it.
As we previously saw, the TableStorageSettings
values come from the application configurations, here is the description of each value:
- AccountKey: this is one of your Azure Storage “Access Keys.”
- AccountName: this is your Azure Storage name, the one you entered during creation.
- TableName: this is the name of the table to use (you dont need to create anything, just define the name).
To find your AccountKey
, go to Azure, find your Table Storage Account and click on the `Access Keys` menu item.

Application settings
We now know what we need and that our values should come from the application configuration, but we don’t know how Asp.Net Core handles all of that. In Asp.Net Core 2.0, it is simpler than ever: by default, it read the application settings from appsettings.json
, appsettings.{env.EnvironmentName}.json
, User Secrets and Environment Variables, in that order.
This gives us a lot of possibilities and is provided to us with this simple call: WebHost.CreateDefaultBuilder(args)
(in Starup.cs
).
See the source code of WebHost.cs for more information about the
CreateDefaultBuilder()
method which creates the default configuration options.
We will configure and use the first 3 of these settings locations.
To start, in the appsettings.json
, we will add the default settings which are mostly placeholders but the TableName
.
"AzureTable": {
"AccountKey": "[Configure your account key in secrets]",
"AccountName": "[Configure your account name in secrets]",
"TableName": "Ninja"
}
In the appsettings.Development.json
we will override the TableName
to make sure that we are not writing to the “Production” environment’s table. As you may have noticed, we are doing the same thing that we did for our integration tests, but for the specific “Development” environment.
"AzureTable": {
"TableName": "NinjaDev"
}
Tips
In case of a real project, I would recommend storing production data in a different Storage Account than dev/testing.
Why? So many things can go wrong so fast that I don’t even know where to start… Just don’t use the same storage account
Finally, we will add our credentials to the User Secrets, using the secrets manager (don’t worry, it is only JSON).
The Secret Manager tool stores sensitive data for development work outside of your project tree. The Secret Manager tool is a project tool that can be used to store secrets for a .NET Core project during development. With the Secret Manager tool, you can associate app secrets with a specific project and share them across multiple projects.
To open the secrets manager, right-click the ForEvolve.Blog.Samples.NinjaApi
project then clicks the Manage User Secrets
menu item.

This should open an empty secrets.json
file.
We will use this file to manage our credentials.
In my opinions, the biggest upside of secrets is that it is located outside of the solution directory. Due to this, secrets are not added to source control, which gives the option of configuring settings per developer (connection strings, accounts, username, password, etc.). It is also allowing you to keep the production credentials somewhere else; developers might not even have access to production credentials.
The Secret Manager tool does not encrypt the stored secrets and should not be treated as a trusted store. It is for development purposes only. The keys and values are stored in a JSON configuration file in the user profile directory.
We will add our development credentials to secrets.json
.
{
"AzureTable": {
"AccountKey": "some-key==",
"AccountName": "some-account-name"
}
}
Where are those secret?
Secrets are saved in
%APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json
file. Visual Studio knows about<userSecretsId>
using the value of theUserSecretsId
tag located in thecsproj
file.Ex.: in the
ForEvolve.Blog.Samples.NinjaApi.csproj
file:<UserSecretsId>aspnet-ForEvolve.Blog.Samples.NinjaApi-F62B525A-ACF4-4C7C-BF23-1EB0F434DDE5</UserSecretsId>
which leads to the following file on disk:%APPDATA%\microsoft\UserSecrets\aspnet-ForEvolve.Blog.Samples.NinjaApi-F62B525A-ACF4-4C7C-BF23-1EB0F434DDE5\secrets.json
I am saying this because, you know, in case the tooling break…
Yes, connecting to Azure Table Storage was that easy!
It took way longer to write or read this part of the article than it takes to actually do the job.
As you can see, it is ultra easy to connect to Azure Table Storage when using ForEvolve.Azure
.
You only need to define a few settings and register a few services.
There are other functionalities in the framework, but I will keep that for another day.
The reason why I started to build that up was to speed up development of my projects.
Azure table storage is a great data source for multiple types of application, and I especially like it for quick prototyping and small applications (or why not a tiny application like a microservice). For more complex data integrity scenarios, there is always Azure Functions, Queues, etc. that you can use to keep tables in sync, duplicate data around, etc.
As a last advice: always analyze your need before picking up a technology; if you think that a relational database is the best for your project, choose that. However, if an Azure Table can do, don’t waste time and money on SQL Server.
More on that, the Azure Table API is supported as part of Cosmos DB, which could lead to an easy port to the newest kid on the block: Microsoft’s Globally distributed, multi-model database
.
That’s it for the Ninja subsystem integration
At this point, if we run our automated tests, everything should be green!
We should also be able to access the ninja’s data from a browser or with a tool like Postman by running the API (F5
).
We could also build a UI on top of it.
The end of this article
Congratulation, you reached this end of the article series!
Moreover, this end was not a typo. I have many ideas to build on top of the Ninja API; so yes, I might write articles based on this code in the future.
Backstory of this article series
I initially planned on writing a single article (epic fail). It quickly became a 5-8 article series. However, the 8th part was becoming so long that I split it into 3 pieces, adding a little to each piece. Which, after countless hours, led to this 11 article series.
Thanks for reading and I hope you enjoyed it!
What have we covered in this article?
In this article:
- We created integration tests, testing the API over HTTP (in-memory)
- We integrated the Ninja subsystem
- We used
ForEvolve.Azure
to connect the Ninja App to Azure Table Storage - We explored the new Asp.Net Core 2.0 default configuration
I hope you enjoyed the little glimpse of the ForEvolve Framework, it is still a work in progress, and there are many functionalities that I would like to add to it (and probably a lot more than I have not thought of yet).
If you have any interests, questions, requests or time to invest: feel free to leave a comment here, open an issue in the ForEvolve Framework GitHub repository, contact me on Twitter or on LinkedIn (see the page footer for these).
What’s next?
What’s next? You guys tell me.
- What are you up to now that you played a little around with Repositories, Services, and Web APIs?
- Any project in mind?
- Maybe a ninja game ahead?
I hope you enjoyed, feel free to leave a comment and happy coding!
Last word (shared section)
Table of content
Resources
Some additional resources used during the article (or not).
Articles & concepts
- Bounded Context
- Dependency Injection
- From STUPID to SOLID Code!
- Mocks Aren’t Stubs
- Testing and debugging ASP.NET Core
Tools & technologies
Code samples
Special thanks
I’d like to finish with special thanks to Emmanuel Genest who took the time to read my drafts and give me comments from a reader point of view.