Skip to main content

This setup provides a comprehensive structure for a .NET Core 8 API project using the CQRS pattern with MediatR, FluentValidation, JWT authentication, Swagger documentation, and unit/integration tests.

---
title: .NET API Clean Architecture Project using CQRS
subtitle: This setup provides a comprehensive structure for a .NET Core 8 API project using the CQRS pattern with MediatR, FluentValidation, JWT authentication, Swagger documentation, and unit/integration tests.
author: Jon LaBelle
date: September 28, 2024
source: https://jonlabelle.com/snippets/view/markdown/net-api-clean-architecture-project-using-cqrs
notoc: false
---

In this article, we will walk through the process of setting up a .NET Core 8
API project using the CQRS pattern with MediatR, FluentValidation, JWT
authentication, Swagger documentation, and unit/integration tests. We will use a
vertical slice architecture to keep our code organized and maintainable.
Additionally, we will create commands for all CRUD operations.

## Project Structure

Here is the structure of our project:

```plaintext
MyApi
├── MyApi.Api
│   ├── Controllers
│   │   └── WeatherForecastController.cs
│   ├── Program.cs
│   └── appsettings.json
├── MyApi.Application
│   ├── Commands
│   │   └── WeatherForecasts
│   │       ├── CreateWeatherForecastCommand.cs
│   │       ├── CreateWeatherForecastCommandHandler.cs
│   │       ├── CreateWeatherForecastCommandValidator.cs
│   │       ├── UpdateWeatherForecastCommand.cs
│   │       ├── UpdateWeatherForecastCommandHandler.cs
│   │       ├── UpdateWeatherForecastCommandValidator.cs
│   │       ├── DeleteWeatherForecastCommand.cs
│   │       ├── DeleteWeatherForecastCommandHandler.cs
│   │       └── DeleteWeatherForecastCommandValidator.cs
│   ├── Queries
│   │   └── WeatherForecasts
│   │       ├── GetWeatherForecastQuery.cs
│   │       ├── GetWeatherForecastQueryHandler.cs
│   │       └── GetWeatherForecastQueryValidator.cs
│   ├── Common
│   │   ├── Behaviors
│   │   │   └── ValidationBehavior.cs
│   │   └── Results
│   │       ├── Result.cs
│   │       └── ResultExtensions.cs
├── MyApi.Domain
│   └── Entities
│       └── WeatherForecast.cs
├── MyApi.Infrastructure
│   ├── Data
│   │   ├── ApplicationDbContext.cs
│   │   └── WeatherForecastRepository.cs
│   └── Services
│       └── JwtService.cs
├── MyApi.Tests
│   ├── UnitTests
│   │   └── WeatherForecastTests.cs
│   └── IntegrationTests
│       └── WeatherForecastIntegrationTests.cs
```

## Setting Up the Project

### 1. Create the Solution and Projects

```bash
dotnet new sln -n MyApi
dotnet new webapi -n MyApi.Api
dotnet new classlib -n MyApi.Application
dotnet new classlib -n MyApi.Domain
dotnet new classlib -n MyApi.Infrastructure
dotnet new xunit -n MyApi.Tests
dotnet sln add MyApi.Api/MyApi.Api.csproj
dotnet sln add MyApi.Application/MyApi.Application.csproj
dotnet sln add MyApi.Domain/MyApi.Domain.csproj
dotnet sln add MyApi.Infrastructure/MyApi.Infrastructure.csproj
dotnet sln add MyApi.Tests/MyApi.Tests.csproj
dotnet add MyApi.Api/MyApi.Api.csproj reference MyApi.Application/MyApi.Application.csproj
dotnet add MyApi.Api/MyApi.Api.csproj reference MyApi.Infrastructure/MyApi.Infrastructure.csproj
dotnet add MyApi.Application/MyApi.Application.csproj reference MyApi.Domain/MyApi.Domain.csproj
dotnet add MyApi.Infrastructure/MyApi.Infrastructure.csproj reference MyApi.Domain/MyApi.Domain.csproj
```

### 2. Install Required NuGet Packages

```bash
dotnet add package MediatR
dotnet add package FluentValidation
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Swashbuckle.AspNetCore
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
```

### 3. Configure Program.cs

```csharp
// MyApi.Api/Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddMediatR(typeof(Program));
builder.Services.AddValidatorsFromAssembly(typeof(Program).Assembly);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<IWeatherForecastRepository, WeatherForecastRepository>();
builder.Services.AddTransient<IJwtService, JwtService>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
```

### 4. Create the WeatherForecast Entity

```csharp
// MyApi.Domain/Entities/WeatherForecast.cs
namespace MyApi.Domain.Entities
{
    public class WeatherForecast
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string Summary { get; set; }
    }
}
```

### 5. Create the ApplicationDbContext

```csharp
// MyApi.Infrastructure/Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
using MyApi.Domain.Entities;

namespace MyApi.Infrastructure.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

        public DbSet<WeatherForecast> WeatherForecasts { get; set; }
    }
}
```

### 6. Create the Repository

```csharp
// MyApi.Infrastructure/Data/WeatherForecastRepository.cs
using MyApi.Domain.Entities;

namespace MyApi.Infrastructure.Data
{
    public interface IWeatherForecastRepository
    {
        Task<WeatherForecast> GetByIdAsync(int id);
        Task AddAsync(WeatherForecast weatherForecast);
        Task UpdateAsync(WeatherForecast weatherForecast);
        Task DeleteAsync(int id);
    }

    public class WeatherForecastRepository : IWeatherForecastRepository
    {
        private readonly ApplicationDbContext _context;

        public WeatherForecastRepository(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<WeatherForecast> GetByIdAsync(int id)
        {
            return await _context.WeatherForecasts.FindAsync(id);
        }

        public async Task AddAsync(WeatherForecast weatherForecast)
        {
            await _context.WeatherForecasts.AddAsync(weatherForecast);
            await _context.SaveChangesAsync();
        }

        public async Task UpdateAsync(WeatherForecast weatherForecast)
        {
            _context.WeatherForecasts.Update(weatherForecast);
            await _context.SaveChangesAsync();
        }

        public async Task DeleteAsync(int id)
        {
            var weatherForecast = await _context.WeatherForecasts.FindAsync(id);
            if (weatherForecast != null)
            {
                _context.WeatherForecasts.Remove(weatherForecast);
                await _context.SaveChangesAsync();
            }
        }
    }
}
```

### 7. Create the Command and Query Handlers

#### Create Command

```csharp
// MyApi.Application/Commands/WeatherForecasts/CreateWeatherForecastCommand.cs
using MediatR;
using MyApi.Application.Common.Results;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class CreateWeatherForecastCommand : IRequest<Result<int>>
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string Summary { get; set; }
    }
}

// MyApi.Application/Commands/WeatherForecasts/CreateWeatherForecastCommandHandler.cs
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Domain.Entities;
using MyApi.Infrastructure.Data;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class CreateWeatherForecastCommandHandler : IRequestHandler<CreateWeatherForecastCommand, Result<int>>
    {
        private readonly IWeatherForecastRepository _repository;

        public CreateWeatherForecastCommandHandler(IWeatherForecastRepository repository)
        {
            _repository = repository;
        }

        public async Task<Result<int>> Handle(CreateWeatherForecastCommand request, CancellationToken cancellationToken)
        {
            var weatherForecast = new WeatherForecast
            {
                Date = request.Date,
                TemperatureC = request.TemperatureC,
                Summary = request.Summary
            };

            await _repository.AddAsync(weatherForecast);

            return Result.Success(weatherForecast.Id);
        }
    }
}

// MyApi.Application/Commands/WeatherForecasts/CreateWeatherForecastCommandValidator.cs
using FluentValidation;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class CreateWeatherForecastCommandValidator : AbstractValidator<CreateWeatherForecastCommand>
    {
        public CreateWeatherForecastCommandValidator()
        {
            RuleFor(x => x.Date).NotEmpty();
            RuleFor(x => x.TemperatureC).NotEmpty();
            RuleFor(x => x.Summary).NotEmpty().MaximumLength(200);
        }
    }
```

#### Update Command

```csharp
// MyApi.Application/Commands/WeatherForecasts/UpdateWeatherForecastCommand.cs
using MediatR;
using MyApi.Application.Common.Results;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class UpdateWeatherForecastCommand : IRequest<Result>
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string Summary { get; set; }
    }
}

// MyApi.Application/Commands/WeatherForecasts/UpdateWeatherForecastCommandHandler.cs
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Domain.Entities;
using MyApi.Infrastructure.Data;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class UpdateWeatherForecastCommandHandler : IRequestHandler<UpdateWeatherForecastCommand, Result>
    {
        private readonly IWeatherForecastRepository _repository;

        public UpdateWeatherForecastCommandHandler(IWeatherForecastRepository repository)
        {
            _repository = repository;
        }

        public async Task<Result> Handle(UpdateWeatherForecastCommand request, CancellationToken cancellationToken)
        {
            var weatherForecast = await _repository.GetByIdAsync(request.Id);
            if (weatherForecast == null)
            {
                return Result.Failure("Weather forecast not found.");
            }

            weatherForecast.Date = request.Date;
            weatherForecast.TemperatureC = request.TemperatureC;
            weatherForecast.Summary = request.Summary;

            await _repository.UpdateAsync(weatherForecast);

            return Result.Success();
        }
    }
}

// MyApi.Application/Commands/WeatherForecasts/UpdateWeatherForecastCommandValidator.cs
using FluentValidation;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class UpdateWeatherForecastCommandValidator : AbstractValidator<UpdateWeatherForecastCommand>
    {
        public UpdateWeatherForecastCommandValidator()
        {
            RuleFor(x => x.Id).GreaterThan(0);
            RuleFor(x => x.Date).NotEmpty();
            RuleFor(x => x.TemperatureC).NotEmpty();
            RuleFor(x => x.Summary).NotEmpty().MaximumLength(200);
        }
    }
```

#### Delete Command

```csharp
// MyApi.Application/Commands/WeatherForecasts/DeleteWeatherForecastCommand.cs
using MediatR;
using MyApi.Application.Common.Results;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class DeleteWeatherForecastCommand : IRequest<Result>
    {
        public int Id { get; set; }
    }
}

// MyApi.Application/Commands/WeatherForecasts/DeleteWeatherForecastCommandHandler.cs
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Infrastructure.Data;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class DeleteWeatherForecastCommandHandler : IRequestHandler<DeleteWeatherForecastCommand, Result>
    {
        private readonly IWeatherForecastRepository _repository;

        public DeleteWeatherForecastCommandHandler(IWeatherForecastRepository repository)
        {
            _repository = repository;
        }

        public async Task<Result> Handle(DeleteWeatherForecastCommand request, CancellationToken cancellationToken)
        {
            var weatherForecast = await _repository.GetByIdAsync(request.Id);
            if (weatherForecast == null)
            {
                return Result.Failure("Weather forecast not found.");
            }

            await _repository.DeleteAsync(request.Id);

            return Result.Success();
        }
    }
}

// MyApi.Application/Commands/WeatherForecasts/DeleteWeatherForecastCommandValidator.cs
using FluentValidation;

namespace MyApi.Application.Commands.WeatherForecasts
{
    public class DeleteWeatherForecastCommandValidator : AbstractValidator<DeleteWeatherForecastCommand>
    {
        public DeleteWeatherForecastCommandValidator()
        {
            RuleFor(x => x.Id).GreaterThan(0);
        }
    }
```

#### Get Query

```csharp
// MyApi.Application/Queries/WeatherForecasts/GetWeatherForecastQuery.cs
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Domain.Entities;

namespace MyApi.Application.Queries.WeatherForecasts
{
    public class GetWeatherForecastQuery : IRequest<Result<WeatherForecast>>
    {
        public int Id { get; set; }
    }
}

// MyApi.Application/Queries/WeatherForecasts/GetWeatherForecastQueryHandler.cs
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Domain.Entities;
using MyApi.Infrastructure.Data;

namespace MyApi.Application.Queries.WeatherForecasts
{
    public class GetWeatherForecastQueryHandler : IRequestHandler<GetWeatherForecastQuery, Result<WeatherForecast>>
    {
        private readonly IWeatherForecastRepository _repository;

        public GetWeatherForecastQueryHandler(IWeatherForecastRepository repository)
        {
            _repository = repository;
        }

        public async Task<Result<WeatherForecast>> Handle(GetWeatherForecastQuery request, CancellationToken cancellationToken)
        {
            var weatherForecast = await _repository.GetByIdAsync(request.Id);

            if (weatherForecast == null)
            {
                return Result.Failure<WeatherForecast>("Weather forecast not found.");
            }

            return Result.Success(weatherForecast);
        }
    }
}

// MyApi.Application/Queries/WeatherForecasts/GetWeatherForecastQueryValidator.cs
using FluentValidation;

namespace MyApi.Application.Queries.WeatherForecasts
{
    public class GetWeatherForecastQueryValidator : AbstractValidator<GetWeatherForecastQuery>
    {
        public GetWeatherForecastQueryValidator()
        {
            RuleFor(x => x.Id).GreaterThan(0);
        }
    }
```

### 8. Create Result Pattern Classes

```csharp
// MyApi.Application/Common/Results/Result.cs
namespace MyApi.Application.Common.Results
{
    public class Result
    {
        public bool IsSuccess { get; }
        public string Error { get; }

        protected Result(bool isSuccess, string error)
        {
            IsSuccess = isSuccess;
            Error = error;
        }

        public static Result Success() => new Result(true, string.Empty);
        public static Result Failure(string error) => new Result(false, error);
    }

    public class Result<T> : Result
    {
        public T Value { get; }

        protected Result(bool isSuccess, string error, T value) : base(isSuccess, error)
        {
            Value = value;
        }

        public static Result<T> Success(T value) => new Result<T>(true, string.Empty, value);
        public static Result<T> Failure(string error) => new Result<T>(false, error, default);
    }
}
```

### 9. Create the Controller

```csharp
// MyApi.Api/Controllers/WeatherForecastController.cs
using MediatR;
using Microsoft.AspNetCore.Mvc;
using MyApi.Application.Commands.WeatherForecasts;
using MyApi.Application.Queries.WeatherForecasts;

namespace MyApi.Api.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly IMediator _mediator;

        public WeatherForecastController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpPost]
        public async Task<IActionResult> Create(CreateWeatherForecastCommand command)
        {
            var result = await _mediator.Send(command);

            if (!result.IsSuccess)
            {
                return BadRequest(result.Error);
            }

            return Ok(result.Value);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> Update(int id, UpdateWeatherForecastCommand command)
        {
            if (id != command.Id)
            {
                return BadRequest("ID mismatch");
            }

            var result = await _mediator.Send(command);

            if (!result.IsSuccess)
            {
                return BadRequest(result.Error);
            }

            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            var command = new DeleteWeatherForecastCommand { Id = id };
            var result = await _mediator.Send(command);

            if (!result.IsSuccess)
            {
                return NotFound(result.Error);
            }

            return NoContent();
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> Get(int id)
        {
            var query = new GetWeatherForecastQuery { Id = id };
            var result = await _mediator.Send(query);

            if (!result.IsSuccess)
            {
                return NotFound(result.Error);
            }

            return Ok(result.Value);
        }
    }
}
```

### 10. Setup exception handling

Set up a global exception handler that follows the Problem Details specification
and uses a base exception class with status/error codes.

#### 1. Create a Base Exception Class

Create a base exception class that includes a status code and an error code.

**File: MyApi.Domain/Exceptions/AppException.cs**

```csharp
using System;

public abstract class AppException : Exception
{
    public int StatusCode { get; }
    public string ErrorCode { get; }

    protected AppException(string message, int statusCode, string errorCode) : base(message)
    {
        StatusCode = statusCode;
        ErrorCode = errorCode;
    }
}
```

#### 2. Create Specific Exception Classes

Create specific exception classes that inherit from the base exception class.

**File: MyApi.Domain/Exceptions/NotFoundException.cs**

```csharp
public class NotFoundException : AppException
{
    public NotFoundException(string message) : base(message, 404, "NotFound") { }
}
```

**File: MyApi.Domain/Exceptions/ValidationException.cs**

```csharp
public class ValidationException : AppException
{
    public ValidationException(string message) : base(message, 400, "ValidationError") { }
}
```

#### 3. Create a Global Exception Handling Middleware

Create a middleware to handle exceptions globally and format the response according to the Problem Details specification.

**File: MyApi.Api/Middleware/ExceptionHandlingMiddleware.cs**

```csharp
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An unhandled exception has occurred.");
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        context.Response.ContentType = "application/problem+json";

        var statusCode = (int)HttpStatusCode.InternalServerError;
        var errorCode = "InternalServerError";
        var detail = "An unexpected error occurred. Please try again later.";

        if (exception is AppException appException)
        {
            statusCode = appException.StatusCode;
            errorCode = appException.ErrorCode;
            detail = appException.Message;
        }

        context.Response.StatusCode = statusCode;

        var problemDetails = new
        {
            type = $"https://httpstatuses.com/{statusCode}",
            title = errorCode,
            status = statusCode,
            detail = detail,
            instance = context.Request.Path
        };

        var json = JsonSerializer.Serialize(problemDetails);

        return context.Response.WriteAsync(json);
    }
}
```

#### 4. Register the Middleware in Program.cs

Register the middleware in your `Program.cs` file.

**File: MyApi.Api/Program.cs**

```csharp
// Add this line after "var app = builder.Build()"
app.UseMiddleware<ExceptionHandlingMiddleware>();
```

#### 5. Use the Custom Exceptions in Your Application

Throw the custom exceptions in your application where appropriate.

**File: MyApi.Application/Commands/CreateWeatherForecast/CreateWeatherForecastCommandHandler.cs**

```csharp
using MediatR;
using MyApi.Application.Common.Results;
using MyApi.Domain.Entities;
using MyApi.Infrastructure.Data;
using System;
using System.Threading;
using System.Threading.Tasks;

public class CreateWeatherForecastCommandHandler : IRequestHandler<CreateWeatherForecastCommand, Result<int>>
{
    private readonly IWeatherForecastRepository _repository;

    public CreateWeatherForecastCommandHandler(IWeatherForecastRepository repository)
    {
        _repository = repository;
    }

    public async Task<Result<int>> Handle(CreateWeatherForecastCommand request, CancellationToken cancellationToken)
    {
        try
        {
            var weatherForecast = new WeatherForecast
            {
                Date = request.Date,
                TemperatureC = request.TemperatureC,
                Summary = request.Summary
            };

            await _repository.AddAsync(weatherForecast);

            return Result.Success(weatherForecast.Id);
        }
        catch (Exception ex)
        {
            // Log the exception and return a failure result
            throw new ValidationException("An error occurred while creating the weather forecast.");
        }
    }
}
```

**File: MyApi.Api/Controllers/WeatherForecastController.cs**

```csharp
using MediatR;
using Microsoft.AspNetCore.Mvc;
using MyApi.Application.Commands.CreateWeatherForecast;
using MyApi.Application.Queries.GetWeatherForecast;
using MyApi.Domain.Exceptions;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IMediator _mediator;

    public WeatherForecastController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateWeatherForecastCommand command)
    {
        var result = await _mediator.Send(command);

        if (!result.IsSuccess)
        {
            return BadRequest(result.Error);
        }

        return Ok(result.Value);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        try
        {
            var query = new GetWeatherForecastQuery { Id = id };
            var result = await _mediator.Send(query);

            if (!result.IsSuccess)
            {
                throw new NotFoundException("Weather forecast not found.");
            }

            return Ok(result.Value);
        }
        catch (NotFoundException ex)
        {
            return NotFound(ex.Message);
        }
    }
}
```

### 11. Add Unit and Integration Tests

#### Unit Tests

Unit tests will focus on testing individual components in isolation. We will use
xUnit and Moq for our unit tests.

```bash
dotnet add MyApi.Tests package xunit
dotnet add MyApi.Tests package Moq
```

**File: MyApi.Tests/UnitTests/WeatherForecastTests.cs**

```csharp
using Xunit;
using Moq;
using MyApi.Application.Commands.CreateWeatherForecast;
using MyApi.Infrastructure.Data;
using MyApi.Domain.Entities;
using System.Threading;
using System.Threading.Tasks;

public class WeatherForecastTests
{
    [Fact]
    public async Task CreateWeatherForecast_ShouldReturnSuccess()
    {
        // Arrange
        var repositoryMock = new Mock<IWeatherForecastRepository>();
        var handler = new CreateWeatherForecastCommandHandler(repositoryMock.Object);
        var command = new CreateWeatherForecastCommand
        {
            Date = DateTime.Now,
            TemperatureC = 25,
            Summary = "Sunny"
        };

        // Act
        var result = await handler.Handle(command, CancellationToken.None);

        // Assert
        Assert.True(result.IsSuccess);
        repositoryMock.Verify(r => r.AddAsync(It.IsAny<WeatherForecast>()), Times.Once);
    }

    [Fact]
    public async Task GetWeatherForecast_ShouldReturnSuccess()
    {
        // Arrange
        var repositoryMock = new Mock<IWeatherForecastRepository>();
        var handler = new GetWeatherForecastQueryHandler(repositoryMock.Object);
        var query = new GetWeatherForecastQuery { Id = 1 };
        var weatherForecast = new WeatherForecast { Id = 1, Date = DateTime.Now, TemperatureC = 25, Summary = "Sunny" };

        repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<int>())).ReturnsAsync(weatherForecast);

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        Assert.True(result.IsSuccess);
        Assert.Equal(weatherForecast, result.Value);
    }
}
```

#### Integration Tests

Integration tests will focus on testing the interaction between multiple
components. We will use xUnit and the `WebApplicationFactory` class for our
integration tests.

```bash
dotnet add MyApi.Tests package Microsoft.AspNetCore.Mvc.Testing
dotnet add MyApi.Tests package xunit
dotnet add MyApi.Tests package Moq
dotnet add MyApi.Tests package Microsoft.EntityFrameworkCore.InMemory
```

First, create a base class that handles the common setup tasks such as
configuring the test server and seeding the database.

**File: MyApi.Tests/IntegrationTests/IntegrationTestBase.cs**

```csharp
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using MyApi.Api;
using MyApi.Infrastructure.Data;
using System;
using System.Linq;
using System.Net.Http;
using Xunit;

public class IntegrationTestBase : IClassFixture<WebApplicationFactory<Program>>
{
    protected readonly HttpClient Client;

    private readonly WebApplicationFactory<Program> _factory;

    public IntegrationTestBase(WebApplicationFactory<Program> factory)
    {
        _factory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                var descriptor = services.SingleOrDefault(
                    d => d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));

                services.Remove(descriptor);

                services.AddDbContext<ApplicationDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                });

                var sp = services.BuildServiceProvider();

                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<ApplicationDbContext>();

                    db.Database.EnsureCreated();

                    SeedDatabase(db);
                }
            });
        });

        Client = _factory.CreateClient();
    }

    private void SeedDatabase(ApplicationDbContext db)
    {
        db.WeatherForecasts.Add(new MyApi.Domain.Entities.WeatherForecast
        {
            Date = DateTime.Now,
            TemperatureC = 25,
            Summary = "Sunny"
        });
        db.SaveChanges();
    }
}
```

**File: MyApi.Tests/IntegrationTests/WeatherForecastIntegrationTests.cs**

```csharp
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using MyApi.Application.Commands.CreateWeatherForecast;
using Xunit;

public class WeatherForecastIntegrationTests : IntegrationTestBase
{
    public WeatherForecastIntegrationTests(WebApplicationFactory<Program> factory) : base(factory) { }

    [Fact]
    public async Task CreateWeatherForecast_ShouldReturnSuccess()
    {
        // Arrange
        var command = new CreateWeatherForecastCommand
        {
            Date = DateTime.Now,
            TemperatureC = 25,
            Summary = "Sunny"
        };

        // Act
        var response = await Client.PostAsJsonAsync("/api/weatherforecast", command);

        // Assert
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task GetWeatherForecast_ShouldReturnSuccess()
    {
        // Act
        var response = await Client.GetAsync("/api/weatherforecast/1");

        // Assert
        response.EnsureSuccessStatusCode();
    }

    [Fact]
    public async Task GetWeatherForecast_ShouldReturnNotFound()
    {
        // Act
        var response = await Client.GetAsync("/api/weatherforecast/999");

        // Assert
        Assert.Equal(System.Net.HttpStatusCode.NotFound, response.StatusCode);
    }
}
```

You can now easily add more integration tests by creating new test methods in
the `WeatherForecastIntegrationTests` class or by creating new test classes that
inherit from `IntegrationTestBase`.

**Example: Additional Integration Test**

```csharp
public class AdditionalWeatherForecastIntegrationTests : IntegrationTestBase
{
    public AdditionalWeatherForecastIntegrationTests(WebApplicationFactory<Program> factory) : base(factory) { }

    [Fact]
    public async Task UpdateWeatherForecast_ShouldReturnSuccess()
    {
        // Arrange
        var updateCommand = new UpdateWeatherForecastCommand
        {
            Id = 1,
            Date = DateTime.Now.AddDays(1),
            TemperatureC = 30,
            Summary = "Hot"
        };

        // Act
        var response = await Client.PutAsJsonAsync("/api/weatherforecast/1", updateCommand);

        // Assert
        response.EnsureSuccessStatusCode();
    }
}
```

#### Run Your Tests

You can run your tests using the following command:

```bash
dotnet test
```

This will execute all the tests in your test project, including the integration tests.