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
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"
- Replace
[password]
for the connection string inappsettings.json
andappsettings.docker.json
. - Set
SA_PASSWORD
in thedocker-compose.yml
file to be the same as the password used in the connection string inappsettings.json
andappsettings.docker.json
. - In the command prompt, navigate to the solution level and run the command
docker-compose up -d
. - 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 rundotnet-ef database update
. If you are using an IDE (or other tools), you can find instructions in Microsoft’s migration documentation. - 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
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:
AssemblyVersionService
: Gets the version from the compiled assembly (default setting)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.