Create you’re first CQRS webapi

CQRS is short for Command Query Responsibility Segregation. A couple of years ago, when I just started web programming, I came in contact with this design pattern. This pattern separates read and update operations for a data store. Read here for the basic description.

1. How to start?

First step it to create a new webapi project (Preferably .NET5). I named it GJ.CQRSCore.Example.

1.1 Setup basic structure

The next step is to install my NuGet Package GJ.CQRSCore. This is my personal framework to use for CQRSCore. In a next blog I will discuss the parts that are in this core library. Also add the following empty .NET Standard projects to the solution with corresponding folders:

  • GJ.CQRSCore.Example.BusinessLogic
    • CommandHandlers
    • QueryHandlers
    • Validators
  • GJ.CQRSCore.Example.Data
    • Interfaces
    • Models
  • GJ.CQRSCore.Example.Models
    • Commands
    • Queries

1.2 Setup Data layer

We create a basic data layer that stores it’s information in statics. Ofcourse that is not a real life situation. You can replace this yourself with EntityFramework, MongoDb or another database. Copy the GJ.CQRSCore.Example.Data project to you’re own project.

No we are going to configure the data layer in the startup.cs. Add in the method configure services the following lines of code:

services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddScoped<IOfficeRepository, OfficeRepository>();

And add the two repositories in the Configure method and add GenerateStartUpInfo to the method.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, 
            ICompanyRepository companyRepo, 
            IOfficeRepository officeRepo)
{
    TestData.GenerateStartupInfo(companyRepo, officeRepo);
    ###
}

Now whe have some testdata, that is loaded on start-up.

2. Create you’re first query

In this chapter we are creating our first query. Queries are used for getting information. We want a list with all the companies with their CEO’s.

2.1 Creating the response model

First we create the model we want to receive as a response. We should put this in “GJ.CQRSCore.Example.Models”:

using System;

namespace GJ.CQRSCore.Example.Models
{
    public class CompanyCeoModel
    {
        public Guid CompanyId { get; set; }
        public string CompanyName { get; set; }
        public string CEO { get; set; }
    }
}

2.2 Creating the QueryModel

We are now creating the model, that we are going to use to query the backend. We create a class that inherits from the IQuery interface. IQuery is part of GJ.CQRSCore. With this interface it can be used in the coming steps.

Add the following class to “GJ.CQRSCore.Example.Models.Queries”:

using GJ.CQRSCore.Interfaces;

namespace GJ.CQRSCore.Example.Models.Queries
{
    public class GetCompanyWithCeoListQuery : IQuery
    {
    }
}

Alert

Be as descriptive as possible for naming commands and queries. This makes CQRS most readable.

2.3 Adding the QueryHandler

Now we are adding the businesslogic in the queryhandler. This queryhandler inherits from QueryHandlerBase. The first parameter of QueryHandlerBase is the inputquery defined in step 2.2. The second parameter is the expected result of the method. Now add the following file in “GJ.CQRSCore.Example.BusinessLogic.QueryHandlers”:

using GJ.CQRSCore.Example.Models;
using GJ.CQRSCore.Example.Models.Queries;
using System.Collections.Generic;

namespace GJ.CQRSCore.Example.BusinessLogic.QueryHandlers
{
    public class GetCompanyWithCeoListQueryHandler : QueryHandlerBase<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>>
    {
        public override IList<CompanyCeoModel> Handle(GetCompanyWithCeoListQuery query)
        {
            return new List<CompanyCeoModel>();
        }
    }
}

The next part is adding the business logic in the GetCompanyWithCeoListQueryHandler. First we need to introduce the constructor that injects ICompanyRepository.

private readonly ICompanyRepository _companyRepository;

public GetCompanyWithCeoListQueryHandler(ICompanyRepository companyRepository)
{
    _companyRepository = companyRepository;
}

Now we can add the logic to the handle function. We need to use companyRepository to get the info from dat datalayer:

public override IList<CompanyCeoModel> Handle(GetCompanyWithCeoListQuery query)
{
    return _companyRepository.GetAll().Select(x=> new CompanyCeoModel()
    {
        CompanyId = x.Id,
        CompanyName = x.Name,
        CEO = x.CEO
    }).ToList();
}

2.4 Registering the QueryHandler in the dependency injection

We are going back to the startup.cs to register the queryhandler in dependency injection. Add the following line of code in the method that is called ConfigureServices:

services.AddScoped<IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>>, GetCompanyWithCeoListQueryHandler>();

2.5 Adding the controller

We first start with adding a new controller named “CompanyController”.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace GJ.CQRSCore.Example.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class CompanyController : ControllerBase
    {

    }
}

Then we are going add a constructor that injects IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>>:

private readonly IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>> _getCompanyWithCeoListQueryHandler;
public CompanyController(IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>> getCompanyWithCeoListQueryHandler)
{
    _getCompanyWithCeoListQueryHandler = getCompanyWithCeoListQueryHandler;
}

The next part is adding a method that calls this queryhandler:

[HttpGet("GetCompanyWithCeoListQuery")]
public IEnumerable<CompanyCeoModel> Get()
{
    return _getCompanyWithCeoListQueryHandler.Execute(new GetCompanyWithCeoListQuery());
}

Now we can run the solution. When you browse to /company/GetCompanyWithCeoListQuery or use postman, you get response in json format with two companies and their according CEO’s.

3. Create you’re first command

In this chapter we are creating our first command. Commands are used We want to add a company with an adress and the command should automatically add a company and a office.

3.1 Creating the CommandModel

We are now creating the model, that we are going to use to send the command to the backend. We create a class that inherits from the ICommand interface. ICommand is part of GJ.CQRSCore. With this interface it can be used in the coming steps.

Add the following class to “GJ.CQRSCore.Example.Models.Commands”:

using GJ.CQRSCore.Interfaces;
using System;

namespace GJ.CQRSCore.Example.Models.Commands
{
    public class AddCompanyWithOfficeCommand : ICommand
    {
        public string CompanyName { get; set; }
        public string CEO { get; set; }
        public string BuildingName { get; set; }
        public string Street { get; set; }
        public int HouseNumber { get; set; }
        public string City { get; set; }
    }
}

3.2 Adding the CommandHandler

Now we are adding the businesslogic in the commandhandler. This commandhandler inherits from CommandHandlerBase. The first parameter of CommandHandlerBase is the inputcommand defined in step 3.1. Now add the following file in “GJ.CQRSCore.Example.BusinessLogic.CommandHandlers”:

using GJ.CQRSCore.Example.Data.Interfaces;
using GJ.CQRSCore.Example.Data.Models;
using GJ.CQRSCore.Example.Models.Commands;
using System;

namespace GJ.CQRSCore.Example.BusinessLogic.CommandHandlers
{
    public class AddCompanyWithOfficeCommandHandler : CommandHandlerBase<AddCompanyWithOfficeCommand>
    {
        private readonly ICompanyRepository _companyRepository;
        private readonly IOfficeRepository _officeRepository;

        public AddCompanyWithOfficeCommandHandler(ICompanyRepository companyRepository, IOfficeRepository officeRepository)
        {
            _companyRepository = companyRepository;
            _officeRepository = officeRepository;
        }

        public override void Handle(AddCompanyWithOfficeCommand command)
        {
            var office = new Office() {
                Id = Guid.NewGuid(),
                BuildingName = command.BuildingName,
                Street = command.Street,
                HouseNumber = command.HouseNumber,
                City = command.City
            };
            var company = new Company() {
                Id = Guid.NewGuid(),
                Name = command.CompanyName,
                CEO = command.CEO,
            };
            company.OfficeIds.Add(office.Id);

            _companyRepository.Add(company);
            _officeRepository.Add(office);
        }
    }
}

3.3 Registering the CommandHandler in the dependency injection

We are going back to the startup.cs to register the queryhandler in dependency injection. Add the following line of code in the method that is called ConfigureServices:

services.AddScoped<ICommandHandler<AddCompanyWithOfficeCommand>, AddCompanyWithOfficeCommandHandler>();

3.4 Expand the controller

Now we are going to expand the controller with the new command. We start with expanding the constructor:

private readonly IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>> _getCompanyWithCeoListQueryHandler;
private readonly ICommandHandler<AddCompanyWithOfficeCommand> _addCompanyWithOfficeCommandHandler;

public CompanyController(
     IQueryHandler<GetCompanyWithCeoListQuery, IList<CompanyCeoModel>> getCompanyWithCeoListQueryHandler,
     ICommandHandler<AddCompanyWithOfficeCommand> addCompanyWithOfficeCommandHandler)
{
     _getCompanyWithCeoListQueryHandler = getCompanyWithCeoListQueryHandler;
     _addCompanyWithOfficeCommandHandler = addCompanyWithOfficeCommandHandler;
}

The next step is adding the new operator:

[HttpPost("AddCompanyWithOfficeCommand")]
public IActionResult AddCompanyWithOfficeCommand([FromBody] AddCompanyWithOfficeCommand command)
{
     if (command == null) throw new ArgumentNullException();

     _addCompanyWithOfficeCommandHandler.Execute(command);

    return Ok();
}

3.5 Testing the controller

Last step is testing the controller. We do this by starting the application and run postman. We are going to create a request with the following URL: “https://localhost:44356/company/AddCompanyWithOfficeCommand”. In the body we will the following json:

{
    "CompanyName": "TestCompany",
    "CEO": "Tester Test",
    "BuildingName": "Test Location",
    "Street": "Test Street",
    "Housenumber": 1,
    "City": "TestCity"
}

Now we can check if the command has succesfully run, by opening the browser on “https://localhost:44356/company/GetCompanyWithCeoListQuery”.

URL

I used the url https:///localhost:44356. This depends on the config of your solution.

4. Create you’re first validator

We don’t want any companies that have no name or a double name. Also we don’t want any offices with no fields filled or multiple entries on the same adress. That’s why we need a validator with validation logic.

4.1 Create the validator

We start with creating a empty validator class in “GJ.CQRSCore.Example.BusinessLogic.Validators”:

using GJ.CQRSCore.Example.Models.Commands;
using GJ.CQRSCore.Interfaces;
using GJ.CQRSCore.Validation;

namespace GJ.CQRSCore.Example.BusinessLogic.Validator
{
    public class AddCompanyWithOfficeCommandValidator : IValidator<AddCompanyWithOfficeCommand>
    {
        public ValidationResults Validate(ValidationResults results, AddCompanyWithOfficeCommand validatableObject)
        {

            return results;
        }
    }
}

We will add the logic for checking the values on not null or empty. Add the following code to the validate method and:

results = ValidateNoNullOrEmptyValues(results, validatableObject);

And add the following method to the AddCompanyWithOfficeCommandValidator.cs:

private static ValidationResults ValidateNoNullOrEmptyValues(ValidationResults results, AddCompanyWithOfficeCommand validatableObject)
{
    results.ValidateNotNullOrEmpty(validatableObject.CompanyName, nameof(validatableObject.CompanyName));
    results.ValidateNotNullOrEmpty(validatableObject.CEO, nameof(validatableObject.CEO));
    results.ValidateNotNullOrEmpty(validatableObject.BuildingName, nameof(validatableObject.BuildingName));
    results.ValidateNotNullOrEmpty(validatableObject.Street, nameof(validatableObject.Street));
    results.ValidateNotNull(validatableObject.HouseNumber, nameof(validatableObject.HouseNumber));
    results.ValidateNotNullOrEmpty(validatableObject.City, nameof(validatableObject.City));
    return results;
}

The next validation is to add the other validations. Now add the following constructor to the AddCompanyWithOfficeCommandValidator.cs so we can call the ICompanyRepository and the IOfficeRepository:

private readonly ICompanyRepository _companyRepository;
private readonly IOfficeRepository _officeRepository;
public AddCompanyWithOfficeCommandValidator(ICompanyRepository companyRepository, IOfficeRepository officeRepository)
{
    _companyRepository = companyRepository;
    _officeRepository = officeRepository;
}

Add the following code to the validate method:

results = ValidateCompanyNameDoesntExist(results, validatableObject);
results = ValidateAddressAlreadyExists(results, validatableObject);

And add the following methods to the AddCompanyWithOfficeCommandValidator.cs:

private ValidationResults ValidateAddressAlreadyExists(ValidationResults results, AddCompanyWithOfficeCommand validatableObject)
{
   if (_officeRepository.GetAll().Any(x => x.Street == validatableObject.Street && x.HouseNumber == validatableObject.HouseNumber && x.City == validatableObject.City))
   {
       results.AddValidationResult(nameof(validatableObject.CompanyName), "The adress already exists in the database.");
   }
   return results;
}

private ValidationResults ValidateCompanyNameDoesntExist(ValidationResults results, AddCompanyWithOfficeCommand validatableObject)
{
   if (_companyRepository.GetAll().Any(x => x.Name == validatableObject.CompanyName))
   {
        results.AddValidationResult(nameof(validatableObject.CompanyName), "{0} already exists.");
   }
   return results;
}

4.2 Adding the validator to the CommandHandler

Now we are going to add the validator to the commandhandler by modifying the constructor in AddCompanyWithOfficeCommandHandler.cs:

public AddCompanyWithOfficeCommandHandler(IValidator<AddCompanyWithOfficeCommand> validator, ICompanyRepository companyRepository, IOfficeRepository officeRepository) : base(validator)
{
    _companyRepository = companyRepository;
    _officeRepository = officeRepository;
}

Easy

The commandhandlerbase class takes care of calling the validator. So we don’t have to worry about that.

4.3 Registering the validator in the dependency injection

We are going back to the startup.cs to register the validator in dependency injection. Add the following line of code in the method that is called ConfigureServices:

services.AddScoped<IValidator<AddCompanyWithOfficeCommand>, AddCompanyWithOfficeCommandValidator>();

4.4 Testing the validator

Last step is testing the controller. We do this by starting the application and run postman. We are going to create a request with the following URL: “https://localhost:44356/company/AddCompanyWithOfficeCommand”. In the body we will the following json:

{
    "CompanyName": "",
    "CEO": "",
    "BuildingName": "",
    "Street": "",
    "Housenumber": 1,
    "City": ""
}

We now get a result with the following exception:

GJ.CQRSCore.Validation.ValidationException: CompanyName cannot be null or empty
CEO cannot be null or empty
BuildingName cannot be null or empty
Street cannot be null or empty
City cannot be null or empty

If we send the following code twice, the first time we get a succes callback:

{
    "CompanyName": "TestCompany",
    "CEO": "Tester Test",
    "BuildingName": "Test Location",
    "Street": "Test Street",
    "Housenumber": 1,
    "City": "TestCity"
}

The second time we should get the following exception:

GJ.CQRSCore.Validation.ValidationException: CompanyName already exists.
The adress already exists in the database.

5. Conclusion

Now we have a working example in of CQRS. If you want to have a look al the example code. Please click here.

If you have any questions, please let me know!

Using GitHub Actions to build, publish and deploy NuGet Packages

In my dayjob i’m all about automating tasks that are dumb and repetitive. Now I was creating a NuGet package for a hobby project and it wanted to automate that to. After some looking into this, I found GitHub Actions.

Github actions

With GitHub Actions you can automate all kinds of stuff starting with you’re code on GitHub. This is from the website of GitHub:

Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you’d like, including CI/CD, and combine actions in a completely customized workflow.

https://docs.github.com/en/actions

What do we need?

The following parts you need before we can start:

  • Source code that you want to package and publish to NuGet
  • A unique name – You can check this by searching for the name you want to use, here!
  • A GitHub account
  • A NuGet.Org accound

Step 1: GitHub Repository

The first step is creating you’re GitHub repository here! When you have done this, you should commit you’re sourcecode to this repository.

Step 2: Create a new GitHub Action

When you are in you’re git hub repository click on “Actions”(1). Here can you select a predefined workflow or start creating a workflow from scratch. In this example, i’m gonna build a .NET Standard project, so i’ll choose the .NET Predefined workflow. Click on “Set up this workflow”(2)

Now a new yaml file is created. It should look like this:

name: .NET

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal

We can breakout the script as following:

name: .NET

Name of the workflow

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

When the workflow should start. In this case when there is a push to the “main” branch and when there is a pull request to the “main” branch.

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal

These are the build steps of the workflow.
It starts with setting the dotnet version, restores dependencies, build the software and at lasts runs the unit tests.

Step 3: Updating the yaml workflow file

We start with updating the name to a better description of this workflow:

name: .NET
name: Publish Package

Now we remove the functionality that the workflow starts when a pullrequest is started:

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
on:
  push:
    branches: [ main ]

When you did this add the following code to the end, as a new step:

- name: Publish
      uses: brandedoutcast/publish-nuget@v2.5.2  
      with: 
       PROJECT_FILE_PATH: [[PROJECT_LOCATION]]  
       NUGET_KEY: ${{secrets.NUGET_API_KEY}}
       PACKAGE_NAME: [[PROJECT_NAME]]   

The following properties you should add yourself:

  • “[[PROJECT_LOCATION]]” – You should fill in the location of the project you want to build. For example: “example/example.csproj”
  • “[[PROJECT_NAME]]” – You should fill here the unique name you have chosen for you’re NuGet package.

For the “secrets.NUGET_API_KEY”, we come back in the following steps.

Warning

If you’re solution file is not in the root directory, you should the following to every step:

working-directory: ./solutiondirectory/

Now we should have the following file:

name: Publish Package

on:
  push:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal
    - name: Publish
      uses: brandedoutcast/publish-nuget@v2.5.2  
      with: 
       PROJECT_FILE_PATH: [[PROJECT_LOCATION]]  
       NUGET_KEY: ${{secrets.NUGET_API_KEY}}
       PACKAGE_NAME: [[PROJECT_NAME]]   

Now we can click on “Start commit”(1) and then “Commit new file”(2)

When you click again on “Actions” then we can see the workflows is already started. If we click on the build we can see the steps. When we wait, the following steps are completed:

  • Setup .NET
  • Restore dependencies
  • Build
  • Test

And the Publish package has failed because we first need need to create a API key on nuget.org and fill the NUGET_API_KEY in GitHub Secrets.

Step 4: Create NuGet Api Key

We are hosting our package at NuGet.org. So open the webpage and login. Then click your accountname and API Keys.

Now click on create and fill in the “Key Name”(1). I named it “Github Nuget API Key”. Then you should fill a asterix(*) with “Glob Pattern”(2). Then you can create the key with “Create”(3).

Now the API-key is created and you can click on “Copy” to copy the API-Key to the clipboard.

Step 5: Adding NuGet API Key as a GitHub secret

In this step we put the NuGet API Key as a Github secret. We start with opening the repository. Then click on “Settings”(1), “Secrets”(2) and last “New repository secret”(3)

Now we are going to fill the fields. First we fill “NUGET_API_KEY” in the “Name”(1) field. In the “Value”(2) field you fill the NuGet Api Key, generated in the previous step. And click on “Add secret”(3).

Now you created the secret that can be used in the yaml workflow file.

Step 6: Re-run GitHub Action

Now we completely configured the workflow. Now we can re-run the GitHub Action. Yo can do this by opening “Actions” and click on the latest run workflow run result.
In this screen you can click “Re-run jobs”.

If you have a green check mark, then the build and deploy is finished correctly.

Now you can find the NuGet package in you’re NuGet package manager and/or on NuGet.Org.

Versioning

Without changing versions, the next run will fail. Every time you want a new version on NuGet, you have to update the version of you’re project.

Hiding connection strings from public Git using .gitignore

Introduction

Currently i’m increasing my visisbility on the great and big internet. One thing was to add some hobby projects to my public GIT. The first problem I walked into was that there was a secret detected in my public code. Oh no, I uploaded my MongoDb connection string with username and password to public GIT. I quickly removed the public repository and then tought, how am I gonna tackle this problem.

Prerequisites

I’m working with a .NET 5 Solution, but this also should work in .NET Core.

Warning

You’re code should not already be in git, or else the connection strings can be found in the Git history.

Solution

My solution is using a development version of the normal appsettings.json file, named appsettings.Development.json. And hide it using .gitignore.

You can find the appsettings.Development.json blij clicking open the appsettings.json:

Step 1

First you start with adding the following code to the appsettings.json:

"ConnectionStrings": 
{
    "MongoDbConnectionString": ""
}

Step 2

Now open the file appsettings.Development.json and add the connection strings with the connection string you want to be hidden from Git:

"ConnectionStrings": 
{
    "MongoDbConnectionString": "##THESECRETMONGODBCONNECTIONSTRING##"
}

Warning!

Please check if the structure of the appsettings.json and the appsettings.Development.json are the same. I had a issue with that the template of both files where not the same.

Step 3

Now create in the root directory a file names .gitignore, or use f.e. this one.

And then add the following lines of code at the bottom:

# Config files
appsettings.Development.json

Now you can add you’re sourcecode to GitHub or another public git repository. The appsettings.Development.json will be ignored by GIT and won’t be in youre public repository.

Notes

Ofcourse there are other ways to hide your connectionstrings from your public Git repository. F.e. with the user secrets. Maybe I will check this out later and write a blog about that.