├── CleanArchitectureDemo.Application ├── CleanArchitectureDemo.Application.csproj ├── Common │ ├── Exceptions │ │ ├── ApplicationException.cs │ │ ├── BadRequestException.cs │ │ └── NotFoundException.cs │ └── Mappings │ │ ├── IMapFrom.cs │ │ └── MappingProfile.cs ├── DTOs │ └── Email │ │ └── EmailRequestDto.cs ├── Extensions │ ├── IServiceCollectionExtensions.cs │ └── QueryableExtensions.cs ├── Features │ └── Players │ │ ├── Commands │ │ ├── CreatePlayer │ │ │ ├── CreatePlayerCommand.cs │ │ │ └── PlayerCreatedEvent.cs │ │ ├── DeletePlayer │ │ │ ├── DeletePlayerCommand.cs │ │ │ └── PlayerDeletedEvent.cs │ │ └── UpdatePlayer │ │ │ ├── PlayerUpdatedEvent.cs │ │ │ └── UpdatePlayerCommand.cs │ │ └── Queries │ │ ├── GetAllPlayers │ │ ├── GetAllPlayersDto.cs │ │ └── GetAllPlayersQuery.cs │ │ ├── GetPlayerById │ │ ├── GetPlayerByIdDto.cs │ │ └── GetPlayerByIdQuery.cs │ │ ├── GetPlayersByClub │ │ ├── GetPlayersByClubDto.cs │ │ └── GetPlayersByClubQuery.cs │ │ └── GetPlayersWithPagination │ │ ├── GetPlayersWithPaginationDto.cs │ │ ├── GetPlayersWithPaginationQuery.cs │ │ └── GetPlayersWithPaginationValidator.cs └── Interfaces │ ├── IDateTimeService.cs │ ├── IEmailService.cs │ └── Repositories │ ├── IClubRepository.cs │ ├── ICountryRepository.cs │ ├── IGenericRepository.cs │ ├── IPlayerRepository.cs │ ├── IStadiumRepository.cs │ └── IUnitOfWork.cs ├── CleanArchitectureDemo.Domain ├── CleanArchitectureDemo.Domain.csproj ├── Common │ ├── BaseAuditableEntity.cs │ ├── BaseEntity.cs │ ├── BaseEvent.cs │ ├── DomainEventDispatcher.cs │ ├── Interfaces │ │ ├── IAuditableEntity.cs │ │ ├── IDomainEventDispatcher.cs │ │ └── IEntity.cs │ └── ValueObject.cs └── Entities │ ├── Club.cs │ ├── Country.cs │ ├── Player.cs │ └── Stadium.cs ├── CleanArchitectureDemo.Infrastructure ├── CleanArchitectureDemo.Infrastructure.csproj ├── Extensions │ └── IServiceCollectionExtensions.cs └── Services │ ├── DateTimeService.cs │ └── EmailService.cs ├── CleanArchitectureDemo.Persistence ├── CleanArchitectureDemo.Persistence.csproj ├── Contexts │ └── ApplicationDbContext.cs ├── Extensions │ └── IServiceCollectionExtensions.cs └── Repositories │ ├── ClubRepository.cs │ ├── CountryRepository.cs │ ├── GenericRepository.cs │ ├── PlayerRepository.cs │ ├── StadiumRepository.cs │ └── UnitOfWork.cs ├── CleanArchitectureDemo.Shared ├── CleanArchitectureDemo.Shared.csproj ├── Interfaces │ └── IResult.cs ├── PaginatedResult.cs └── Result.cs ├── CleanArchitectureDemo.WebAPI ├── CleanArchitectureDemo.WebAPI.csproj ├── Controllers │ ├── ApiControllerBase.cs │ └── PlayersController.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── CleanArchitectureDemo.sln └── README.md /CleanArchitectureDemo.Application/CleanArchitectureDemo.Application.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Common/Exceptions/ApplicationException.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace CleanArchitectureDemo.Application.Common.Exceptions 4 | { 5 | public class ApplicationException : Exception 6 | { 7 | public ApplicationException() 8 | : base() 9 | { 10 | } 11 | 12 | public ApplicationException(string message) 13 | : base(message) 14 | { 15 | } 16 | 17 | public ApplicationException(string message, Exception innerException) 18 | : base(message, innerException) 19 | { 20 | } 21 | 22 | public ApplicationException(string message, params object[] args) 23 | : base(string.Format(CultureInfo.CurrentCulture, message, args)) 24 | { 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Common/Exceptions/BadRequestException.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Application.Common.Exceptions 2 | { 3 | public class BadRequestException : Exception 4 | { 5 | public string[] Errors { get; set; } 6 | 7 | public BadRequestException() 8 | : base() 9 | { 10 | } 11 | 12 | public BadRequestException(string message) 13 | : base(message) 14 | { 15 | } 16 | 17 | public BadRequestException(string message, Exception innerException) 18 | : base(message, innerException) 19 | { 20 | } 21 | 22 | public BadRequestException(string[] errors) : base("Multiple errors occurred. See error details.") 23 | { 24 | Errors = errors; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Common/Exceptions/NotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Application.Common.Exceptions 2 | { 3 | public class NotFoundException : Exception 4 | { 5 | public NotFoundException() 6 | : base() 7 | { 8 | } 9 | 10 | public NotFoundException(string message) 11 | : base(message) 12 | { 13 | } 14 | 15 | public NotFoundException(string message, Exception innerException) 16 | : base(message, innerException) 17 | { 18 | } 19 | 20 | public NotFoundException(string name, object key) 21 | : base($"Entity \"{name}\" ({key}) was not found.") 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Common/Mappings/IMapFrom.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | namespace CleanArchitectureDemo.Application.Common.Mappings 4 | { 5 | public interface IMapFrom 6 | { 7 | void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Common/Mappings/MappingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | using System.Reflection; 4 | 5 | namespace CleanArchitectureDemo.Application.Common.Mappings 6 | { 7 | public class MappingProfile : Profile 8 | { 9 | public MappingProfile() 10 | { 11 | ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly()); 12 | } 13 | 14 | private void ApplyMappingsFromAssembly(Assembly assembly) 15 | { 16 | var mapFromType = typeof(IMapFrom<>); 17 | 18 | var mappingMethodName = nameof(IMapFrom.Mapping); 19 | 20 | bool HasInterface(Type t) => t.IsGenericType && t.GetGenericTypeDefinition() == mapFromType; 21 | 22 | var types = assembly.GetExportedTypes().Where(t => t.GetInterfaces().Any(HasInterface)).ToList(); 23 | 24 | var argumentTypes = new Type[] { typeof(Profile) }; 25 | 26 | foreach (var type in types) 27 | { 28 | var instance = Activator.CreateInstance(type); 29 | 30 | var methodInfo = type.GetMethod(mappingMethodName); 31 | 32 | if (methodInfo != null) 33 | { 34 | methodInfo.Invoke(instance, new object[] { this }); 35 | } 36 | else 37 | { 38 | var interfaces = type.GetInterfaces().Where(HasInterface).ToList(); 39 | 40 | if (interfaces.Count > 0) 41 | { 42 | foreach (var @interface in interfaces) 43 | { 44 | var interfaceMethodInfo = @interface.GetMethod(mappingMethodName, argumentTypes); 45 | 46 | interfaceMethodInfo.Invoke(instance, new object[] { this }); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/DTOs/Email/EmailRequestDto.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace CleanArchitectureDemo.Application.DTOs.Email 3 | { 4 | public class EmailRequestDto 5 | { 6 | public string To { get; set; } 7 | public string Subject { get; set; } 8 | public string Body { get; set; } 9 | public string From { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Extensions/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using MediatR; 3 | using System.Reflection; 4 | using FluentValidation; 5 | 6 | namespace CleanArchitectureDemo.Application.Extensions 7 | { 8 | public static class IServiceCollectionExtensions 9 | { 10 | public static void AddApplicationLayer(this IServiceCollection services) 11 | { 12 | services.AddAutoMapper(); 13 | services.AddMediator(); 14 | services.AddValidators(); 15 | } 16 | 17 | private static void AddAutoMapper(this IServiceCollection services) 18 | { 19 | services.AddAutoMapper(Assembly.GetExecutingAssembly()); 20 | } 21 | 22 | private static void AddMediator(this IServiceCollection services) 23 | { 24 | services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); 25 | } 26 | 27 | private static void AddValidators(this IServiceCollection services) 28 | { 29 | services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Extensions/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using CleanArchitectureDemo.Shared; 3 | using System.Threading; 4 | 5 | namespace CleanArchitectureDemo.Application.Extensions 6 | { 7 | public static class QueryableExtensions 8 | { 9 | public static async Task> ToPaginatedListAsync(this IQueryable source, int pageNumber, int pageSize, CancellationToken cancellationToken) where T : class 10 | { 11 | pageNumber = pageNumber == 0 ? 1 : pageNumber; 12 | pageSize = pageSize == 0 ? 10 : pageSize; 13 | int count = await source.CountAsync(); 14 | pageNumber = pageNumber <= 0 ? 1 : pageNumber; 15 | List items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync(cancellationToken); 16 | return PaginatedResult.Create(items, count, pageNumber, pageSize); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/CreatePlayer/CreatePlayerCommand.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using MediatR; 3 | 4 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 5 | using CleanArchitectureDemo.Shared; 6 | using CleanArchitectureDemo.Domain.Entities; 7 | using CleanArchitectureDemo.Application.Common.Mappings; 8 | 9 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.CreatePlayer 10 | { 11 | public record CreatePlayerCommand : IRequest>, IMapFrom 12 | { 13 | public string Name { get; set; } 14 | public int ShirtNo { get; set; } 15 | public string PhotoUrl { get; set; } 16 | public DateTime? BirthDate { get; set; } 17 | } 18 | 19 | internal class CreatePlayerCommandHandler : IRequestHandler> 20 | { 21 | private readonly IUnitOfWork _unitOfWork; 22 | private readonly IMapper _mapper; 23 | 24 | public CreatePlayerCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) 25 | { 26 | _unitOfWork = unitOfWork; 27 | _mapper = mapper; 28 | } 29 | 30 | public async Task> Handle(CreatePlayerCommand command, CancellationToken cancellationToken) 31 | { 32 | var player = new Player() 33 | { 34 | Name = command.Name, 35 | ShirtNo = command.ShirtNo, 36 | PhotoUrl = command.PhotoUrl, 37 | BirthDate = command.BirthDate 38 | }; 39 | 40 | await _unitOfWork.Repository().AddAsync(player); 41 | player.AddDomainEvent(new PlayerCreatedEvent(player)); 42 | 43 | await _unitOfWork.Save(cancellationToken); 44 | 45 | return await Result.SuccessAsync(player.Id, "Player Created."); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/CreatePlayer/PlayerCreatedEvent.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.CreatePlayer 5 | { 6 | public class PlayerCreatedEvent : BaseEvent 7 | { 8 | public Player Player { get; } 9 | 10 | public PlayerCreatedEvent(Player player) 11 | { 12 | Player = player; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/DeletePlayer/DeletePlayerCommand.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | using CleanArchitectureDemo.Application.Common.Mappings; 4 | using CleanArchitectureDemo.Application.Features.Players.Commands.UpdatePlayer; 5 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 6 | using CleanArchitectureDemo.Domain.Entities; 7 | using CleanArchitectureDemo.Shared; 8 | 9 | using MediatR; 10 | 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Text; 15 | using System.Threading.Tasks; 16 | 17 | using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; 18 | 19 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.DeletePlayer 20 | { 21 | public record DeletePlayerCommand : IRequest>, IMapFrom 22 | { 23 | public int Id { get; set; } 24 | 25 | public DeletePlayerCommand() 26 | { 27 | 28 | } 29 | 30 | public DeletePlayerCommand(int id) 31 | { 32 | Id = id; 33 | } 34 | } 35 | 36 | internal class DeletePlayerCommandHandler : IRequestHandler> 37 | { 38 | private readonly IUnitOfWork _unitOfWork; 39 | private readonly IMapper _mapper; 40 | 41 | public DeletePlayerCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) 42 | { 43 | _unitOfWork = unitOfWork; 44 | _mapper = mapper; 45 | } 46 | 47 | public async Task> Handle(DeletePlayerCommand command, CancellationToken cancellationToken) 48 | { 49 | var player = await _unitOfWork.Repository().GetByIdAsync(command.Id); 50 | if (player != null) 51 | { 52 | await _unitOfWork.Repository().DeleteAsync(player); 53 | player.AddDomainEvent(new PlayerDeletedEvent(player)); 54 | 55 | await _unitOfWork.Save(cancellationToken); 56 | 57 | return await Result.SuccessAsync(player.Id, "Product Deleted"); 58 | } 59 | else 60 | { 61 | return await Result.FailureAsync("Player Not Found."); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/DeletePlayer/PlayerDeletedEvent.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.DeletePlayer 5 | { 6 | public class PlayerDeletedEvent : BaseEvent 7 | { 8 | public Player Player { get; } 9 | 10 | public PlayerDeletedEvent(Player player) 11 | { 12 | Player = player; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/UpdatePlayer/PlayerUpdatedEvent.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.UpdatePlayer 5 | { 6 | public class PlayerUpdatedEvent : BaseEvent 7 | { 8 | public Player Player { get; } 9 | 10 | public PlayerUpdatedEvent(Player player) 11 | { 12 | Player = player; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Commands/UpdatePlayer/UpdatePlayerCommand.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | 3 | using CleanArchitectureDemo.Application.Common.Exceptions; 4 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 5 | using CleanArchitectureDemo.Domain.Entities; 6 | using CleanArchitectureDemo.Shared; 7 | 8 | using MediatR; 9 | 10 | namespace CleanArchitectureDemo.Application.Features.Players.Commands.UpdatePlayer 11 | { 12 | public record UpdatePlayerCommand : IRequest> 13 | { 14 | public int Id { get; set; } 15 | public string Name { get; set; } 16 | public int ShirtNo { get; set; } 17 | public string PhotoUrl { get; set; } 18 | public DateTime? BirthDate { get; set; } 19 | } 20 | 21 | internal class UpdatePlayerCommandHandler : IRequestHandler> 22 | { 23 | private readonly IUnitOfWork _unitOfWork; 24 | private readonly IMapper _mapper; 25 | 26 | public UpdatePlayerCommandHandler(IUnitOfWork unitOfWork, IMapper mapper) 27 | { 28 | _unitOfWork = unitOfWork; 29 | _mapper = mapper; 30 | } 31 | 32 | public async Task> Handle(UpdatePlayerCommand command, CancellationToken cancellationToken) 33 | { 34 | var player = await _unitOfWork.Repository().GetByIdAsync(command.Id); 35 | if (player != null) 36 | { 37 | player.Name = command.Name; 38 | player.ShirtNo = command.ShirtNo; 39 | player.PhotoUrl = command.PhotoUrl; 40 | player.BirthDate = command.BirthDate; 41 | 42 | await _unitOfWork.Repository().UpdateAsync(player); 43 | player.AddDomainEvent(new PlayerUpdatedEvent(player)); 44 | 45 | await _unitOfWork.Save(cancellationToken); 46 | 47 | return await Result.SuccessAsync(player.Id, "Player Updated."); 48 | } 49 | else 50 | { 51 | return await Result.FailureAsync("Player Not Found."); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetAllPlayers/GetAllPlayersDto.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Common.Mappings; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 12 | { 13 | public class GetAllPlayersDto : IMapFrom 14 | { 15 | public int Id { get; init; } 16 | public string Name { get; init; } 17 | public int ShirtNo { get; init; } 18 | public int HeightInCm { get; init; } 19 | public string FacebookUrl { get; init; } 20 | public string TwitterUrl { get; init; } 21 | public string InstagramUrl { get; init; } 22 | public int DisplayOrder { get; init; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetAllPlayers/GetAllPlayersQuery.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using AutoMapper.QueryableExtensions; 3 | 4 | using CleanArchitectureDemo.Application.Extensions; 5 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 6 | using CleanArchitectureDemo.Domain.Entities; 7 | using CleanArchitectureDemo.Shared; 8 | using MediatR; 9 | 10 | using Microsoft.EntityFrameworkCore; 11 | 12 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 13 | { 14 | public record GetAllPlayersQuery : IRequest>>; 15 | 16 | internal class GetAllPlayersQueryHandler : IRequestHandler>> 17 | { 18 | private readonly IUnitOfWork _unitOfWork; 19 | private readonly IMapper _mapper; 20 | 21 | public GetAllPlayersQueryHandler(IUnitOfWork unitOfWork, IMapper mapper) 22 | { 23 | _unitOfWork = unitOfWork; 24 | _mapper = mapper; 25 | } 26 | 27 | public async Task>> Handle(GetAllPlayersQuery query, CancellationToken cancellationToken) 28 | { 29 | var players = await _unitOfWork.Repository().Entities 30 | .ProjectTo(_mapper.ConfigurationProvider) 31 | .ToListAsync(cancellationToken); 32 | 33 | return await Result>.SuccessAsync(players); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayerById/GetPlayerByIdDto.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Common.Mappings; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 12 | { 13 | public class GetPlayerByIdDto : IMapFrom 14 | { 15 | public int Id { get; init; } 16 | public string Name { get; init; } 17 | public int ShirtNo { get; init; } 18 | public int HeightInCm { get; init; } 19 | public string FacebookUrl { get; init; } 20 | public string TwitterUrl { get; init; } 21 | public string InstagramUrl { get; init; } 22 | public int DisplayOrder { get; init; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayerById/GetPlayerByIdQuery.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using AutoMapper.QueryableExtensions; 3 | 4 | using CleanArchitectureDemo.Application.Extensions; 5 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 6 | using CleanArchitectureDemo.Domain.Common.Interfaces; 7 | using CleanArchitectureDemo.Domain.Entities; 8 | using CleanArchitectureDemo.Shared; 9 | using MediatR; 10 | 11 | using Microsoft.EntityFrameworkCore; 12 | 13 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 14 | { 15 | public record GetPlayerByIdQuery : IRequest> 16 | { 17 | public int Id { get; set; } 18 | 19 | public GetPlayerByIdQuery() 20 | { 21 | 22 | } 23 | 24 | public GetPlayerByIdQuery(int id) 25 | { 26 | Id = id; 27 | } 28 | } 29 | 30 | internal class GetPlayerByIdQueryHandler : IRequestHandler> 31 | { 32 | private readonly IUnitOfWork _unitOfWork; 33 | private readonly IMapper _mapper; 34 | 35 | public GetPlayerByIdQueryHandler(IUnitOfWork unitOfWork, IMapper mapper) 36 | { 37 | _unitOfWork = unitOfWork; 38 | _mapper = mapper; 39 | } 40 | 41 | public async Task> Handle(GetPlayerByIdQuery query, CancellationToken cancellationToken) 42 | { 43 | var entity = await _unitOfWork.Repository().GetByIdAsync(query.Id); 44 | var player = _mapper.Map(entity); 45 | return await Result.SuccessAsync(player); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayersByClub/GetPlayersByClubDto.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Common.Mappings; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 12 | { 13 | public class GetPlayersByClubDto : IMapFrom 14 | { 15 | public int Id { get; init; } 16 | public string Name { get; init; } 17 | public int ShirtNo { get; init; } 18 | public int HeightInCm { get; init; } 19 | public string FacebookUrl { get; init; } 20 | public string TwitterUrl { get; init; } 21 | public string InstagramUrl { get; init; } 22 | public int DisplayOrder { get; init; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayersByClub/GetPlayersByClubQuery.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using AutoMapper.QueryableExtensions; 3 | 4 | using CleanArchitectureDemo.Application.Extensions; 5 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 6 | using CleanArchitectureDemo.Domain.Entities; 7 | using CleanArchitectureDemo.Shared; 8 | using MediatR; 9 | 10 | using Microsoft.EntityFrameworkCore; 11 | 12 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 13 | { 14 | public record GetPlayersByClubQuery : IRequest>> 15 | { 16 | public int ClubId { get; set; } 17 | 18 | public GetPlayersByClubQuery() 19 | { 20 | 21 | } 22 | 23 | public GetPlayersByClubQuery(int clubId) 24 | { 25 | ClubId = clubId; 26 | } 27 | } 28 | 29 | internal class GetPlayersByClubQueryHandler : IRequestHandler>> 30 | { 31 | private readonly IUnitOfWork _unitOfWork; 32 | private readonly IPlayerRepository _playerRepository; 33 | private readonly IMapper _mapper; 34 | 35 | public GetPlayersByClubQueryHandler(IUnitOfWork unitOfWork, IPlayerRepository playerRepository, IMapper mapper) 36 | { 37 | _unitOfWork = unitOfWork; 38 | _playerRepository = playerRepository; 39 | _mapper = mapper; 40 | } 41 | 42 | public async Task>> Handle(GetPlayersByClubQuery query, CancellationToken cancellationToken) 43 | { 44 | var entities = await _playerRepository.GetPlayersByClubAsync(query.ClubId); 45 | var players = _mapper.Map>(entities); 46 | return await Result>.SuccessAsync(players); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayersWithPagination/GetPlayersWithPaginationDto.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Common.Mappings; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 12 | { 13 | public class GetPlayersWithPaginationDto : IMapFrom 14 | { 15 | public int Id { get; init; } 16 | public string Name { get; init; } 17 | public int ShirtNo { get; init; } 18 | public int HeightInCm { get; init; } 19 | public string FacebookUrl { get; init; } 20 | public string TwitterUrl { get; init; } 21 | public string InstagramUrl { get; init; } 22 | public int DisplayOrder { get; init; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayersWithPagination/GetPlayersWithPaginationQuery.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using AutoMapper.QueryableExtensions; 3 | 4 | using CleanArchitectureDemo.Application.Extensions; 5 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 6 | using CleanArchitectureDemo.Domain.Entities; 7 | using CleanArchitectureDemo.Shared; 8 | using MediatR; 9 | 10 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 11 | { 12 | public record GetPlayersWithPaginationQuery : IRequest> 13 | { 14 | public int PageNumber { get; set; } 15 | public int PageSize { get; set; } 16 | 17 | public GetPlayersWithPaginationQuery() { } 18 | 19 | public GetPlayersWithPaginationQuery(int pageNumber, int pageSize) 20 | { 21 | PageNumber = pageNumber; 22 | PageSize = pageSize; 23 | } 24 | } 25 | 26 | internal class GetPlayersWithPaginationQueryHandler : IRequestHandler> 27 | { 28 | private readonly IUnitOfWork _unitOfWork; 29 | private readonly IMapper _mapper; 30 | 31 | public GetPlayersWithPaginationQueryHandler(IUnitOfWork unitOfWork, IMapper mapper) 32 | { 33 | _unitOfWork = unitOfWork; 34 | _mapper = mapper; 35 | } 36 | 37 | public async Task> Handle(GetPlayersWithPaginationQuery query, CancellationToken cancellationToken) 38 | { 39 | return await _unitOfWork.Repository().Entities 40 | .OrderBy(x => x.Name) 41 | .ProjectTo(_mapper.ConfigurationProvider) 42 | .ToPaginatedListAsync(query.PageNumber, query.PageSize, cancellationToken); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Features/Players/Queries/GetPlayersWithPagination/GetPlayersWithPaginationValidator.cs: -------------------------------------------------------------------------------- 1 | using FluentValidation; 2 | 3 | namespace CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination 4 | { 5 | public class GetPlayersWithPaginationValidator : AbstractValidator 6 | { 7 | public GetPlayersWithPaginationValidator() 8 | { 9 | RuleFor(x => x.PageNumber) 10 | .GreaterThanOrEqualTo(1) 11 | .WithMessage("PageNumber at least greater than or equal to 1."); 12 | 13 | RuleFor(x => x.PageSize) 14 | .GreaterThanOrEqualTo(1) 15 | .WithMessage("PageSize at least greater than or equal to 1."); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/IDateTimeService.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Application.Interfaces 2 | { 3 | public interface IDateTimeService 4 | { 5 | DateTime NowUtc { get; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/IEmailService.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.DTOs.Email; 2 | 3 | namespace CleanArchitectureDemo.Application.Interfaces 4 | { 5 | public interface IEmailService 6 | { 7 | Task SendAsync(EmailRequestDto request); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/IClubRepository.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Application.Interfaces.Repositories 2 | { 3 | public interface IClubRepository 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/ICountryRepository.cs: -------------------------------------------------------------------------------- 1 |  namespace CleanArchitectureDemo.Application.Interfaces.Repositories 2 | { 3 | public interface ICountryRepository 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/IGenericRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common.Interfaces; 2 | 3 | namespace CleanArchitectureDemo.Application.Interfaces.Repositories 4 | { 5 | public interface IGenericRepository where T : class, IEntity 6 | { 7 | IQueryable Entities { get; } 8 | 9 | Task GetByIdAsync(int id); 10 | Task> GetAllAsync(); 11 | Task AddAsync(T entity); 12 | Task UpdateAsync(T entity); 13 | Task DeleteAsync(T entity); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/IPlayerRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Entities; 2 | 3 | namespace CleanArchitectureDemo.Application.Interfaces.Repositories 4 | { 5 | public interface IPlayerRepository 6 | { 7 | Task> GetPlayersByClubAsync(int clubId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/IStadiumRepository.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Application.Interfaces.Repositories 2 | { 3 | public interface IStadiumRepository 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Application/Interfaces/Repositories/IUnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | 3 | namespace CleanArchitectureDemo.Application.Interfaces.Repositories 4 | { 5 | public interface IUnitOfWork : IDisposable 6 | { 7 | IGenericRepository Repository() where T : BaseAuditableEntity; 8 | 9 | Task Save(CancellationToken cancellationToken); 10 | 11 | Task SaveAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys); 12 | 13 | Task Rollback(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/CleanArchitectureDemo.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/BaseAuditableEntity.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common.Interfaces; 2 | 3 | namespace CleanArchitectureDemo.Domain.Common 4 | { 5 | public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity 6 | { 7 | public int? CreatedBy { get; set; } 8 | public DateTime? CreatedDate { get; set; } 9 | public int? UpdatedBy { get; set; } 10 | public DateTime? UpdatedDate { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common.Interfaces; 2 | 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace CleanArchitectureDemo.Domain.Common 6 | { 7 | public abstract class BaseEntity : IEntity 8 | { 9 | private readonly List _domainEvents = new(); 10 | 11 | public int Id { get; set; } 12 | 13 | [NotMapped] 14 | public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); 15 | 16 | public void AddDomainEvent(BaseEvent domainEvent) => _domainEvents.Add(domainEvent); 17 | public void RemoveDomainEvent(BaseEvent domainEvent) => _domainEvents.Remove(domainEvent); 18 | public void ClearDomainEvents() => _domainEvents.Clear(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/BaseEvent.cs: -------------------------------------------------------------------------------- 1 |  2 | using MediatR; 3 | 4 | namespace CleanArchitectureDemo.Domain.Common 5 | { 6 | public abstract class BaseEvent : INotification 7 | { 8 | public DateTime DateOccurred { get; protected set; } = DateTime.UtcNow; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/DomainEventDispatcher.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common.Interfaces; 2 | 3 | using MediatR; 4 | 5 | namespace CleanArchitectureDemo.Domain.Common 6 | { 7 | public class DomainEventDispatcher : IDomainEventDispatcher 8 | { 9 | private readonly IMediator _mediator; 10 | 11 | public DomainEventDispatcher(IMediator mediator) 12 | { 13 | _mediator = mediator; 14 | } 15 | 16 | public async Task DispatchAndClearEvents(IEnumerable entitiesWithEvents) 17 | { 18 | foreach (var entity in entitiesWithEvents) 19 | { 20 | var events = entity.DomainEvents.ToArray(); 21 | 22 | entity.ClearDomainEvents(); 23 | 24 | foreach (var domainEvent in events) 25 | { 26 | await _mediator.Publish(domainEvent).ConfigureAwait(false); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/Interfaces/IAuditableEntity.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Domain.Common.Interfaces 2 | { 3 | public interface IAuditableEntity : IEntity 4 | { 5 | int? CreatedBy { get; set; } 6 | DateTime? CreatedDate { get; set; } 7 | int? UpdatedBy { get; set; } 8 | DateTime? UpdatedDate { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/Interfaces/IDomainEventDispatcher.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Domain.Common.Interfaces 2 | { 3 | public interface IDomainEventDispatcher 4 | { 5 | Task DispatchAndClearEvents(IEnumerable entitiesWithEvents); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/Interfaces/IEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | namespace CleanArchitectureDemo.Domain.Common.Interfaces 4 | { 5 | public interface IEntity 6 | { 7 | public int Id { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Common/ValueObject.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Domain.Common 2 | { 3 | public abstract class ValueObject 4 | { 5 | protected static bool EqualOperator(ValueObject left, ValueObject right) 6 | { 7 | if (left is null ^ right is null) 8 | { 9 | return false; 10 | } 11 | 12 | return left?.Equals(right!) != false; 13 | } 14 | 15 | protected static bool NotEqualOperator(ValueObject left, ValueObject right) 16 | { 17 | return !(EqualOperator(left, right)); 18 | } 19 | 20 | protected abstract IEnumerable GetEqualityComponents(); 21 | 22 | public override bool Equals(object obj) 23 | { 24 | if (obj == null || obj.GetType() != GetType()) 25 | { 26 | return false; 27 | } 28 | 29 | var other = (ValueObject)obj; 30 | return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | return GetEqualityComponents() 36 | .Select(x => x != null ? x.GetHashCode() : 0) 37 | .Aggregate((x, y) => x ^ y); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Entities/Club.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | 3 | namespace CleanArchitectureDemo.Domain.Entities 4 | { 5 | public class Club : BaseAuditableEntity 6 | { 7 | public Club() 8 | { 9 | Players = new List(); 10 | } 11 | 12 | public string Name { get; set; } 13 | public string PhotoUrl { get; set; } 14 | public string WebsiteUrl { get; set; } 15 | public string FacebookUrl { get; set; } 16 | public string TwitterUrl { get; set; } 17 | public string YoutubeUrl { get; set; } 18 | public string InstagramUrl { get; set; } 19 | public int? StadiumId { get; set; } 20 | 21 | public Stadium Stadium { get; set; } 22 | public IList Players { get; set; } 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Entities/Country.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | 3 | namespace CleanArchitectureDemo.Domain.Entities 4 | { 5 | public class Country : BaseAuditableEntity 6 | { 7 | public Country() 8 | { 9 | Players = new List(); 10 | Stadiums = new List(); 11 | } 12 | 13 | public string Name { get; set; } 14 | public string TwoLetterIsoCode { get; set; } 15 | public string ThreeLetterIsoCode { get; set; } 16 | public string FlagUrl { get; set; } 17 | public int? DisplayOrder { get; set; } 18 | 19 | public IList Players { get; set; } 20 | public IList Stadiums { get; set; } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Entities/Player.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | 3 | namespace CleanArchitectureDemo.Domain.Entities 4 | { 5 | public class Player : BaseAuditableEntity 6 | { 7 | public string Name { get; set; } 8 | public int? ShirtNo { get; set; } 9 | public int? ClubId { get; set; } 10 | public int? PlayerPositionId { get; set; } 11 | public string PhotoUrl { get; set; } 12 | public int? CountryId { get; set; } 13 | public DateTime? BirthDate { get; set; } 14 | public int? HeightInCm { get; set; } 15 | public string FacebookUrl { get; set; } 16 | public string TwitterUrl { get; set; } 17 | public string InstagramUrl { get; set; } 18 | public int? DisplayOrder { get; set; } 19 | 20 | public Club Club { get; set; } 21 | public Country Country { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Domain/Entities/Stadium.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | 3 | namespace CleanArchitectureDemo.Domain.Entities 4 | { 5 | public class Stadium : BaseAuditableEntity 6 | { 7 | public Stadium() 8 | { 9 | Clubs = new List(); 10 | } 11 | 12 | public string Name { get; set; } 13 | public string PhotoUrl { get; set; } 14 | public int? Capacity { get; set; } 15 | public int? BuiltYear { get; set; } 16 | public int? PitchLength { get; set; } 17 | public int? PitchWidth { get; set; } 18 | public string Phone { get; set; } 19 | public string AddressLine1 { get; set; } 20 | public string AddressLine2 { get; set; } 21 | public string AddressLine3 { get; set; } 22 | public string City { get; set; } 23 | public string PostalCode { get; set; } 24 | public int? CountryId { get; set; } 25 | 26 | public Country Country { get; set; } 27 | public IList Clubs { get; set; } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Infrastructure/CleanArchitectureDemo.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Infrastructure/Extensions/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces; 2 | using CleanArchitectureDemo.Domain.Common; 3 | using CleanArchitectureDemo.Domain.Common.Interfaces; 4 | using CleanArchitectureDemo.Infrastructure.Services; 5 | 6 | using MediatR; 7 | 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | namespace CleanArchitectureDemo.Infrastructure.Extensions 11 | { 12 | public static class IServiceCollectionExtensions 13 | { 14 | public static void AddInfrastructureLayer(this IServiceCollection services) 15 | { 16 | services.AddServices(); 17 | } 18 | 19 | private static void AddServices(this IServiceCollection services) 20 | { 21 | services 22 | .AddTransient() 23 | .AddTransient() 24 | .AddTransient() 25 | .AddTransient(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Infrastructure/Services/DateTimeService.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces; 2 | 3 | namespace CleanArchitectureDemo.Infrastructure.Services 4 | { 5 | public class DateTimeService : IDateTimeService 6 | { 7 | public DateTime NowUtc => DateTime.UtcNow; 8 | } 9 | } -------------------------------------------------------------------------------- /CleanArchitectureDemo.Infrastructure/Services/EmailService.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.DTOs.Email; 2 | using CleanArchitectureDemo.Application.Interfaces; 3 | 4 | using Microsoft.Extensions.Logging; 5 | using System.Net.Mail; 6 | 7 | namespace CleanArchitectureDemo.Infrastructure.Services 8 | { 9 | public class EmailService : IEmailService 10 | { 11 | public async Task SendAsync(EmailRequestDto request) 12 | { 13 | var emailClient = new SmtpClient("localhost"); 14 | var message = new MailMessage 15 | { 16 | From = new MailAddress(request.From), 17 | Subject = request.Subject, 18 | Body = request.Body 19 | }; 20 | message.To.Add(new MailAddress(request.To)); 21 | await emailClient.SendMailAsync(message); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/CleanArchitectureDemo.Persistence.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Contexts/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Domain.Common; 2 | using CleanArchitectureDemo.Domain.Common.Interfaces; 3 | using CleanArchitectureDemo.Domain.Entities; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | using System.Reflection; 8 | 9 | namespace CleanArchitectureDemo.Persistence.Contexts 10 | { 11 | public class ApplicationDbContext : DbContext 12 | { 13 | private readonly IDomainEventDispatcher _dispatcher; 14 | 15 | public ApplicationDbContext(DbContextOptions options, 16 | IDomainEventDispatcher dispatcher) 17 | : base(options) 18 | { 19 | _dispatcher = dispatcher; 20 | } 21 | 22 | public DbSet Clubs => Set(); 23 | public DbSet Players => Set(); 24 | public DbSet Stadiums => Set(); 25 | public DbSet Countries => Set(); 26 | 27 | protected override void OnModelCreating(ModelBuilder modelBuilder) 28 | { 29 | base.OnModelCreating(modelBuilder); 30 | modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); 31 | } 32 | 33 | public override async Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) 34 | { 35 | int result = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false); 36 | 37 | // ignore events if no dispatcher provided 38 | if (_dispatcher == null) return result; 39 | 40 | // dispatch events only if save was successful 41 | var entitiesWithEvents = ChangeTracker.Entries() 42 | .Select(e => e.Entity) 43 | .Where(e => e.DomainEvents.Any()) 44 | .ToArray(); 45 | 46 | await _dispatcher.DispatchAndClearEvents(entitiesWithEvents); 47 | 48 | return result; 49 | } 50 | 51 | public override int SaveChanges() 52 | { 53 | return SaveChangesAsync().GetAwaiter().GetResult(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Extensions/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Persistence.Contexts; 3 | using CleanArchitectureDemo.Persistence.Repositories; 4 | 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Configuration; 7 | using System.Reflection; 8 | using Microsoft.EntityFrameworkCore; 9 | 10 | namespace CleanArchitectureDemo.Persistence.Extensions 11 | { 12 | public static class IServiceCollectionExtensions 13 | { 14 | public static void AddPersistenceLayer(this IServiceCollection services, IConfiguration configuration) 15 | { 16 | //services.AddMappings(); 17 | services.AddDbContext(configuration); 18 | services.AddRepositories(); 19 | } 20 | 21 | //private static void AddMappings(this IServiceCollection services) 22 | //{ 23 | // services.AddAutoMapper(Assembly.GetExecutingAssembly()); 24 | //} 25 | 26 | public static void AddDbContext(this IServiceCollection services, IConfiguration configuration) 27 | { 28 | var connectionString = configuration.GetConnectionString("DefaultConnection"); 29 | 30 | services.AddDbContext(options => 31 | options.UseSqlServer(connectionString, 32 | builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); 33 | } 34 | 35 | private static void AddRepositories(this IServiceCollection services) 36 | { 37 | services 38 | .AddTransient(typeof(IUnitOfWork), typeof(UnitOfWork)) 39 | .AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>)) 40 | .AddTransient() 41 | .AddTransient() 42 | .AddTransient() 43 | .AddTransient(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/ClubRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | using CleanArchitectureDemo.Persistence.Contexts; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace CleanArchitectureDemo.Persistence.Repositories 14 | { 15 | public class ClubRepository : IClubRepository 16 | { 17 | private readonly IGenericRepository _repository; 18 | 19 | public ClubRepository(IGenericRepository repository) 20 | { 21 | _repository = repository; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/CountryRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | using CleanArchitectureDemo.Persistence.Contexts; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace CleanArchitectureDemo.Persistence.Repositories 14 | { 15 | public class CountryRepository : ICountryRepository 16 | { 17 | private readonly IGenericRepository _repository; 18 | 19 | public CountryRepository(IGenericRepository repository) 20 | { 21 | _repository = repository; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/GenericRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Common; 3 | using CleanArchitectureDemo.Persistence.Contexts; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace CleanArchitectureDemo.Persistence.Repositories 8 | { 9 | public class GenericRepository : IGenericRepository where T : BaseAuditableEntity 10 | { 11 | private readonly ApplicationDbContext _dbContext; 12 | 13 | public GenericRepository(ApplicationDbContext dbContext) 14 | { 15 | _dbContext = dbContext; 16 | } 17 | 18 | public IQueryable Entities => _dbContext.Set(); 19 | 20 | public async Task AddAsync(T entity) 21 | { 22 | await _dbContext.Set().AddAsync(entity); 23 | return entity; 24 | } 25 | 26 | public Task UpdateAsync(T entity) 27 | { 28 | T exist = _dbContext.Set().Find(entity.Id); 29 | _dbContext.Entry(exist).CurrentValues.SetValues(entity); 30 | return Task.CompletedTask; 31 | } 32 | 33 | public Task DeleteAsync(T entity) 34 | { 35 | _dbContext.Set().Remove(entity); 36 | return Task.CompletedTask; 37 | } 38 | 39 | public async Task> GetAllAsync() 40 | { 41 | return await _dbContext 42 | .Set() 43 | .ToListAsync(); 44 | } 45 | 46 | public async Task GetByIdAsync(int id) 47 | { 48 | return await _dbContext.Set().FindAsync(id); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/PlayerRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | using CleanArchitectureDemo.Persistence.Contexts; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace CleanArchitectureDemo.Persistence.Repositories 14 | { 15 | public class PlayerRepository : IPlayerRepository 16 | { 17 | private readonly IGenericRepository _repository; 18 | 19 | public PlayerRepository(IGenericRepository repository) 20 | { 21 | _repository = repository; 22 | } 23 | 24 | public async Task> GetPlayersByClubAsync(int clubId) 25 | { 26 | return await _repository.Entities.Where(x => x.ClubId == clubId).ToListAsync(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/StadiumRepository.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Entities; 3 | using CleanArchitectureDemo.Persistence.Contexts; 4 | 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace CleanArchitectureDemo.Persistence.Repositories 14 | { 15 | public class StadiumRepository : IStadiumRepository 16 | { 17 | private readonly IGenericRepository _repository; 18 | 19 | public StadiumRepository(IGenericRepository repository) 20 | { 21 | _repository = repository; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Persistence/Repositories/UnitOfWork.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Interfaces.Repositories; 2 | using CleanArchitectureDemo.Domain.Common; 3 | using CleanArchitectureDemo.Domain.Common.Interfaces; 4 | using CleanArchitectureDemo.Persistence.Contexts; 5 | 6 | using System.Collections; 7 | 8 | namespace CleanArchitectureDemo.Persistence.Repositories 9 | { 10 | public class UnitOfWork : IUnitOfWork 11 | { 12 | private readonly ApplicationDbContext _dbContext; 13 | private Hashtable _repositories; 14 | private bool disposed; 15 | 16 | public UnitOfWork(ApplicationDbContext dbContext) 17 | { 18 | _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); 19 | } 20 | 21 | public IGenericRepository Repository() where T : BaseAuditableEntity 22 | { 23 | if (_repositories == null) 24 | _repositories = new Hashtable(); 25 | 26 | var type = typeof(T).Name; 27 | 28 | if (!_repositories.ContainsKey(type)) 29 | { 30 | var repositoryType = typeof(GenericRepository<>); 31 | 32 | var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _dbContext); 33 | 34 | _repositories.Add(type, repositoryInstance); 35 | } 36 | 37 | return (IGenericRepository) _repositories[type]; 38 | } 39 | 40 | public Task Rollback() 41 | { 42 | _dbContext.ChangeTracker.Entries().ToList().ForEach(x => x.Reload()); 43 | return Task.CompletedTask; 44 | } 45 | 46 | public async Task Save(CancellationToken cancellationToken) 47 | { 48 | return await _dbContext.SaveChangesAsync(cancellationToken); 49 | } 50 | 51 | public Task SaveAndRemoveCache(CancellationToken cancellationToken, params string[] cacheKeys) 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | public void Dispose() 57 | { 58 | Dispose(true); 59 | GC.SuppressFinalize(this); 60 | } 61 | 62 | protected virtual void Dispose(bool disposing) 63 | { 64 | if (disposed) 65 | { 66 | if (disposing) 67 | { 68 | //dispose managed resources 69 | _dbContext.Dispose(); 70 | } 71 | } 72 | //dispose unmanaged resources 73 | disposed = true; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Shared/CleanArchitectureDemo.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | disable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Shared/Interfaces/IResult.cs: -------------------------------------------------------------------------------- 1 | namespace CleanArchitectureDemo.Shared.Interfaces 2 | { 3 | public interface IResult 4 | { 5 | List Messages { get; set; } 6 | 7 | bool Succeeded { get; set; } 8 | 9 | T Data { get; set; } 10 | 11 | //List ValidationErrors { get; set; } 12 | 13 | Exception Exception { get; set; } 14 | 15 | int Code { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Shared/PaginatedResult.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Shared.Interfaces; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace CleanArchitectureDemo.Shared 10 | { 11 | public class PaginatedResult : Result 12 | { 13 | public PaginatedResult(List data) 14 | { 15 | Data = data; 16 | } 17 | 18 | public PaginatedResult(bool succeeded, List data = default, List messages = null, int count = 0, int pageNumber = 1, int pageSize = 10) 19 | { 20 | Data = data; 21 | CurrentPage = pageNumber; 22 | Succeeded = succeeded; 23 | Messages = messages; 24 | PageSize = pageSize; 25 | TotalPages = (int)Math.Ceiling(count / (double)pageSize); 26 | TotalCount = count; 27 | } 28 | 29 | public new List Data { get; set; } 30 | public int CurrentPage { get; set; } 31 | public int TotalPages { get; set; } 32 | public int TotalCount { get; set; } 33 | public int PageSize { get; set; } 34 | 35 | public bool HasPreviousPage => CurrentPage > 1; 36 | public bool HasNextPage => CurrentPage < TotalPages; 37 | 38 | public static PaginatedResult Create(List data, int count, int pageNumber, int pageSize) 39 | { 40 | return new PaginatedResult(true, data, null, count, pageNumber, pageSize); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.Shared/Result.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Shared.Interfaces; 2 | 3 | namespace CleanArchitectureDemo.Shared 4 | { 5 | public class Result : IResult 6 | { 7 | public List Messages { get; set; } = new List(); 8 | 9 | public bool Succeeded { get; set; } 10 | 11 | public T Data { get; set; } 12 | 13 | //public List ValidationErrors { get; set; } 14 | 15 | public Exception Exception { get; set; } 16 | 17 | public int Code { get; set; } 18 | 19 | #region Non Async Methods 20 | 21 | #region Success Methods 22 | 23 | public static Result Success() 24 | { 25 | return new Result 26 | { 27 | Succeeded = true 28 | }; 29 | } 30 | 31 | public static Result Success(string message) 32 | { 33 | return new Result 34 | { 35 | Succeeded = true, 36 | Messages = new List { message } 37 | }; 38 | } 39 | 40 | public static Result Success(T data) 41 | { 42 | return new Result 43 | { 44 | Succeeded = true, 45 | Data = data 46 | }; 47 | } 48 | 49 | public static Result Success(T data, string message) 50 | { 51 | return new Result 52 | { 53 | Succeeded = true, 54 | Messages = new List { message }, 55 | Data = data 56 | }; 57 | } 58 | 59 | #endregion 60 | 61 | #region Failure Methods 62 | 63 | public static Result Failure() 64 | { 65 | return new Result 66 | { 67 | Succeeded = false 68 | }; 69 | } 70 | 71 | public static Result Failure(string message) 72 | { 73 | return new Result 74 | { 75 | Succeeded = false, 76 | Messages = new List { message } 77 | }; 78 | } 79 | 80 | public static Result Failure(List messages) 81 | { 82 | return new Result 83 | { 84 | Succeeded = false, 85 | Messages = messages 86 | }; 87 | } 88 | 89 | public static Result Failure(T data) 90 | { 91 | return new Result 92 | { 93 | Succeeded = false, 94 | Data = data 95 | }; 96 | } 97 | 98 | public static Result Failure(T data, string message) 99 | { 100 | return new Result 101 | { 102 | Succeeded = false, 103 | Messages = new List { message }, 104 | Data = data 105 | }; 106 | } 107 | 108 | public static Result Failure(T data, List messages) 109 | { 110 | return new Result 111 | { 112 | Succeeded = false, 113 | Messages = messages, 114 | Data = data 115 | }; 116 | } 117 | 118 | public static Result Failure(Exception exception) 119 | { 120 | return new Result 121 | { 122 | Succeeded = false, 123 | Exception = exception 124 | }; 125 | } 126 | 127 | #endregion 128 | 129 | #endregion 130 | 131 | #region Async Methods 132 | 133 | #region Success Methods 134 | 135 | public static Task> SuccessAsync() 136 | { 137 | return Task.FromResult(Success()); 138 | } 139 | 140 | public static Task> SuccessAsync(string message) 141 | { 142 | return Task.FromResult(Success(message)); 143 | } 144 | 145 | public static Task> SuccessAsync(T data) 146 | { 147 | return Task.FromResult(Success(data)); 148 | } 149 | 150 | public static Task> SuccessAsync(T data, string message) 151 | { 152 | return Task.FromResult(Success(data, message)); 153 | } 154 | 155 | #endregion 156 | 157 | #region Failure Methods 158 | 159 | public static Task> FailureAsync() 160 | { 161 | return Task.FromResult(Failure()); 162 | } 163 | 164 | public static Task> FailureAsync(string message) 165 | { 166 | return Task.FromResult(Failure(message)); 167 | } 168 | 169 | public static Task> FailureAsync(List messages) 170 | { 171 | return Task.FromResult(Failure(messages)); 172 | } 173 | 174 | public static Task> FailureAsync(T data) 175 | { 176 | return Task.FromResult(Failure(data)); 177 | } 178 | 179 | public static Task> FailureAsync(T data, string message) 180 | { 181 | return Task.FromResult(Failure(data, message)); 182 | } 183 | 184 | public static Task> FailureAsync(T data, List messages) 185 | { 186 | return Task.FromResult(Failure(data, messages)); 187 | } 188 | 189 | public static Task> FailureAsync(Exception exception) 190 | { 191 | return Task.FromResult(Failure(exception)); 192 | } 193 | 194 | #endregion 195 | 196 | #endregion 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/CleanArchitectureDemo.WebAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | disable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/Controllers/ApiControllerBase.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace CleanArchitectureDemo.WebAPI.Controllers 5 | { 6 | [ApiController] 7 | [Route("api/[controller]")] 8 | public abstract class ApiControllerBase : ControllerBase 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/Controllers/PlayersController.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Features.Players.Commands.CreatePlayer; 2 | using CleanArchitectureDemo.Application.Features.Players.Commands.DeletePlayer; 3 | using CleanArchitectureDemo.Application.Features.Players.Commands.UpdatePlayer; 4 | using CleanArchitectureDemo.Application.Features.Players.Queries.GetPlayersWithPagination; 5 | using CleanArchitectureDemo.Shared; 6 | using MediatR; 7 | 8 | using Microsoft.AspNetCore.Mvc; 9 | 10 | namespace CleanArchitectureDemo.WebAPI.Controllers 11 | { 12 | public class PlayersController : ApiControllerBase 13 | { 14 | private readonly IMediator _mediator; 15 | 16 | public PlayersController(IMediator mediator) 17 | { 18 | _mediator = mediator; 19 | } 20 | 21 | [HttpGet] 22 | public async Task>>> Get() 23 | { 24 | return await _mediator.Send(new GetAllPlayersQuery()); 25 | } 26 | 27 | [HttpGet("{id}")] 28 | public async Task>> GetPlayersById(int id) 29 | { 30 | return await _mediator.Send(new GetPlayerByIdQuery(id)); 31 | } 32 | 33 | [HttpGet] 34 | [Route("club/{clubId}")] 35 | public async Task>>> GetPlayersByClub(int clubId) 36 | { 37 | return await _mediator.Send(new GetPlayersByClubQuery(clubId)); 38 | } 39 | 40 | [HttpGet] 41 | [Route("paged")] 42 | public async Task>> GetPlayersWithPagination([FromQuery] GetPlayersWithPaginationQuery query) 43 | { 44 | var validator = new GetPlayersWithPaginationValidator(); 45 | 46 | // Call Validate or ValidateAsync and pass the object which needs to be validated 47 | var result = validator.Validate(query); 48 | 49 | if (result.IsValid) 50 | { 51 | return await _mediator.Send(query); 52 | } 53 | 54 | var errorMessages = result.Errors.Select(x => x.ErrorMessage).ToList(); 55 | return BadRequest(errorMessages); 56 | } 57 | 58 | [HttpPost] 59 | public async Task>> Create(CreatePlayerCommand command) 60 | { 61 | return await _mediator.Send(command); 62 | } 63 | 64 | [HttpPut("{id}")] 65 | public async Task>> Update(int id, UpdatePlayerCommand command) 66 | { 67 | if (id != command.Id) 68 | { 69 | return BadRequest(); 70 | } 71 | 72 | return await _mediator.Send(command); 73 | } 74 | 75 | [HttpDelete("{id}")] 76 | public async Task>> Delete(int id) 77 | { 78 | return await _mediator.Send(new DeletePlayerCommand(id)); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | using CleanArchitectureDemo.Application.Extensions; 2 | using CleanArchitectureDemo.Infrastructure.Extensions; 3 | using CleanArchitectureDemo.Persistence.Extensions; 4 | 5 | var builder = WebApplication.CreateBuilder(args); 6 | 7 | // Add services to the container. 8 | builder.Services.AddApplicationLayer(); 9 | builder.Services.AddInfrastructureLayer(); 10 | builder.Services.AddPersistenceLayer(builder.Configuration); 11 | 12 | builder.Services.AddControllers(); 13 | 14 | var app = builder.Build(); 15 | 16 | // Configure the HTTP request pipeline. 17 | 18 | if (app.Environment.IsDevelopment()) 19 | { 20 | app.UseDeveloperExceptionPage(); 21 | } 22 | 23 | app.MapControllers(); 24 | 25 | app.Run(); 26 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:22748", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "api/players", 17 | "applicationUrl": "http://localhost:5079", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "api/players", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.WebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=DESKTOP-6SDKTRC;Database=PremierLeagueAppDb;User Id=sa;Password=sql;MultipleActiveResultSets=true;TrustServerCertificate=True;" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /CleanArchitectureDemo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33414.496 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{CA65986F-B20C-4EC4-A082-1F9612F60ED5}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{3D4D9ABE-EDA6-488E-BA92-B1C11BBF218B}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{DF9E0885-BDE2-4429-B5DA-FDEDF046A23E}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CleanArchitectureDemo.Domain", "CleanArchitectureDemo.Domain\CleanArchitectureDemo.Domain.csproj", "{D846A1AD-755E-420B-A978-F776F20D02FF}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureDemo.Application", "CleanArchitectureDemo.Application\CleanArchitectureDemo.Application.csproj", "{A4FE6579-2E67-4927-9795-CE7D377C14F4}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureDemo.Infrastructure", "CleanArchitectureDemo.Infrastructure\CleanArchitectureDemo.Infrastructure.csproj", "{5266DEF2-AFB1-4C05-BB96-40D0624735CD}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureDemo.WebAPI", "CleanArchitectureDemo.WebAPI\CleanArchitectureDemo.WebAPI.csproj", "{1F95F04A-14C4-4E80-B1EE-8E476618D5CB}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{319A965F-881D-432E-81BA-18609C32F4B4}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureDemo.Shared", "CleanArchitectureDemo.Shared\CleanArchitectureDemo.Shared.csproj", "{12097BC3-E863-44A5-9E1E-8FD72ADADA1E}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CleanArchitectureDemo.Persistence", "CleanArchitectureDemo.Persistence\CleanArchitectureDemo.Persistence.csproj", "{6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {D846A1AD-755E-420B-A978-F776F20D02FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {D846A1AD-755E-420B-A978-F776F20D02FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {D846A1AD-755E-420B-A978-F776F20D02FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {D846A1AD-755E-420B-A978-F776F20D02FF}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {A4FE6579-2E67-4927-9795-CE7D377C14F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {A4FE6579-2E67-4927-9795-CE7D377C14F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {A4FE6579-2E67-4927-9795-CE7D377C14F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {A4FE6579-2E67-4927-9795-CE7D377C14F4}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {5266DEF2-AFB1-4C05-BB96-40D0624735CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {5266DEF2-AFB1-4C05-BB96-40D0624735CD}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {5266DEF2-AFB1-4C05-BB96-40D0624735CD}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {5266DEF2-AFB1-4C05-BB96-40D0624735CD}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {1F95F04A-14C4-4E80-B1EE-8E476618D5CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {1F95F04A-14C4-4E80-B1EE-8E476618D5CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {1F95F04A-14C4-4E80-B1EE-8E476618D5CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {1F95F04A-14C4-4E80-B1EE-8E476618D5CB}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {12097BC3-E863-44A5-9E1E-8FD72ADADA1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {12097BC3-E863-44A5-9E1E-8FD72ADADA1E}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {12097BC3-E863-44A5-9E1E-8FD72ADADA1E}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {12097BC3-E863-44A5-9E1E-8FD72ADADA1E}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {D846A1AD-755E-420B-A978-F776F20D02FF} = {CA65986F-B20C-4EC4-A082-1F9612F60ED5} 62 | {A4FE6579-2E67-4927-9795-CE7D377C14F4} = {CA65986F-B20C-4EC4-A082-1F9612F60ED5} 63 | {5266DEF2-AFB1-4C05-BB96-40D0624735CD} = {3D4D9ABE-EDA6-488E-BA92-B1C11BBF218B} 64 | {1F95F04A-14C4-4E80-B1EE-8E476618D5CB} = {DF9E0885-BDE2-4429-B5DA-FDEDF046A23E} 65 | {12097BC3-E863-44A5-9E1E-8FD72ADADA1E} = {319A965F-881D-432E-81BA-18609C32F4B4} 66 | {6F508EF1-0749-41F6-BFC9-1DC98A7F8AAF} = {3D4D9ABE-EDA6-488E-BA92-B1C11BBF218B} 67 | EndGlobalSection 68 | GlobalSection(ExtensibilityGlobals) = postSolution 69 | SolutionGuid = {EE98FB89-042B-4868-A412-16E4D6F02D43} 70 | EndGlobalSection 71 | EndGlobal 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Building ASP.NET Core Apps with Clean Architecture](https://www.ezzylearning.net/tutorial/building-asp-net-core-apps-with-clean-architecture) 2 | 3 | ![Building ASP.NET Core Apps with Clean Architecture](https://www.ezzylearning.net/wp-content/uploads/Building-ASP.NET-Core-Apps-with-Clean-Architecture.png) 4 | 5 | [Read Full Article](https://www.ezzylearning.net/tutorial/building-asp-net-core-apps-with-clean-architecture) 6 | 7 | --------------------------------------------------------------------------------