Skip to content

Latest commit

 

History

History
292 lines (218 loc) · 7.58 KB

Step1.md

File metadata and controls

292 lines (218 loc) · 7.58 KB

Step 1 - Setup the project

Create the Solution

  • 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 Nuget Package

  • Add the MediatR Package to the QuestionsApp.Web Dependencies
  • Save all

Add folders for the Web Api

  • Add the folders in the QuestionsApp.Web project
    • Api
    • Api/Commands
    • Api/Queries

Add the GetQuestionsQuery class

Add a class GetQuestionsQuery in the Api/Queries/ folder

  • Add using MediatR;
  • Save the file

Add Request and Response Classes

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>>
{ }

Setup the GetQuestionsQuery Request-Handler

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 the AskQuestionCommand class

Add a class AskQuestionCommand in the Api/Commands/ folder

  • Add using MediatR;
  • Save the file

Add Request Class

Add the AskQuestionRequest class with IRequest<IResult> interface
public class AskQuestionRequest :IRequest<IResult>
{
	public string Content { get; set; } = "";
}

Setup the AskQuestionCommand Request-Handler

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 the VoteForQuestionCommand class

Add a class VoteForQuestionCommand in the Api/Commands/ folder

  • Add using MediatR;
  • Save the file

Add Request Class

Add the VoteForQuestionRequest class with IRequest<IResult> interface
public class VoteForQuestionRequest : IRequest<IResult>
{
	public int QuestionID { get; set; }
}

Setup the VoteForQuestionCommand Request-Handler

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();
	}
}

Setup Minimal API in Program.cs File

Clean Up example code

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);
}

Register MediatR Handlers

  • Add using MediatR;
Add the MediatR Registration after builder.Services.AddSwaggerGen()
builder.Services.AddSwaggerGen();
// Register MediatR
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>());

Add Maps for Queries and Commands

  • 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 Unittests for RequestHandlers

  • 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;

Implement tests

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);
}