diff --git a/Directory.Packages.props b/Directory.Packages.props
index b7ca5fa..8a3520a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -16,8 +16,7 @@
-
-
+
diff --git a/README.md b/README.md
index a1db4f9..46fee29 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,8 @@ This project repository is created based on [Clean Architecture solution templat
- [ASP.NET API with .NET 8](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-8.0)
- CQRS with [MediatR](https://github.com/jbogard/MediatR)
- [FluentValidation](https://fluentvalidation.net/)
-- [AutoMapper](https://automapper.org/)
- [Entity Framework Core 8](https://docs.microsoft.com/en-us/ef/core/)
-- [NUnit](https://nunit.org/), [FluentAssertions](https://fluentassertions.com/), [Moq](https://github.com/moq)
+- [xUnit](https://xunit.net/), [FluentAssertions](https://fluentassertions.com/), [Moq](https://github.com/moq)
Afterwards, the projects and architecture is refactored towards the Vertical slice architecture style.
diff --git a/src/Application/ApiPaths.cs b/src/Application/ApiPaths.cs
new file mode 100644
index 0000000..7d41de9
--- /dev/null
+++ b/src/Application/ApiPaths.cs
@@ -0,0 +1,6 @@
+namespace Application;
+
+internal static class ApiPaths
+{
+ internal const string Root = "api";
+}
\ No newline at end of file
diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj
index 8920bc6..adbc22a 100644
--- a/src/Application/Application.csproj
+++ b/src/Application/Application.csproj
@@ -4,7 +4,6 @@
-
diff --git a/src/Application/Common/Mappings/IMapFrom.cs b/src/Application/Common/Mappings/IMapFrom.cs
deleted file mode 100644
index 79a20ac..0000000
--- a/src/Application/Common/Mappings/IMapFrom.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using AutoMapper;
-
-namespace VerticalSliceArchitecture.Application.Common.Mappings;
-
-public interface IMapFrom
-{
- void Mapping(Profile profile)
- {
- profile.CreateMap(typeof(T), GetType());
- }
-}
\ No newline at end of file
diff --git a/src/Application/Common/Mappings/MappingExtensions.cs b/src/Application/Common/Mappings/MappingExtensions.cs
index 39d406b..a2337e7 100644
--- a/src/Application/Common/Mappings/MappingExtensions.cs
+++ b/src/Application/Common/Mappings/MappingExtensions.cs
@@ -1,9 +1,4 @@
-using AutoMapper;
-using AutoMapper.QueryableExtensions;
-
-using Microsoft.EntityFrameworkCore;
-
-using VerticalSliceArchitecture.Application.Common.Models;
+using VerticalSliceArchitecture.Application.Common.Models;
namespace VerticalSliceArchitecture.Application.Common.Mappings;
@@ -13,9 +8,4 @@ public static Task> PaginatedListAsync
{
return PaginatedList.CreateAsync(queryable, pageNumber, pageSize);
}
-
- public static Task> ProjectToListAsync(this IQueryable queryable, IConfigurationProvider configuration)
- {
- return queryable.ProjectTo(configuration).ToListAsync();
- }
}
\ No newline at end of file
diff --git a/src/Application/Common/Mappings/MappingProfile.cs b/src/Application/Common/Mappings/MappingProfile.cs
deleted file mode 100644
index 99cbfb0..0000000
--- a/src/Application/Common/Mappings/MappingProfile.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using System.Reflection;
-
-using AutoMapper;
-
-namespace VerticalSliceArchitecture.Application.Common.Mappings;
-
-public class MappingProfile : Profile
-{
- public MappingProfile()
- {
- ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
- }
-
- private void ApplyMappingsFromAssembly(Assembly assembly)
- {
- var types = assembly.GetExportedTypes()
- .Where(t => t.GetInterfaces().Any(i =>
- i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
- .ToList();
-
- foreach (var type in types)
- {
- var instance = Activator.CreateInstance(type);
-
- var methodInfo = type.GetMethod("Mapping")
- ?? type.GetInterface("IMapFrom`1")!.GetMethod("Mapping");
-
- methodInfo?.Invoke(instance, new object[] { this });
- }
- }
-}
\ No newline at end of file
diff --git a/src/Application/ConfigureServices.cs b/src/Application/ConfigureServices.cs
index 8088e93..cda66c7 100644
--- a/src/Application/ConfigureServices.cs
+++ b/src/Application/ConfigureServices.cs
@@ -16,7 +16,6 @@ public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
- services.AddAutoMapper(typeof(DependencyInjection).Assembly);
services.AddValidatorsFromAssembly(typeof(DependencyInjection).Assembly);
services.AddMediatR(options =>
diff --git a/src/Application/Domain/Todos/TodoItem.cs b/src/Application/Domain/Todos/TodoItem.cs
index 50d9252..829201b 100644
--- a/src/Application/Domain/Todos/TodoItem.cs
+++ b/src/Application/Domain/Todos/TodoItem.cs
@@ -37,16 +37,6 @@ public bool Done
public List DomainEvents { get; } = new List();
}
-public class TodoItemCompletedEvent : DomainEvent
-{
- public TodoItemCompletedEvent(TodoItem item)
- {
- Item = item;
- }
-
- public TodoItem Item { get; }
-}
-
public enum PriorityLevel
{
None = 0,
@@ -55,9 +45,4 @@ public enum PriorityLevel
High = 3,
}
-public class TodoItemRecord : IMapFrom
-{
- public string? Title { get; set; }
-
- public bool Done { get; set; }
-}
\ No newline at end of file
+public record TodoItemRecord(string? Title, bool Done);
diff --git a/src/Application/Domain/Todos/TodoItemCompletedEvent.cs b/src/Application/Domain/Todos/TodoItemCompletedEvent.cs
new file mode 100644
index 0000000..55adeb8
--- /dev/null
+++ b/src/Application/Domain/Todos/TodoItemCompletedEvent.cs
@@ -0,0 +1,8 @@
+using VerticalSliceArchitecture.Application.Common;
+
+namespace VerticalSliceArchitecture.Application.Domain.Todos;
+
+internal sealed class TodoItemCompletedEvent(TodoItem item) : DomainEvent
+{
+ public TodoItem Item { get; } = item;
+}
\ No newline at end of file
diff --git a/src/Application/Domain/Todos/TodoItemCreatedEvent.cs b/src/Application/Domain/Todos/TodoItemCreatedEvent.cs
new file mode 100644
index 0000000..13f33ff
--- /dev/null
+++ b/src/Application/Domain/Todos/TodoItemCreatedEvent.cs
@@ -0,0 +1,8 @@
+using VerticalSliceArchitecture.Application.Common;
+
+namespace VerticalSliceArchitecture.Application.Domain.Todos;
+
+internal sealed class TodoItemCreatedEvent(TodoItem item) : DomainEvent
+{
+ public TodoItem Item { get; } = item;
+}
\ No newline at end of file
diff --git a/src/Application/Domain/Todos/TodoItemDeletedEvent.cs b/src/Application/Domain/Todos/TodoItemDeletedEvent.cs
new file mode 100644
index 0000000..6f2dff5
--- /dev/null
+++ b/src/Application/Domain/Todos/TodoItemDeletedEvent.cs
@@ -0,0 +1,8 @@
+using VerticalSliceArchitecture.Application.Common;
+
+namespace VerticalSliceArchitecture.Application.Domain.Todos;
+
+internal sealed class TodoItemDeletedEvent(TodoItem item) : DomainEvent
+{
+ public TodoItem Item { get; } = item;
+}
\ No newline at end of file
diff --git a/src/Application/Features/TodoItems/CreateTodoItem.cs b/src/Application/Features/TodoItems/CreateTodoItem.cs
index 620b0be..6238a0f 100644
--- a/src/Application/Features/TodoItems/CreateTodoItem.cs
+++ b/src/Application/Features/TodoItems/CreateTodoItem.cs
@@ -19,14 +19,9 @@ public async Task> Create(CreateTodoItemCommand command)
}
}
-public class CreateTodoItemCommand : IRequest
-{
- public int ListId { get; set; }
-
- public string? Title { get; set; }
-}
+public record CreateTodoItemCommand(int ListId, string? Title) : IRequest;
-public class CreateTodoItemCommandValidator : AbstractValidator
+internal sealed class CreateTodoItemCommandValidator : AbstractValidator
{
public CreateTodoItemCommandValidator()
{
@@ -36,14 +31,9 @@ public CreateTodoItemCommandValidator()
}
}
-internal sealed class CreateTodoItemCommandHandler : IRequestHandler
+internal sealed class CreateTodoItemCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public CreateTodoItemCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
{
@@ -62,14 +52,4 @@ public async Task Handle(CreateTodoItemCommand request, CancellationToken c
return entity.Id;
}
-}
-
-internal sealed class TodoItemCreatedEvent : DomainEvent
-{
- public TodoItemCreatedEvent(TodoItem item)
- {
- Item = item;
- }
-
- public TodoItem Item { get; }
}
\ No newline at end of file
diff --git a/src/Application/Features/TodoItems/DeleteTodoItem.cs b/src/Application/Features/TodoItems/DeleteTodoItem.cs
index 3be9178..b9acfad 100644
--- a/src/Application/Features/TodoItems/DeleteTodoItem.cs
+++ b/src/Application/Features/TodoItems/DeleteTodoItem.cs
@@ -14,25 +14,17 @@ public class DeleteTodoItemController : ApiControllerBase
[HttpDelete("/api/todo-items/{id}")]
public async Task Delete(int id)
{
- await Mediator.Send(new DeleteTodoItemCommand { Id = id });
+ await Mediator.Send(new DeleteTodoItemCommand(id));
return NoContent();
}
}
-public class DeleteTodoItemCommand : IRequest
-{
- public int Id { get; set; }
-}
+public record DeleteTodoItemCommand(int Id) : IRequest;
-internal sealed class DeleteTodoItemCommandHandler : IRequestHandler
+internal sealed class DeleteTodoItemCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public DeleteTodoItemCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
{
@@ -44,14 +36,4 @@ public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancel
await _context.SaveChangesAsync(cancellationToken);
}
-}
-
-internal class TodoItemDeletedEvent : DomainEvent
-{
- public TodoItemDeletedEvent(TodoItem item)
- {
- Item = item;
- }
-
- public TodoItem Item { get; }
}
\ No newline at end of file
diff --git a/src/Application/Features/TodoItems/EventHandlers/TodoItemCompletedEventHandler.cs b/src/Application/Features/TodoItems/EventHandlers/TodoItemCompletedEventHandler.cs
index 491870c..b84b496 100644
--- a/src/Application/Features/TodoItems/EventHandlers/TodoItemCompletedEventHandler.cs
+++ b/src/Application/Features/TodoItems/EventHandlers/TodoItemCompletedEventHandler.cs
@@ -7,14 +7,9 @@
namespace VerticalSliceArchitecture.Application.Features.TodoItems.EventHandlers;
-public class TodoItemCompletedEventHandler : INotificationHandler>
+internal sealed class TodoItemCompletedEventHandler(ILogger logger) : INotificationHandler>
{
- private readonly ILogger _logger;
-
- public TodoItemCompletedEventHandler(ILogger logger)
- {
- _logger = logger;
- }
+ private readonly ILogger _logger = logger;
public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken)
{
diff --git a/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs b/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs
index 8ce1383..90d4db7 100644
--- a/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs
+++ b/src/Application/Features/TodoItems/EventHandlers/TodoItemCreatedEventHandler.cs
@@ -3,17 +3,13 @@
using Microsoft.Extensions.Logging;
using VerticalSliceArchitecture.Application.Common.Models;
+using VerticalSliceArchitecture.Application.Domain.Todos;
namespace VerticalSliceArchitecture.Application.Features.TodoItems.EventHandlers;
-internal sealed class TodoItemCreatedEventHandler : INotificationHandler>
+internal sealed class TodoItemCreatedEventHandler(ILogger logger) : INotificationHandler>
{
- private readonly ILogger _logger;
-
- public TodoItemCreatedEventHandler(ILogger logger)
- {
- _logger = logger;
- }
+ private readonly ILogger _logger = logger;
public Task Handle(DomainEventNotification notification, CancellationToken cancellationToken)
{
diff --git a/src/Application/Features/TodoItems/GetTodoItemsWithPagination.cs b/src/Application/Features/TodoItems/GetTodoItemsWithPagination.cs
index 59ebee9..89f9ec2 100644
--- a/src/Application/Features/TodoItems/GetTodoItemsWithPagination.cs
+++ b/src/Application/Features/TodoItems/GetTodoItemsWithPagination.cs
@@ -1,7 +1,4 @@
-using AutoMapper;
-using AutoMapper.QueryableExtensions;
-
-using FluentValidation;
+using FluentValidation;
using MediatR;
@@ -18,31 +15,17 @@ namespace VerticalSliceArchitecture.Application.Features.TodoItems;
public class GetTodoItemsWithPaginationController : ApiControllerBase
{
[HttpGet("/api/todo-items")]
- public Task> GetTodoItemsWithPagination([FromQuery] GetTodoItemsWithPaginationQuery query)
+ public Task> GetTodoItemsWithPagination([FromQuery] GetTodoItemsWithPaginationQuery query)
{
return Mediator.Send(query);
}
}
-public class TodoItemBriefDto : IMapFrom
-{
- public int Id { get; set; }
-
- public int ListId { get; set; }
+public record TodoItemBriefResponse(int Id, int ListId, string? Title, bool Done);
- public string? Title { get; set; }
-
- public bool Done { get; set; }
-}
+public record GetTodoItemsWithPaginationQuery(int ListId, int PageNumber = 1, int PageSize = 10) : IRequest>;
-public class GetTodoItemsWithPaginationQuery : IRequest>
-{
- public int ListId { get; set; }
- public int PageNumber { get; set; } = 1;
- public int PageSize { get; set; } = 10;
-}
-
-public class GetTodoItemsWithPaginationQueryValidator : AbstractValidator
+internal sealed class GetTodoItemsWithPaginationQueryValidator : AbstractValidator
{
public GetTodoItemsWithPaginationQueryValidator()
{
@@ -57,23 +40,23 @@ public GetTodoItemsWithPaginationQueryValidator()
}
}
-internal sealed class GetTodoItemsWithPaginationQueryHandler : IRequestHandler>
+internal sealed class GetTodoItemsWithPaginationQueryHandler(ApplicationDbContext context) : IRequestHandler>
{
- private readonly ApplicationDbContext _context;
- private readonly IMapper _mapper;
+ private readonly ApplicationDbContext _context = context;
- public GetTodoItemsWithPaginationQueryHandler(ApplicationDbContext context, IMapper mapper)
- {
- _context = context;
- _mapper = mapper;
- }
-
- public Task> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
+ public Task> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
return _context.TodoItems
- .Where(x => x.ListId == request.ListId)
- .OrderBy(x => x.Title)
- .ProjectTo(_mapper.ConfigurationProvider)
+ .Where(item => item.ListId == request.ListId)
+ .OrderBy(item => item.Title)
+ .Select(item => ToDto(item))
.PaginatedListAsync(request.PageNumber, request.PageSize);
}
-}
\ No newline at end of file
+
+ private static TodoItemBriefResponse ToDto(TodoItem todoItem) =>
+ new(todoItem.Id, todoItem.ListId, todoItem.Title, todoItem.Done);
+}
+
+
+
+
diff --git a/src/Application/Features/TodoItems/UpdateTodoItem.cs b/src/Application/Features/TodoItems/UpdateTodoItem.cs
index c21ed03..d9585bb 100644
--- a/src/Application/Features/TodoItems/UpdateTodoItem.cs
+++ b/src/Application/Features/TodoItems/UpdateTodoItem.cs
@@ -27,16 +27,9 @@ public async Task Update(int id, UpdateTodoItemCommand command)
}
}
-public class UpdateTodoItemCommand : IRequest
-{
- public int Id { get; set; }
-
- public string? Title { get; set; }
-
- public bool Done { get; set; }
-}
+public record UpdateTodoItemCommand(int Id, string? Title, bool Done) : IRequest;
-public class UpdateTodoItemCommandValidator : AbstractValidator
+internal sealed class UpdateTodoItemCommandValidator : AbstractValidator
{
public UpdateTodoItemCommandValidator()
{
@@ -46,22 +39,17 @@ public UpdateTodoItemCommandValidator()
}
}
-internal sealed class UpdateTodoItemCommandHandler : IRequestHandler
+internal sealed class UpdateTodoItemCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public UpdateTodoItemCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
{
- var entity = await _context.TodoItems
+ var todoItem = await _context.TodoItems
.FindAsync(new object[] { request.Id }, cancellationToken) ?? throw new NotFoundException(nameof(TodoItem), request.Id);
- entity.Title = request.Title;
- entity.Done = request.Done;
+ todoItem.Title = request.Title;
+ todoItem.Done = request.Done;
await _context.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Application/Features/TodoItems/UpdateTodoItemDetail.cs b/src/Application/Features/TodoItems/UpdateTodoItemDetail.cs
index 9f6d5d0..6168f3a 100644
--- a/src/Application/Features/TodoItems/UpdateTodoItemDetail.cs
+++ b/src/Application/Features/TodoItems/UpdateTodoItemDetail.cs
@@ -25,34 +25,20 @@ public async Task UpdateItemDetails(int id, UpdateTodoItemDetailCo
}
}
-public class UpdateTodoItemDetailCommand : IRequest
-{
- public int Id { get; set; }
-
- public int ListId { get; set; }
-
- public PriorityLevel Priority { get; set; }
-
- public string? Note { get; set; }
-}
+public record UpdateTodoItemDetailCommand(int Id, int ListId, PriorityLevel Priority, string? Note) : IRequest;
-public class UpdateTodoItemDetailCommandHandler : IRequestHandler
+internal sealed class UpdateTodoItemDetailCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public UpdateTodoItemDetailCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(UpdateTodoItemDetailCommand request, CancellationToken cancellationToken)
{
- var entity = await _context.TodoItems
+ var todoItem = await _context.TodoItems
.FindAsync(new object[] { request.Id }, cancellationToken) ?? throw new NotFoundException(nameof(TodoItem), request.Id);
- entity.ListId = request.ListId;
- entity.Priority = request.Priority;
- entity.Note = request.Note;
+ todoItem.ListId = request.ListId;
+ todoItem.Priority = request.Priority;
+ todoItem.Note = request.Note;
await _context.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Application/Features/TodoLists/CreateTodoList.cs b/src/Application/Features/TodoLists/CreateTodoList.cs
index 0dd2f67..2f456e0 100644
--- a/src/Application/Features/TodoLists/CreateTodoList.cs
+++ b/src/Application/Features/TodoLists/CreateTodoList.cs
@@ -20,12 +20,9 @@ public async Task> Create(CreateTodoListCommand command)
}
}
-public class CreateTodoListCommand : IRequest
-{
- public string? Title { get; set; }
-}
+public record CreateTodoListCommand(string? Title) : IRequest;
-public class CreateTodoListCommandValidator : AbstractValidator
+internal sealed class CreateTodoListCommandValidator : AbstractValidator
{
private readonly ApplicationDbContext _context;
@@ -46,23 +43,18 @@ private Task BeUniqueTitle(string title, CancellationToken cancellationTok
}
}
-internal sealed class CreateTodoListCommandHandler : IRequestHandler
+internal sealed class CreateTodoListCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public CreateTodoListCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(CreateTodoListCommand request, CancellationToken cancellationToken)
{
- var entity = new TodoList { Title = request.Title };
+ var todoList = new TodoList { Title = request.Title };
- _context.TodoLists.Add(entity);
+ _context.TodoLists.Add(todoList);
await _context.SaveChangesAsync(cancellationToken);
- return entity.Id;
+ return todoList.Id;
}
}
\ No newline at end of file
diff --git a/src/Application/Features/TodoLists/DeleteTodoList.cs b/src/Application/Features/TodoLists/DeleteTodoList.cs
index 546b3c3..9d17202 100644
--- a/src/Application/Features/TodoLists/DeleteTodoList.cs
+++ b/src/Application/Features/TodoLists/DeleteTodoList.cs
@@ -15,33 +15,25 @@ public class DeleteTodoListController : ApiControllerBase
[HttpDelete("/api/todo-lists/{id}")]
public async Task Delete(int id)
{
- await Mediator.Send(new DeleteTodoListCommand { Id = id });
+ await Mediator.Send(new DeleteTodoListCommand(id));
return NoContent();
}
}
-public class DeleteTodoListCommand : IRequest
-{
- public int Id { get; set; }
-}
+public record DeleteTodoListCommand(int Id) : IRequest;
-internal sealed class DeleteTodoListCommandHandler : IRequestHandler
+internal sealed class DeleteTodoListCommandHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
-
- public DeleteTodoListCommandHandler(ApplicationDbContext context)
- {
- _context = context;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(DeleteTodoListCommand request, CancellationToken cancellationToken)
{
- var entity = await _context.TodoLists
+ var todoList = await _context.TodoLists
.Where(l => l.Id == request.Id)
.SingleOrDefaultAsync(cancellationToken) ?? throw new NotFoundException(nameof(TodoList), request.Id);
- _context.TodoLists.Remove(entity);
+ _context.TodoLists.Remove(todoList);
await _context.SaveChangesAsync(cancellationToken);
}
diff --git a/src/Application/Features/TodoLists/ExportTodos.cs b/src/Application/Features/TodoLists/ExportTodos.cs
index da080c9..3e4be29 100644
--- a/src/Application/Features/TodoLists/ExportTodos.cs
+++ b/src/Application/Features/TodoLists/ExportTodos.cs
@@ -1,7 +1,4 @@
-using AutoMapper;
-using AutoMapper.QueryableExtensions;
-
-using MediatR;
+using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -18,51 +15,26 @@ public class ExportTodosController : ApiControllerBase
[HttpGet("/api/todo-lists/{id}")]
public async Task Get(int id)
{
- var vm = await Mediator.Send(new ExportTodosQuery { ListId = id });
+ var vm = await Mediator.Send(new ExportTodosQuery(id));
return File(vm.Content, vm.ContentType, vm.FileName);
}
}
-public class ExportTodosQuery : IRequest
-{
- public int ListId { get; set; }
-}
-
-public class ExportTodosVm
-{
- public ExportTodosVm(string fileName, string contentType, byte[] content)
- {
- FileName = fileName;
- ContentType = contentType;
- Content = content;
- }
+public record ExportTodosQuery(int ListId) : IRequest;
- public string FileName { get; set; }
+public record ExportTodosVm(string FileName, string ContentType, byte[] Content);
- public string ContentType { get; set; }
-
- public byte[] Content { get; set; }
-}
-
-internal sealed class ExportTodosQueryHandler : IRequestHandler
+internal sealed class ExportTodosQueryHandler(ApplicationDbContext context, ICsvFileBuilder fileBuilder) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
- private readonly IMapper _mapper;
- private readonly ICsvFileBuilder _fileBuilder;
-
- public ExportTodosQueryHandler(ApplicationDbContext context, IMapper mapper, ICsvFileBuilder fileBuilder)
- {
- _context = context;
- _mapper = mapper;
- _fileBuilder = fileBuilder;
- }
+ private readonly ApplicationDbContext _context = context;
+ private readonly ICsvFileBuilder _fileBuilder = fileBuilder;
public async Task Handle(ExportTodosQuery request, CancellationToken cancellationToken)
{
var records = await _context.TodoItems
.Where(t => t.ListId == request.ListId)
- .ProjectTo(_mapper.ConfigurationProvider)
+ .Select(item => new TodoItemRecord(item.Title, item.Done))
.ToListAsync(cancellationToken);
var vm = new ExportTodosVm(
diff --git a/src/Application/Features/TodoLists/GetTodos.cs b/src/Application/Features/TodoLists/GetTodos.cs
index 42a13af..a30b74f 100644
--- a/src/Application/Features/TodoLists/GetTodos.cs
+++ b/src/Application/Features/TodoLists/GetTodos.cs
@@ -1,13 +1,9 @@
-using AutoMapper;
-using AutoMapper.QueryableExtensions;
-
-using MediatR;
+using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using VerticalSliceArchitecture.Application.Common;
-using VerticalSliceArchitecture.Application.Common.Mappings;
using VerticalSliceArchitecture.Application.Domain.Todos;
using VerticalSliceArchitecture.Application.Infrastructure.Persistence;
@@ -22,9 +18,7 @@ public async Task> Get()
}
}
-public class GetTodosQuery : IRequest
-{
-}
+public record GetTodosQuery : IRequest;
public class TodosVm
{
@@ -33,60 +27,21 @@ public class TodosVm
public IList Lists { get; set; } = new List();
}
-public class PriorityLevelDto
-{
- public int Value { get; set; }
+public record PriorityLevelDto(int Value, string? Name);
- public string? Name { get; set; }
-}
-
-public class TodoListDto : IMapFrom
+public record TodoListDto(int Id, string? Title, string? Colour, IList Items)
{
public TodoListDto()
+ : this(default, null, null, new List())
{
- Items = new List();
}
-
- public int Id { get; set; }
-
- public string? Title { get; set; }
-
- public string? Colour { get; set; }
-
- public IList Items { get; set; }
}
-public class TodoItemDto : IMapFrom
-{
- public int Id { get; set; }
-
- public int ListId { get; set; }
-
- public string? Title { get; set; }
-
- public bool Done { get; set; }
-
- public int Priority { get; set; }
-
- public string? Note { get; set; }
-
- public void Mapping(Profile profile)
- {
- profile.CreateMap()
- .ForMember(d => d.Priority, opt => opt.MapFrom(s => (int)s.Priority));
- }
-}
+public record TodoItemDto(int Id, int ListId, string? Title, bool Done, int Priority, string? Note);
-internal sealed class GetTodosQueryHandler : IRequestHandler
+internal sealed class GetTodosQueryHandler(ApplicationDbContext context) : IRequestHandler
{
- private readonly ApplicationDbContext _context;
- private readonly IMapper _mapper;
-
- public GetTodosQueryHandler(ApplicationDbContext context, IMapper mapper)
- {
- _context = context;
- _mapper = mapper;
- }
+ private readonly ApplicationDbContext _context = context;
public async Task Handle(GetTodosQuery request, CancellationToken cancellationToken)
{
@@ -94,14 +49,36 @@ public async Task Handle(GetTodosQuery request, CancellationToken cance
{
PriorityLevels = Enum.GetValues(typeof(PriorityLevel))
.Cast()
- .Select(p => new PriorityLevelDto { Value = (int)p, Name = p.ToString() })
+ .Select(p => new PriorityLevelDto((int)p, p.ToString()))
.ToList(),
Lists = await _context.TodoLists
.AsNoTracking()
- .ProjectTo(_mapper.ConfigurationProvider)
.OrderBy(t => t.Title)
+ .Select(todoListItem => ToDto(todoListItem))
.ToListAsync(cancellationToken),
};
}
+
+ private static TodoItemDto ToDto(TodoItem todoItem)
+ {
+ var todoItemDto = new TodoItemDto(todoItem.Id, todoItem.ListId, todoItem.Title, todoItem.Done, (int)todoItem.Priority, todoItem.Note);
+
+ return todoItemDto;
+ }
+
+ private static TodoListDto ToDto(TodoList todoList)
+ {
+ var todoListDto = new TodoListDto
+ {
+ Id = todoList.Id,
+ Title = todoList.Title,
+ Colour = todoList.Colour,
+ Items = todoList.Items
+ .Select(item => ToDto(item))
+ .ToList(),
+ };
+
+ return todoListDto;
+ }
}
\ No newline at end of file
diff --git a/src/Application/Infrastructure/Files/CsvFileBuilder.cs b/src/Application/Infrastructure/Files/CsvFileBuilder.cs
index 5dbcea6..d423695 100644
--- a/src/Application/Infrastructure/Files/CsvFileBuilder.cs
+++ b/src/Application/Infrastructure/Files/CsvFileBuilder.cs
@@ -4,7 +4,6 @@
using VerticalSliceArchitecture.Application.Common.Interfaces;
using VerticalSliceArchitecture.Application.Domain.Todos;
-using VerticalSliceArchitecture.Application.Infrastructure.Files.Maps;
namespace VerticalSliceArchitecture.Application.Infrastructure.Files;
diff --git a/src/Application/Infrastructure/Files/Maps/TodoItemRecordMap.cs b/src/Application/Infrastructure/Files/TodoItemRecordMap.cs
similarity index 93%
rename from src/Application/Infrastructure/Files/Maps/TodoItemRecordMap.cs
rename to src/Application/Infrastructure/Files/TodoItemRecordMap.cs
index d098454..5c2d2d2 100644
--- a/src/Application/Infrastructure/Files/Maps/TodoItemRecordMap.cs
+++ b/src/Application/Infrastructure/Files/TodoItemRecordMap.cs
@@ -4,7 +4,7 @@
using VerticalSliceArchitecture.Application.Domain.Todos;
-namespace VerticalSliceArchitecture.Application.Infrastructure.Files.Maps;
+namespace VerticalSliceArchitecture.Application.Infrastructure.Files;
public class TodoItemRecordMap : ClassMap
{
diff --git a/tests/Application.UnitTests/Common/Behaviours/RequestLoggerTests.cs b/tests/Application.UnitTests/Common/Behaviours/RequestLoggerTests.cs
index 19518df..130b4d4 100644
--- a/tests/Application.UnitTests/Common/Behaviours/RequestLoggerTests.cs
+++ b/tests/Application.UnitTests/Common/Behaviours/RequestLoggerTests.cs
@@ -26,7 +26,7 @@ public async Task ShouldCallGetUserNameAsyncOnceIfAuthenticated()
var requestLogger = new LoggingBehaviour(_logger.Object, _currentUserService.Object);
- await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, CancellationToken.None);
+ await requestLogger.Process(new CreateTodoItemCommand(1, "title"), CancellationToken.None);
}
[Fact]
@@ -34,6 +34,6 @@ public async Task ShouldNotCallGetUserNameAsyncOnceIfUnauthenticated()
{
var requestLogger = new LoggingBehaviour(_logger.Object, _currentUserService.Object);
- await requestLogger.Process(new CreateTodoItemCommand { ListId = 1, Title = "title" }, CancellationToken.None);
+ await requestLogger.Process(new CreateTodoItemCommand(1, "title"), CancellationToken.None);
}
}
\ No newline at end of file
diff --git a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs b/tests/Application.UnitTests/Common/Mappings/MappingTests.cs
deleted file mode 100644
index af0918c..0000000
--- a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using AutoMapper;
-
-using VerticalSliceArchitecture.Application.Common.Mappings;
-using VerticalSliceArchitecture.Application.Domain.Todos;
-using VerticalSliceArchitecture.Application.Features.TodoLists;
-
-namespace VerticalSliceArchitecture.Application.UnitTests.Common.Mappings;
-
-public class MappingTests
-{
- private readonly IConfigurationProvider _configuration;
- private readonly IMapper _mapper;
-
- public MappingTests()
- {
- _configuration = new MapperConfiguration(config =>
- config.AddProfile());
-
- _mapper = _configuration.CreateMapper();
- }
-
- [Fact]
- public void ShouldHaveValidConfiguration()
- {
- _configuration.AssertConfigurationIsValid();
- }
-
- [Theory]
- [InlineData(typeof(TodoList), typeof(TodoListDto))]
- [InlineData(typeof(TodoItem), typeof(TodoItemDto))]
- public void ShouldSupportMappingFromSourceToDestination(Type source, Type destination)
- {
- var instance = GetInstanceOf(source);
-
- _mapper.Map(instance, source, destination);
- }
-
- private static object GetInstanceOf(Type type)
- {
- return Activator.CreateInstance(type)!;
- }
-}
\ No newline at end of file