BlackSlope in Action: A Guide to Using our DotNet Reference Architecture

A practical configuration guide for Slalom Build’s simple reference architecture designed for building ASP.NET Core APIs

Robert Watts
Slalom Build
Published in
9 min readJun 23, 2020

--

BlackSlope is an API reference architecture for ASP.NET Core applications. It has recently been open-sourced, and is now available through GitHub.

This post follows-up our post introducing and explaining the components of BlackSlope. Previously, we explained the architecture proposed by BlackSlope, so start there for an introduction on everything the project contains.

Here, we’ll help you take the next steps by showing BlackSlope in action and providing guidelines on practical ways to spin up, configure, and use a BlackSlope-based project.

Spin up

It is possible to host BlackSlope in different hosting environments like Azure App Service, Windows Server IIS, Self-hosted on Linux, or a Windows virtual machine. For the examples here, we’ll use a Docker container, and to store data, we’ll use a SQL Server instance, also running in a (different) Docker container.

Install Docker

BlackSlope can be set up and run in only a few steps using Docker, but first you’ll need to make sure you have the necessary programs and packages installed.

Follow Docker’s instructions to get it installed and running.

Install .NET Core 3.1

You will also need to download and install the .NET Core 3.1 SDK.

After that is installed, you should also install EF Core so that you can run database migrations locally. To install this tool, in the command prompt, run:

dotnet tool install --global dotnet-ef

Get the code

Either clone or download the BlackSlope repo from GitHub.

After cloning the repo, you should see that we provided an example Controller, Service, and Repository which implement basic functionality for managing (creating, getting, editing, deleting, etc.) a Movie API. This MovieController, MovieService, and MovieRepository are meant to act as an example of how the application handles a request, relays it to the SQL database, and responds to the user. The idea is that after looking at this simple example, you can build on top of the existing functionality or add your own Controllers, Services, and Repositories.

Settings

You will need to modify some settings to make sure that BlackSlope is configured for your environment prior to launching.

Most of these settings are defined in appsettings.json. Settings from appsettings.json can be overridden with local, environment-specific values in the appsettings.docker.json file (we can do this because we set an environment variable ASPNETCORE_ENVIRONMENT: “docker” in docker-compose.yml).

Open the solution folder in your preferred IDE. Locate docker-compose.yml file at the solution level and the appsettings.json and appsettings.docker.json files at the BlackSlope.Api project level.

appsettings.json

// appsettings.json
{
"BlackSlope.Api": {
"Swagger": {
"Version": "1",
"ApplicationName": "BlackSlope",
"XmlFile": "BlackSlope.Api.xml"
},
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "[tenant-id]",
"Audience": "https://[host-name]"
},
"Serilog": {
"MinimumLevel": "2",
"FileName": "log.txt",
"WriteToFile": "false",
"WriteToAppInsights": "false",
"WriteToConsole": "true"
},
"ApplicationInsights": {
"InstrumentationKey": "[instrumentation-key]"
},
"MoviesConnectionString": "Data Source=.,1401;initial catalog=movies;User Id=sa;Password=[password]"
},
"AllowedHosts": "*"
}

appsettings.docker.json

// appsettings.docker.json
{
"BlackSlope.Api": {
"Serilog": {
"WriteToFile": "true",
"WriteToConsole": "false"
},
"MoviesConnectionString": "data source=db,1433;initial catalog=movies;User Id=sa;Password=[password]"
}
}

docker-compose.yml

// docker-compose.yml
version: '3.5'
services:
db:
container_name: db
image: mcr.microsoft.com/mssql/server:2017-latest
environment:
SA_PASSWORD: "[password]"
ACCEPT_EULA: "Y"
MSSQL_PID: Developer
ports:
- "1401:1433"
apiapp:
container_name: apiapp
image: blackslope/api-app
build:
context: .
depends_on:
- db
ports:
- "5010:80"
environment:
ASPNETCORE_ENVIRONMENT: "docker"
  1. Replace [password] for the connection string in appsettings.json and appsettings.docker.json.
  2. Set SA_PASSWORD in the docker-compose.yml file to be the same as the password used in the connection string in appsettings.json and appsettings.docker.json.
  3. In the command prompt, navigate to the solution level and run the command docker-compose up -d.
  4. The first time you set this up, you’ll need to run the database migration to setup the database and tables. From the command prompt, when you are in the BlackSlope.Api folder, you can run dotnet-ef database update. If you are using an IDE (or other tools), you can find instructions in Microsoft’s migration documentation.
  5. Open a browser and navigate to http://localhost:5010/swagger/index.html. You should be able to use Swagger and try out the different endpoints. For example, try getting a list of all the movies: click on the first endpoint to expand its section, click on “Try it out,” then on “Execute.” You should see a list of movies in the response body
http://localhost:5010/swagger/index.html on initial load.
Trying out the first endpoint to get a list of all movies on http://localhost:5010/swagger/index.html.

Using Components

Fluent Validation

In order to use any validators, they have to be added to the services through an extension. This is registered in the Startup.cs file.

Each request validator requires an interface. Each interface needs to inherit from IBlackslopeValidator<TRequest>, where the TRequest is the type being validated.

All of the validators (for requests and view models) need to inherit from their appropriate interface, and also from BlackslopeValidator<TRequest>, again where TRequest is the request object or view model object to be validated.

Finally, in the constructor for the validator Class, any validation rules for the TRequest type should be added. For more details how to write validation rules, check Fluent Validation’s documentation.

public class UpdateMovieRequestValidator : BlackslopeValidator<UpdateMovieRequest>, IUpdateMovieRequestValidator
{
public UpdateMovieRequestValidator()
{
RuleFor(x => x.Movie)
.NotNull()
.WithState(x => MovieErrorCode.NullRequestModel); ...
}
}

Swagger

In order to use Swagger for built-in API documentation, you need to add it to the app pipeline, again in the Startup.cs file:

app.UseSwagger(HostConfig.Swagger);

There is also a section in appsettings.json which contains Swagger configuration data. You can customize Swagger behavior by changing the settings:

"Swagger": {
"Version": "1",
"ApplicationName": "BlackSlope",
"XmlFile": "BlackSlope.Api.xml"
}

AutoMapper

AutoMapper is used to help translate the different types of models used by BlackSlope’s application layers. In order to use AutoMapper, add it to the services collection by using the AddAutoMapper extension method (in Startup.cs):

public void ConfigureServices(IServiceCollection services)
{
...
services.AddAutoMapper();
}

You should define your object mappers in profile classes which inherit AutoMapper’s Profile class. AutoMapper will discover all the classes inheriting Profile, and use the defined mapper to map one type to another one. For more details how to create maps, check AutoMapper’s documentation.

Serilog

You can easily setup Serilog as the logging framework for BlackSlope. It has a Host Builder extension which can be used to configure Serilog before building the Web Host:

//Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog("configuration section")
.UseStartup<Startup>();

When using Serilog with the provided extension, its configuration must be added as part of appsettings.json. The custom extension UseSerilog tells it which section of the file it should read the Serilog configuration from. Within this extension, Serilog is configured at runtime to log according to the setting’s values:

//appsettings.json
{
"BlackSlope.Api": {
"Serilog": {
"MinimumLevel": "information",
"FileName": "log.txt",
"WriteToFile": "false",
"WriteToAppInsights": "false",
"WriteToConsole": "true"
}
// note: Minimum level could be either a level number or its equivalent name: Verbose = 0, Debug = 1,Information = 2, Warning = 3, Error = 4, Fatal = 5

For more details how to configure logging behavior, check Serilog’s documentation.

Application Insights

For performance management in BlackSlope, Application Insights can be utilized. If you’re using Visual Studio, you can use Application Insights locally to debug your application. Check out “Debug your applications with Azure Application Insights in Visual Studio” for more details.

Serilog also provides a Sink for Application Insights. This means that if you use Serilog for logging in BlackSlope, you can easily stream log data into Application Insights by setting the WriteToApplicationInsights setting to true in the Serilog configuration section.

In order to wire up your application to log in Application Insights in Azure, you would need to “Create an Application Insights resource.” After creating, add its instrumentation key to the ApplicationInsights section of the appsettings.json file:

//appsettings.json
{
"BlackSlope.Api": {
...
"ApplicationInsights": {
"InstrumentationKey": "[instrumentation-key]"
}
}
}

Versioning

The version of a BlackSlope-based application is exposed by default through an endpoint — the version can be retrieved by sending a GET request to /api/version.

There are currently two VersionServices, which you can set (in Startup.cs) depending on your preferred versioning strategy:

  1. AssemblyVersionService: Gets the version from the compiled assembly (default setting)
  2. JsonVersionService: Gets the version from a local JSON file

EF Core, Database

EF Core is a lightweight and extensible ORM which can easily be used by ASP.NET Core applications. In appsettings.json, there is a key-value pair for the connection string it should use. In the BlackSlope template, there is a connection which is used in MovieContext to connect to the database, as an example. This class inherits from EF Core’s DbContext, which defines a context that acts as a mapping to the connected Database. Any table needed by a Repository should map to a DTO.

The DBContext should be registered in the service collection. This can be done by defining an extension for IServiceCollection.

EF Core also has a migration feature which can be used to create and update a database. In order to execute a migration in Visual Studio, you can run dotnet-ef database update again. To get more details regarding migration and other ways to run it, check EF Core documentation for Migrations.

Authentication Middleware

ASP.NET Core has a built-in Authentication Middleware, which we make use of in BlackSlope. In order to add this to the app pipeline, use the below code in the Configure method in Startup.cs:

app.UseAuthentication();

To demonstrate this in BlackSlope, Azure AD is being used as an identity service. The implementation is described in the following section.

Authentication Using Azure AD

Authentication Middleware can be configured to use different identity providers. BlackSlope has a feature to set up and use Azure AD as an identity provider by default (again configured in in Startup.cs):

services.AddAzureAd(HostConfig.AzureAd)

To use the Azure AD service, you should “Create a new tenant in Azure Active Directory” and “Register the application with it.” Then set the tenant value to be the tenant id.

// appsettings.json
{
"BlackSlope.Api": {
"AzureAd": {
"AadInstance": "https://login.microsoftonline.com/{0}",
"Tenant": "[tenent-id]",
"Audience": "https://localhost:5010"
}
}
}

CorrelationId Middleware

BlackSlope is equipped with a middleware called CorrelationId which can be used to track asynchronous transactions across application services, components, and layers — this is explained in more depth by our previously published article in the BlackSlope series. By simply adding this middleware to the pipeline, the API application will have the ability to utilize this useful feature.

In order to add CorrelationIds to the app pipeline, you can tell BlackSlope to use this middleware by adding the below code in the Configure method in Startup.cs:

app.UseMiddleware<CorrelationIdMiddleware>();

Exception Handling Middleware

Also explained in the first part of this series, BlackSlope provides exception handling middleware for centralized processing of exceptions that may occur in many different places while handling requests. The app pipeline can be equipped with this middleware by adding the below code in the Configure method in the Startup.cs file:

app.UseMiddleware<ExceptionHandlingMiddleware>();

When an otherwise-unhandled exception occurs within the pipeline while equipped with this middleware, the exception will be gracefully handled and a proper error message would be sent back to the caller. This middleware also logs the exception using provided logging sources. An example of an error message which would be sent back to the caller:

{
"data": {
"movie": {
"title": "test",
"description": null,
"releaseDate": null
}
},
"errors": [
{
"code": 40004,
"message": "Movie Description cannot be null or empty"
}
]
}

Final Thoughts

This is just a simple demonstration of BlackSlope features to help in learning how to configure and utilize this powerful tool. Armed with this base project, we encourage you to start using BlackSlope for your own projects.

Lastly, do you have ideas for BlackSlope improvements or want to contribute to the open source project? Feel free to get involved! You can do so by opening an issue or a pull request. All ideas for improvement are welcome as this project continues to grow and evolve.

--

--