Introduction
Welcome back to the "What is Clean Architecture" series! We've covered a lot of ground so far, exploring essential architectural principles and setting up our projects. Now, it's time to bring all of these concepts to life by implementing the actual business logic within our request handlers.
In this part, we'll walk through integrating MediatR and AutoMapper into our application, creating request and request handler classes, and finally, preparing everything to be registered in the IServiceCollection
. This is where the rubber meets the road—transforming contracts into meaningful implementation code.
If you're not familiar with MediatR or AutoMapper or CQRS, I highly recommend checking out my previous articles on these topics:
- Understanding the Mediator Pattern in .NET
- AutoMapper in .NET: When to Use It and When to Avoid It
- Enhancing .NET Applications with CQRS and SOLID Principles
With that foundation in place, let's dive into the implementation!
Step 1: Setting Up the Project Structure
Adding Feature Folders
To maintain a clean and organized project structure, we'll start by creating feature-specific folders under the Application layer. This helps in grouping related classes together, making the project more maintainable.
-
Create a folder named
Features
under the Application layer.- This folder will contain all the business logic related to different features in the application.
-
Under the
Features
folder, create another folder namedEvents
.- The
Events
folder will encapsulate all the logic related to the event management feature.
- The
-
Within the
Events
folder, create a subfolder namedQueries
.- This folder will contain all the query logic related to events.
Here's how the folder structure should look so far:
Step 2: Creating Query Folders
Within the Queries
folder, we'll create specific folders for each query:
-
Create a folder named
GetEventDetail
underQueries
.- This folder will contain the query and handler classes for fetching event details.
-
Create another folder named
GetEventsList
underQueries
.- This folder will contain the query and handler classes for fetching a list of events.
Your Queries
folder structure should now look like this:
Step 3: Creating the Mapping Profile
Next, we'll create a folder for our AutoMapper profiles:
-
Create a folder named
Profiles
under the Application layer.- This folder will contain all AutoMapper profiles that define the mappings between domain entities and ViewModels.
-
Create a class named
MappingProfile.cs
under theProfiles
folder.- This class will define the mappings needed for our application.
Here's how the folder structure with the MappingProfile should look:
Step 4: Implementing the Query and Handler Classes
Now that the folder structure is in place, let's create the query and handler classes:
-
In the
GetEventDetail
folder, create the following classes:-
GetEventDetailQuery.cs
: This class will define the query for fetching event details. -
GetEventDetailQueryHandler.cs
: This class will handle the logic for theGetEventDetailQuery
. -
EventDetailVm.cs
: This class will act as the ViewModel for event details. -
CategoryDto.cs
: This DTO will represent the category information withinEventDetailVm
.
-
-
In the
GetEventsList
folder, create the following classes:-
GetEventsListQuery.cs
: This class will define the query for fetching a list of events. -
GetEventsListQueryHandler.cs
: This class will handle the logic for theGetEventsListQuery
. -
EventListVm.cs
: This class will act as the ViewModel for the list of events.
-
Here’s how the structure should look with all classes in place:
Step 5: Adding Package References
With the folder and class structure in place, let's add the necessary packages:
-
Add the MediatR and AutoMapper packages to your project via NuGet. You can do this via the NuGet Package Manager or by adding them directly to your
.csproj
file:
<ItemGroup>
<PackageReference Include="MediatR" Version="12.3.0" />
<PackageReference Include="AutoMapper" Version="12.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
</ItemGroup>
Here’s how the package references should look in your project file:
Step 6: Implementing the Request Handler Logic
Now that our project structure and dependencies are set up, let's implement the business logic.
GetEventsListQuery and Handler
The GetEventsListQuery
class represents a request to retrieve a list of events. Here’s how it looks:
//EventListVm
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventsList
{
public class EventListVm
{
public Guid EventId { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime Date { get; set; }
public string ImageUrl { get; set; } = string.Empty;
}
}
// GetEventsListQuery.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries
{
public class GetEventsListQuery:IRequest<List<EventListVm>>
{
}
}
And here’s the handler that processes this query:
// GetEventsListQueryHandler.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries
{
public class GetEventsListQueryHandler : IRequestHandler<GetEventsListQuery, List<EventListVm>>
{
private readonly IAsyncRepository<Event> _eventRepository;
private readonly IMapper _mapper;
public GetEventsListQueryHandler(IMapper mapper, IAsyncRepository<Event> eventRepository)
{
_mapper = mapper;
_eventRepository = eventRepository;
}
public async Task<List<EventListVm>> Handle(GetEventsListQuery request, CancellationToken cancellationToken)
{
var allEvents = (await _eventRepository.ListAllAsync()).OrderBy(x => x.Date);
return _mapper.Map<List<EventListVm>>(allEvents);
}
}
}
GetEventDetailQuery and Handler
Similarly, here’s the GetEventDetailQuery
class for fetching event details:
//CategoryDto.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventDetail
{
public class CategoryDto
{
public Guid CategoryId { get; set; }
public string Name { get; set; }
}
}
//EventDetailVm.cs
namespace GloboTicket.TicketManagement.Application.Features.Events.Queries.GetEventDetail
{
public class EventDetailVm
{
public Guid EventId { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public string Artist { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
public string ImageUrl { get; set; }
public Guid CategoryId { get; set; }
public CategoryDto Category { get; set; }
}
}
// GetEventDetailQuery.cs
public class GetEventDetailQuery : IRequest<EventDetailVm>
{
public Guid Id { get; set; }
}
And the handler for processing this query:
// GetEventDetailQueryHandler.cs
public class GetEventDetailQueryHandler : IRequestHandler<GetEventDetailQuery, EventDetailVm>
{
private readonly IAsyncRepository<Event> _eventRepository;
private readonly IAsyncRepository<Category> _categoryRepository;
private readonly IMapper _mapper;
public GetEventDetailQueryHandler(IAsyncRepository<Event> eventRepository, IAsyncRepository<Category> categoryRepository, IMapper mapper)
{
_eventRepository = eventRepository;
_categoryRepository = categoryRepository;
_mapper = mapper;
}
public async Task<EventDetailVm> Handle(GetEventDetailQuery request, CancellationToken cancellationToken)
{
var eventEntity = await _eventRepository.GetByIdAsync(request.Id);
var eventDetailVm = _mapper.Map<EventDetailVm>(eventEntity);
var categoryEntity = await _categoryRepository.GetByIdAsync(eventEntity.CategoryId);
eventDetailVm.Category = _mapper.Map<CategoryDto>(categoryEntity);
return eventDetailVm;
}
}
Step 7: Configuring AutoMapper Profiles
Finally, we need to configure AutoMapper to handle the mappings between our entities and ViewModels. Here’s the MappingProfile
class that defines these mappings:
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<Event, EventListVm>().ReverseMap();
CreateMap<Event, EventDetailVm>().ReverseMap();
CreateMap<Category, CategoryDto>().ReverseMap();
}
}
Conclusion
By following these steps, you've successfully organized your project structure, created the necessary queries and handlers, and configured AutoMapper profiles for a clean and maintainable application.
Stay tuned for the next part of this series, where we’ll explore how to integrate and test these components in a real-world application!