- Create new, ASP.NET Core Web Api Project (c#, Linux, macOS, ...)
- project name: QuestionsApp.Web
- solution name: QuestionsApp
- target framework: .NET 7.0
- Authentication Type: None
- Configure HTTPS: Not checked
- Enable Docker: Not checked
- Use controllers: Not checked
- Enable OpenApi Support: Checked
- Do not use top-level statements: Not checked
- Add new xUnit Test Project (c#, Linux, macOS, ...)
- project name: QuestionsApp.Tests
- target framework .NET 7.0
- Save all
- Add the MediatR Package to the QuestionsApp.Web Dependencies
- Save all
- Add the folders in the QuestionsApp.Web project
- Api
- Api/Commands
- Api/Queries
- Add using MediatR;
- Save the file
Add the GetQuestionsResponse class with int ID, string Content, int Votes properies
public class GetQuestionsResponse
{
public int ID { get; set; }
public string Content { get; set; } = "";
public int Votes { get; set; }
}
Add the GetQuestionsRequest class with IRequest<List<GetQuestionsResponse>> interface
public class GetQuestionsRequest : IRequest<List<GetQuestionsResponse>>
{ }
Add the IRequestHandler<GetQuestionsRequest, List<GetQuestionsResponse>> interface to the GetQuestionsQuery class and add the empty implementation
public class GetQuestionsQuery : IRequestHandler<GetQuestionsRequest, List<GetQuestionsResponse>>
{
public Task<List<GetQuestionsResponse>> Handle(GetQuestionsRequest request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
- Add using MediatR;
- Save the file
Add the AskQuestionRequest class with IRequest<IResult> interface
public class AskQuestionRequest :IRequest<IResult>
{
public string Content { get; set; } = "";
}
Add the IRequestHandler<AskQuestionRequest, IResult> interface to the AskQuestionCommand class and add the empty implementation
public class AskQuestionCommand : IRequestHandler<AskQuestionRequest, IResult>
{
public Task<IResult> Handle(AskQuestionRequest request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
- Add using MediatR;
- Save the file
Add the VoteForQuestionRequest class with IRequest<IResult> interface
public class VoteForQuestionRequest : IRequest<IResult>
{
public int QuestionID { get; set; }
}
Add the IRequestHandler<VoteForQuestionRequest, IResult> interface to the VoteForQuestionCommand class and add the empty implementation
public class VoteForQuestionCommand : IRequestHandler<VoteForQuestionRequest, IResult>
{
public Task<IResult> Handle(VoteForQuestionRequest request, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
Remove the Example code (Summary, app.MapGet and WeatherForecast record
// remove summaries
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
// remove MapGet
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
});
// remove WeatherForecast
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
- Add using MediatR;
Add the MediatR Registration after builder.Services.AddSwaggerGen()
builder.Services.AddSwaggerGen();
// Register MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>());
- Add using QuestionsApp.Web.Api.Commands;
- Add using QuestionsApp.Web.Api.Queries;
Add the query and command maps before the app.Run() statement
// Queries
app.MapGet("api/queries/questions", async (IMediator mediator)
=> await mediator.Send(new GetQuestionsRequest()));
// Commands
app.MapPost("api/commands/questions/", async (IMediator mediator, string content)
=> await mediator.Send(new AskQuestionRequest { Content = content }));
app.MapPost("api/commands/questions/{id:int}/vote", async (IMediator mediator, int id)
=> await mediator.Send(new VoteForQuestionRequest { QuestionID = id }));
app.Run();
- Add a project refererence to the QuestionsApp.Web project in the QuestionsApp.Tests project
- Add the NuGet Package FluentAssertions
- Rename the Unittest1.cs file to QuestionsTests.cs
- Remove the Test1 Method in the QuestionsTests.cs
- Add using FluentAssertions;
- Add using QuestionsApp.Web.Api.Commands;
- Add using QuestionsApp.Web.Api.Queries;
Helper methods to create instances of the handlers
private GetQuestionsQuery GetQuestionsQueryHandler => new();
private AskQuestionCommand AskQuestionCommandHandler => new();
private VoteForQuestionCommand VoteForQuestionCommandHandler => new();
Empty Test
[Fact]
public async void Empty()
{
var response = await GetQuestionsQueryHandler.Handle(new GetQuestionsRequest(), default);
response.Should().BeEmpty();
}
OneQuestion
[Fact]
public async void OneQuestion()
{
var askResponse = await AskQuestionCommandHandler.Handle(new AskQuestionRequest { Content = "Dummy Question" }, default);
askResponse.Should().NotBeNull();
var response = await GetQuestionsQueryHandler.Handle(new GetQuestionsRequest(), default);
response.Should().HaveCount(1);
}
OneQuestionAndVote Test
[Fact]
public async void OneQuestionAndVote()
{
var askResponse = await AskQuestionCommandHandler.Handle(new AskQuestionRequest { Content = "Dummy Question" }, default);
askResponse.Should().NotBeNull();
var response = await GetQuestionsQueryHandler.Handle(new GetQuestionsRequest(), default);
response.Should().HaveCount(1);
response[0].Votes.Should().Be(0);
var voteResponse = await VoteForQuestionCommandHandler.Handle(new VoteForQuestionRequest { QuestionID = response[0].ID }, default);
voteResponse.Should().NotBeNull();
response = await GetQuestionsQueryHandler.Handle(new GetQuestionsRequest(), default);
response.Should().HaveCount(1);
response[0].Votes.Should().Be(1);
}