diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/.gitignore b/csharp-architectural-patterns/VerticalSliceArchitecture/.gitignore new file mode 100644 index 0000000000..1998dc0e60 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/.gitignore @@ -0,0 +1,3 @@ +tickets.db +tickets.db-shm +tickets.db-wal diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/EventTicketing.Tests.csproj b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/EventTicketing.Tests.csproj new file mode 100644 index 0000000000..9530da9bd1 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/EventTicketing.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + + diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/ReserveTicketsTests.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/ReserveTicketsTests.cs new file mode 100644 index 0000000000..76b88cb16d --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/ReserveTicketsTests.cs @@ -0,0 +1,55 @@ +using EventTicketing.Data; +using EventTicketing.Features.Reservations; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace EventTicketing.Tests; + +public sealed class ReserveTicketsTests : IDisposable +{ + private readonly SqliteConnection _connection = new("Data Source=:memory:"); + + public ReserveTicketsTests() => _connection.Open(); + + public void Dispose() => _connection.Dispose(); + + private TicketingDbContext CreateDbContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlite(_connection) + .Options; + + var dbContext = new TicketingDbContext(options); + dbContext.Database.EnsureCreated(); + + return dbContext; + } + + [Fact] + public async Task HandleAsync_WhenTicketsAreAvailable_SavesTheReservation() + { + var handler = new ReserveTickets.Handler(CreateDbContext()); + + var result = await handler.HandleAsync(eventId: 1, quantity: 3, TestContext.Current.CancellationToken); + + Assert.True(result.IsSuccess); + Assert.Equal(97, result.Value.TicketsLeft); + + var saved = await CreateDbContext().Events.SingleAsync(e => e.Id == 1, TestContext.Current.CancellationToken); + Assert.Equal(3, saved.TicketsSold); + } + + [Fact] + public async Task HandleAsync_WhenTheEventIsSoldOut_SavesNothing() + { + var handler = new ReserveTickets.Handler(CreateDbContext()); + + var result = await handler.HandleAsync(eventId: 2, quantity: 3, TestContext.Current.CancellationToken); + + Assert.False(result.IsSuccess); + Assert.Equal("Event.SoldOut", result.Error!.Code); + + var saved = await CreateDbContext().Events.SingleAsync(e => e.Id == 2, TestContext.Current.CancellationToken); + Assert.Equal(0, saved.TicketsSold); + } +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/SliceTests.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/SliceTests.cs new file mode 100644 index 0000000000..9bdd84d73e --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.Tests/SliceTests.cs @@ -0,0 +1,35 @@ +using EventTicketing.Features.Reservations; +using NetArchTest.Rules; + +namespace EventTicketing.Tests; + +public class SliceTests +{ + [Fact] + public void Features_DoNotDependOnEachOther() + { + var assembly = typeof(ReserveTickets).Assembly; + + var features = assembly.GetTypes() + .Select(t => t.Namespace) + .OfType() + .Where(ns => ns.StartsWith("EventTicketing.Features.")) + .Distinct() + .ToArray(); + + foreach (var feature in features) + { + var otherFeatures = features.Where(f => f != feature).ToArray(); + + var result = Types.InAssembly(assembly) + .That() + .ResideInNamespace(feature) + .ShouldNot() + .HaveDependencyOnAny(otherFeatures) + .GetResult(); + + Assert.True(result.IsSuccessful, + $"{feature} depends on another feature: " + string.Join(", ", result.FailingTypeNames ?? [])); + } + } +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.sln b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.sln new file mode 100644 index 0000000000..9eb5cb74d7 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing", "EventTicketing\EventTicketing.csproj", "{5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.Tests", "EventTicketing.Tests\EventTicketing.Tests.csproj", "{4B09EE23-0672-4FE0-8078-430028A5351E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|x64.Build.0 = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Debug|x86.Build.0 = Debug|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|Any CPU.Build.0 = Release|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|x64.ActiveCfg = Release|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|x64.Build.0 = Release|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|x86.ActiveCfg = Release|Any CPU + {5FAF63F8-89C3-4EEE-A3A0-DCB365CA69B6}.Release|x86.Build.0 = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|x64.ActiveCfg = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|x64.Build.0 = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Debug|x86.Build.0 = Debug|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|Any CPU.Build.0 = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|x64.ActiveCfg = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|x64.Build.0 = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|x86.ActiveCfg = Release|Any CPU + {4B09EE23-0672-4FE0-8078-430028A5351E}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Data/TicketingDbContext.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Data/TicketingDbContext.cs new file mode 100644 index 0000000000..33431ebab6 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Data/TicketingDbContext.cs @@ -0,0 +1,22 @@ +using EventTicketing.Domain; +using Microsoft.EntityFrameworkCore; + +namespace EventTicketing.Data; + +public sealed class TicketingDbContext(DbContextOptions options) + : DbContext(options) +{ + public DbSet Events => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property(e => e.Name).HasMaxLength(200); + + builder.HasData( + new { Id = 1, Name = "Vertical Slice Live", Capacity = 100, TicketsSold = 0 }, + new { Id = 2, Name = "Tiny Jazz Club Night", Capacity = 2, TicketsSold = 0 }); + }); + } +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Event.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Event.cs new file mode 100644 index 0000000000..8c2b7c2eda --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Event.cs @@ -0,0 +1,37 @@ +namespace EventTicketing.Domain; + +public sealed class Event +{ + private Event(string name, int capacity) + { + Name = name; + Capacity = capacity; + } + + public int Id { get; private set; } + public string Name { get; private set; } + public int Capacity { get; private set; } + public int TicketsSold { get; private set; } + + public int TicketsLeft => Capacity - TicketsSold; + + public static Event Create(string name, int capacity) + { + ArgumentException.ThrowIfNullOrWhiteSpace(name); + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); + + return new Event(name, capacity); + } + + public Result Reserve(int quantity) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(quantity); + + if (quantity > TicketsLeft) + return EventErrors.SoldOut(TicketsLeft, quantity); + + TicketsSold += quantity; + + return Result.Success(); + } +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/EventErrors.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/EventErrors.cs new file mode 100644 index 0000000000..24d232a0ca --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/EventErrors.cs @@ -0,0 +1,10 @@ +namespace EventTicketing.Domain; + +public static class EventErrors +{ + public static Error NotFound(int eventId) => + new("Event.NotFound", $"Event {eventId} was not found.", ErrorType.NotFound); + + public static Error SoldOut(int ticketsLeft, int requested) => + new("Event.SoldOut", $"Only {ticketsLeft} tickets left, {requested} requested.", ErrorType.Conflict); +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Result.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Result.cs new file mode 100644 index 0000000000..7f406bb586 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Domain/Result.cs @@ -0,0 +1,39 @@ +namespace EventTicketing.Domain; + +public enum ErrorType +{ + NotFound, + Conflict +} + +public sealed record Error(string Code, string Description, ErrorType Type); + +public class Result +{ + protected Result(Error? error) => Error = error; + + public Error? Error { get; } + public bool IsSuccess => Error is null; + + public static Result Success() => new(null); + + public static implicit operator Result(Error error) => new(error); +} + +public sealed class Result : Result +{ + private readonly TValue? _value; + + private Result(TValue? value, Error? error) + : base(error) => _value = value; + + public TValue Value => IsSuccess + ? _value! + : throw new InvalidOperationException("A failed result has no value."); + + public static implicit operator Result(TValue value) => + new(value, null); + + public static implicit operator Result(Error error) => + new(default, error); +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.csproj b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.csproj new file mode 100644 index 0000000000..9361cc4861 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.http b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.http new file mode 100644 index 0000000000..09a304f51a --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/EventTicketing.http @@ -0,0 +1,37 @@ +GET http://localhost:5000/api/events/1 + +### + +POST http://localhost:5000/api/events/1/reservations +Content-Type: application/json + +{ "quantity": 3 } + +### + +POST http://localhost:5000/api/events/2/reservations +Content-Type: application/json + +{ "quantity": 3 } + +### + +POST http://localhost:5000/api/events/1/reservations +Content-Type: application/json + +{ "quantity": 0 } + +### + +GET http://localhost:5000/api/events/99 + +### + +POST http://localhost:5000/api/events/2/reservations +Content-Type: application/json + +{ "quantity": 2 } + +### + +GET http://localhost:5000/api/events diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/GetEventAvailability.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/GetEventAvailability.cs new file mode 100644 index 0000000000..90df16bb21 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/GetEventAvailability.cs @@ -0,0 +1,39 @@ +using EventTicketing.Data; +using EventTicketing.Domain; +using Microsoft.EntityFrameworkCore; + +namespace EventTicketing.Features.Events; + +public static class GetEventAvailability +{ + public sealed record Response(int EventId, string Name, int TicketsLeft); + + public sealed class Handler(TicketingDbContext dbContext) + { + public async Task> HandleAsync( + int eventId, CancellationToken cancellationToken = default) + { + var availability = await dbContext.Events + .AsNoTracking() + .Where(e => e.Id == eventId) + .Select(e => new Response(e.Id, e.Name, e.Capacity - e.TicketsSold)) + .FirstOrDefaultAsync(cancellationToken); + + if (availability is null) + return EventErrors.NotFound(eventId); + + return availability; + } + } + + public static void MapEndpoint(IEndpointRouteBuilder app) => + app.MapGet("/{eventId:int}", async ( + int eventId, + Handler handler, + CancellationToken cancellationToken) => + { + var result = await handler.HandleAsync(eventId, cancellationToken); + + return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblem(); + }); +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/ListAvailableEvents.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/ListAvailableEvents.cs new file mode 100644 index 0000000000..15bb2ac737 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Events/ListAvailableEvents.cs @@ -0,0 +1,17 @@ +using EventTicketing.Data; +using Microsoft.EntityFrameworkCore; + +namespace EventTicketing.Features.Events; + +public static class ListAvailableEvents +{ + public sealed record AvailableEvent(int EventId, string Name, int TicketsLeft); + + public static void MapEndpoint(IEndpointRouteBuilder app) => + app.MapGet("/", async (TicketingDbContext dbContext, CancellationToken cancellationToken) => + await dbContext.Events + .AsNoTracking() + .Where(e => e.TicketsSold < e.Capacity) + .Select(e => new AvailableEvent(e.Id, e.Name, e.Capacity - e.TicketsSold)) + .ToListAsync(cancellationToken)); +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Reservations/ReserveTickets.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Reservations/ReserveTickets.cs new file mode 100644 index 0000000000..b9efe5949e --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Features/Reservations/ReserveTickets.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; +using EventTicketing.Data; +using EventTicketing.Domain; +using Microsoft.EntityFrameworkCore; + +namespace EventTicketing.Features.Reservations; + +public static class ReserveTickets +{ + public sealed record Request([property: Range(1, 20)] int Quantity); + + public sealed record Response(int EventId, int TicketsReserved, int TicketsLeft); + + public sealed class Handler(TicketingDbContext dbContext) + { + public async Task> HandleAsync( + int eventId, int quantity, CancellationToken cancellationToken = default) + { + var ev = await dbContext.Events.FirstOrDefaultAsync(e => e.Id == eventId, cancellationToken); + if (ev is null) + return EventErrors.NotFound(eventId); + + var reservation = ev.Reserve(quantity); + if (!reservation.IsSuccess) + return reservation.Error!; + + await dbContext.SaveChangesAsync(cancellationToken); + + return new Response(ev.Id, quantity, ev.TicketsLeft); + } + } + + public static void MapEndpoint(IEndpointRouteBuilder app) => + app.MapPost("/{eventId:int}/reservations", async ( + int eventId, + Request request, + Handler handler, + CancellationToken cancellationToken) => + { + var result = await handler.HandleAsync(eventId, request.Quantity, cancellationToken); + + return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblem(); + }); +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Program.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Program.cs new file mode 100644 index 0000000000..a6cac08a41 --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Program.cs @@ -0,0 +1,33 @@ +using EventTicketing.Data; +using EventTicketing.Features.Events; +using EventTicketing.Features.Reservations; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDbContext(options => + options.UseSqlite(builder.Configuration.GetConnectionString("Ticketing"))); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +builder.Services.AddProblemDetails(); +builder.Services.AddValidation(); + +var app = builder.Build(); + +app.UseExceptionHandler(); + +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.EnsureCreated(); +} + +var events = app.MapGroup("/api/events"); + +GetEventAvailability.MapEndpoint(events); +ReserveTickets.MapEndpoint(events); +ListAvailableEvents.MapEndpoint(events); + +app.Run(); diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Properties/launchSettings.json b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Properties/launchSettings.json similarity index 50% rename from csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Properties/launchSettings.json rename to csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Properties/launchSettings.json index b3107716ee..b0ac7136aa 100644 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Properties/launchSettings.json +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/Properties/launchSettings.json @@ -1,28 +1,20 @@ -{ +{ "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:47567", - "sslPort": 0 - } - }, "profiles": { - "VerticalSliceArchitecture": { + "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "https://localhost:5216", + "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, - "IIS Express": { - "commandName": "IISExpress", + "https": { + "commandName": "Project", + "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "applicationUrl": "https://localhost:7052;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/ResultExtensions.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/ResultExtensions.cs new file mode 100644 index 0000000000..6b97d710fb --- /dev/null +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/ResultExtensions.cs @@ -0,0 +1,21 @@ +using EventTicketing.Domain; + +namespace EventTicketing; + +public static class ResultExtensions +{ + public static IResult ToProblem(this Result result) + { + var error = result.Error + ?? throw new InvalidOperationException("A successful result is not a problem."); + + var statusCode = error.Type switch + { + ErrorType.NotFound => StatusCodes.Status404NotFound, + ErrorType.Conflict => StatusCodes.Status409Conflict, + _ => StatusCodes.Status500InternalServerError + }; + + return TypedResults.Problem(statusCode: statusCode, title: error.Code, detail: error.Description); + } +} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/appsettings.Development.json b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/appsettings.Development.json similarity index 100% rename from csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/appsettings.Development.json rename to csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/appsettings.Development.json diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/appsettings.json b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/appsettings.json similarity index 66% rename from csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/appsettings.json rename to csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/appsettings.json index 10f68b8c8b..f0d3f98f07 100644 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/appsettings.json +++ b/csharp-architectural-patterns/VerticalSliceArchitecture/EventTicketing/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "Ticketing": "Data Source=tickets.db" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/ConsoleServiceTest.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/ConsoleServiceTest.cs deleted file mode 100644 index 54b0885dd1..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/ConsoleServiceTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace Tests -{ - public class ConsoleServiceTest - { - private DataContext _context; - - [Fact] - public async Task WhenGetAllConsoles_ThenConsolesAreReturnedSuccessfully() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("consoleDB1") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - await _context.SaveChangesAsync(); - - //When - var consoles = await new ConsoleService(_context).GetAllConsolesAsync(); - - //Then - Assert.NotNull(consoles); - - Assert.True(consoles.Any()); - } - - [Fact] - public async Task WhenGetConsoleById_ThenSpecificConsoleIsReturned() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("consoleDB2") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - await _context.SaveChangesAsync(); - - //When - var console = await new ConsoleService(_context).GetConsoleByIdAsync(1); - - await _context.SaveChangesAsync(); - - //Then - Assert.NotNull(console); - - Assert.True(console.Id == 1); - } - } -} \ No newline at end of file diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/GameServiceTest.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/GameServiceTest.cs deleted file mode 100644 index bd663da02e..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/GameServiceTest.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace Tests -{ - public class GameServiceTest - { - private DataContext _context; - - [Fact] - public async Task WhenGameIsAdded_GameCreatedSuccessfully() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("gameDB1") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - //When - new GameService(_context).AddGameToConsole(1, new Game { Id = 10, Name = "Fighting Simulator", Publisher = "Best Fights Corp" }); - - await _context.SaveChangesAsync(); - - //Then - Assert.True(await _context.Games.FirstOrDefaultAsync() != null); - } - - [Fact] - public async Task WhenGetAllGames_ThenGamesAreReturnedSuccessfully() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("gameDB2") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - new GameService(_context).AddGameToConsole(1, new Game { Id = 10, Name = "Fighting Simulator", Publisher = "Best Fights Corp" }); - - await _context.SaveChangesAsync(); - - //When - var games = await new GameService(_context).GetAllGamesAsync(1); - - //Then - Assert.NotNull(games); - - Assert.True(games.Any()); - } - - [Fact] - public async Task WhenGetGameByID_ThenSpecifiedGameIsReturnedSuccessfully() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("gameDB3") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - new GameService(_context).AddGameToConsole(1, new Game { Id = 12, Name = "Running Simulator", Publisher = "Best Runs Corp" }); - - await _context.SaveChangesAsync(); - - //When - var game = await new GameService(_context).GetGameAsync(1, 12); - - //Then - Assert.NotNull(game); - - Assert.True(game.Id == 12); - } - - [Fact] - public async Task WhenDeletingAGame_ThenSpecifiedGameIsDeletedSuccessfully() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase("gameDB4") - .Options; - - _context = new DataContext(options); - - _context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - var newGame = new Game { Id = 13, Name = "Game Dev Simulator", Publisher = "Worst Games Corp" }; - - new GameService(_context).AddGameToConsole(1, newGame); - - await _context.SaveChangesAsync(); - - //When - new GameService(_context).DeleteGame(newGame); - - await _context.SaveChangesAsync(); - - //Then - - var deletedGame = await _context.Games.FirstOrDefaultAsync(x => x.Id == 13); - - Assert.Null(deletedGame); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Tests.csproj b/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Tests.csproj deleted file mode 100644 index 0f65eb1ff5..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - - false - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Usings.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Usings.cs deleted file mode 100644 index 26a7334fba..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/Tests/Usings.cs +++ /dev/null @@ -1,6 +0,0 @@ -global using Microsoft.EntityFrameworkCore; -global using VerticalSliceArchitecture.Data; -global using VerticalSliceArchitecture.Domain; -global using VerticalSliceArchitecture.Features.Consoles; -global using VerticalSliceArchitecture.Features.Games; -global using Xunit; diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture.sln b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture.sln deleted file mode 100644 index 0c9400484e..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32516.85 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VerticalSliceArchitecture", "VerticalSliceArchitecture\VerticalSliceArchitecture.csproj", "{B461B03F-664F-422A-993B-79AC4C6FCF1D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{0E1829EA-90EC-47FC-B7F9-47B4C31AA54B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B461B03F-664F-422A-993B-79AC4C6FCF1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B461B03F-664F-422A-993B-79AC4C6FCF1D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B461B03F-664F-422A-993B-79AC4C6FCF1D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B461B03F-664F-422A-993B-79AC4C6FCF1D}.Release|Any CPU.Build.0 = Release|Any CPU - {0E1829EA-90EC-47FC-B7F9-47B4C31AA54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E1829EA-90EC-47FC-B7F9-47B4C31AA54B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E1829EA-90EC-47FC-B7F9-47B4C31AA54B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E1829EA-90EC-47FC-B7F9-47B4C31AA54B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {938CAD25-5CDA-4BEB-A011-3EA89D816224} - EndGlobalSection -EndGlobal diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/DataContext.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/DataContext.cs deleted file mode 100644 index fff48a5d33..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/DataContext.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Data -{ - public class DataContext : DbContext - { - public DataContext(DbContextOptions options) : base(options) { } - public DbSet Consoles { get; set; } - public DbSet Games { get; set; } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/Seed.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/Seed.cs deleted file mode 100644 index 81df2bb323..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Data/Seed.cs +++ /dev/null @@ -1,61 +0,0 @@ -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Data -{ - public class Seed - { - public void SeedData(DataContext context) - { - //Seeding Consoles - - context.Consoles.Add(new GameConsole - { - Id = 1, - Name = "Xbox Series X", - Manufacturer = "Microsoft" - }); - - context.Consoles.Add(new GameConsole - { - Id = 2, - Name = "PlayStation 5", - Manufacturer = "Sony" - }); - - context.Consoles.Add(new GameConsole - { - Id = 3, - Name = "Nintendo Switch", - Manufacturer = "Nintendo" - }); - - //Seeding Games - - context.Games.Add(new Game - { - Id = 10, - Name = "Horizon Forbidden West", - Publisher = "Sony Interactive Entertainment", - ConsoleId = 2 - }); - - context.Games.Add(new Game - { - Id = 11, - Name = "Animal Crossing: New Horizons", - Publisher = "Nintendo", - ConsoleId = 3 - }); - - context.Games.Add(new Game - { - Id = 12, - Name = "Halo Infinite", - Publisher = "Xbox Game Studios", - ConsoleId = 1 - }); - - context.SaveChanges(); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/Game.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/Game.cs deleted file mode 100644 index 6648a7771f..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/Game.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace VerticalSliceArchitecture.Domain -{ - public class Game - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Publisher { get; set; } - public int ConsoleId { get; set; } - public GameConsole? Console { get; set; } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/GameConsole.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/GameConsole.cs deleted file mode 100644 index 8372cb0d7e..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Domain/GameConsole.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace VerticalSliceArchitecture.Domain -{ - public class GameConsole - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Manufacturer { get; set; } - public ICollection? Games { get; set; } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsoleService.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsoleService.cs deleted file mode 100644 index 8f795e3243..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsoleService.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using VerticalSliceArchitecture.Data; -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Consoles -{ - public class ConsoleService : IConsoleService - { - private readonly DataContext _context; - - public ConsoleService(DataContext context) - { - _context = context; - } - - public async Task> GetAllConsolesAsync() - { - return await _context.Consoles - .OrderBy(x => x.Id) - .ToListAsync(); - } - - public async Task GetConsoleByIdAsync(int consoleId) - { - return await _context.Consoles - .FirstOrDefaultAsync(x => x.Id == consoleId); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsolesController.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsolesController.cs deleted file mode 100644 index c75a8c6d88..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/ConsolesController.cs +++ /dev/null @@ -1,27 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Mvc; - -namespace VerticalSliceArchitecture.Features.Consoles -{ - [Route("api/[controller]")] - [ApiController] - public class ConsolesController : ControllerBase - { - private readonly IMediator _mediator; - public ConsolesController(IMediator mediator) - { - _mediator = mediator; - } - - [HttpGet] - public async Task>> GetConsolesAsync() - { - var result = await _mediator.Send(new GetAllConsoles.GetConsolesQuery()); - - if (result is null) - return NotFound(); - - return Ok(result); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/GetAllConsoles.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/GetAllConsoles.cs deleted file mode 100644 index 9857427aed..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/GetAllConsoles.cs +++ /dev/null @@ -1,41 +0,0 @@ -using AutoMapper; -using MediatR; -using VerticalSliceArchitecture.ServiceManager; - -namespace VerticalSliceArchitecture.Features.Consoles -{ - public class GetAllConsoles - { - //Input - public class GetConsolesQuery : IRequest> { } - - //Output - public class ConsoleResult - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Manufacturer { get; set; } - } - - //Handler - public class Handler : IRequestHandler> - { - private readonly IServiceManager _serviceManager; - private readonly IMapper _mapper; - - public Handler(IServiceManager serviceManager, IMapper mapper) - { - _serviceManager = serviceManager; - _mapper = mapper; - } - - public async Task> Handle(GetConsolesQuery request, CancellationToken cancellationToken) - { - var consoles = await _serviceManager.Console.GetAllConsolesAsync(); - var results = _mapper.Map>(consoles); - - return results; - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/IConsoleService.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/IConsoleService.cs deleted file mode 100644 index 18b3f810ac..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/IConsoleService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Consoles -{ - public interface IConsoleService - { - Task> GetAllConsolesAsync(); - Task GetConsoleByIdAsync(int consoleId); - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/MapperProfile.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/MapperProfile.cs deleted file mode 100644 index e18ebbff8a..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Consoles/MapperProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Consoles -{ - public class MapperProfile : Profile - { - public MapperProfile() - { - CreateMap(); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/AddGameToConsole.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/AddGameToConsole.cs deleted file mode 100644 index 57907817fd..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/AddGameToConsole.cs +++ /dev/null @@ -1,64 +0,0 @@ -using AutoMapper; -using MediatR; -using VerticalSliceArchitecture.Domain; -using VerticalSliceArchitecture.Features.Games.Exceptions; -using VerticalSliceArchitecture.ServiceManager; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class AddGameToConsole - { - //Input - public class AddGameCommand : IRequest - { - public string? Name { get; set; } - public string? Publisher { get; set; } - public int ConsoleId { get; set; } - } - - //Output - public class GameResult - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Publisher { get; set; } - public int ConsoleId { get; set; } - } - - //Handler - public class Handler : IRequestHandler - { - private readonly IServiceManager _serviceManager; - private readonly IMapper _mapper; - - public Handler(IServiceManager serviceManager, IMapper mapper) - { - _serviceManager = serviceManager; - _mapper = mapper; - } - - public async Task Handle(AddGameCommand request, CancellationToken cancellationToken) - { - var console = await _serviceManager.Console.GetConsoleByIdAsync(request.ConsoleId); - - if (console is null) - throw new NoConsoleExistsException(request.ConsoleId); - - var game = new Game() - { - Name = request.Name, - Publisher = request.Publisher, - ConsoleId = request.ConsoleId - }; - - _serviceManager.Game.AddGameToConsole(request.ConsoleId, game); - - await _serviceManager.SaveAsync(); - - var result = _mapper.Map(game); - - return result; - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoConsoleExistsException.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoConsoleExistsException.cs deleted file mode 100644 index 0611c69373..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoConsoleExistsException.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace VerticalSliceArchitecture.Features.Games.Exceptions -{ - public class NoConsoleExistsException : Exception - { - public NoConsoleExistsException(int consoleId) : base($"Console with id: {consoleId} doesn't exist.") { } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoGameExistsException.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoGameExistsException.cs deleted file mode 100644 index 98fb65fceb..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/Exceptions/NoGameExistsException.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace VerticalSliceArchitecture.Features.Games.Exceptions -{ - public class NoGameExistsException : Exception - { - public NoGameExistsException(int consoleId, int gameId) : base($"Game with id: {gameId} doesn't exist for console id {consoleId}") - { - ConsoleId = consoleId; - GameId = gameId; - } - - public int ConsoleId { get; set; } - public int GameId { get; set; } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GameService.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GameService.cs deleted file mode 100644 index 36ce4932bf..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GameService.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using VerticalSliceArchitecture.Data; -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class GameService : IGameService - { - private readonly DataContext _context; - - public GameService(DataContext context) - { - _context = context; - } - public void AddGameToConsole(int consoleId, Game game) - { - game.ConsoleId = consoleId; - - _context.Games.Add(game); - } - - public void DeleteGame(Game game) - { - _context.Games.Remove(game); - } - - public async Task> GetAllGamesAsync(int consoleId) - { - return await _context.Games - .Where(x => x.ConsoleId == consoleId) - .ToListAsync(); - } - - public async Task GetGameAsync(int consoleId, int gameId) - { - return await _context.Games - .FirstOrDefaultAsync(x => x.ConsoleId == consoleId && x.Id == gameId); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GamesController.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GamesController.cs deleted file mode 100644 index 92097d8b28..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GamesController.cs +++ /dev/null @@ -1,120 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Mvc; -using VerticalSliceArchitecture.Features.Games.Exceptions; -using static VerticalSliceArchitecture.Features.Games.GetAllGamesForConsole; -using static VerticalSliceArchitecture.Features.Games.RemoveGameFromConsole; -using static VerticalSliceArchitecture.Features.Games.UpdateGameForConsole; - -namespace VerticalSliceArchitecture.Features.Games -{ - [Route("api/[controller]")] - [ApiController] - public class GamesController : ControllerBase - { - private readonly IMediator _mediator; - - public GamesController(IMediator mediator) - { - _mediator = mediator; - } - - [HttpGet(Name = "GetGamesForConsole")] - public async Task>> GetGamesForConsole(int consoleId) - { - try - { - var query = new GetGamesQuery - { - ConsoleId = consoleId - }; - - var result = await _mediator.Send(query); - - return Ok(result); - } - catch (NoConsoleExistsException ex) - { - return Conflict(new - { - ex.Message - }); - } - } - - [HttpPost] - public async Task AddGame(AddGameToConsole.AddGameCommand command) - { - try - { - var result = await _mediator.Send(command); - - return CreatedAtRoute("GetGamesForConsole", new { consoleId = result.ConsoleId }, result); - } - catch (NoConsoleExistsException ex) - { - return Conflict(new - { - ex.Message - }); - } - } - - [HttpPut] - public async Task UpdateGameForConsole(int consoleId, UpdateGameCommand command) - { - try - { - command.ConsoleId = consoleId; - - await _mediator.Send(command); - - return NoContent(); - } - catch (NoConsoleExistsException ex) - { - return Conflict(new - { - ex.Message - }); - } - catch (NoGameExistsException ex) - { - return Conflict(new - { - ex.Message, - ex.ConsoleId, - ex.GameId - }); - } - } - - [HttpDelete] - public async Task RemoveGameFromConsole(int consoleId, RemoveGameCommand command) - { - try - { - command.ConsoleId = consoleId; - - await _mediator.Send(command); - - return NoContent(); - } - catch (NoConsoleExistsException ex) - { - return Conflict(new - { - ex.Message - }); - } - catch (NoGameExistsException ex) - { - return Conflict(new - { - ex.Message, - ex.ConsoleId, - ex.GameId - }); - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GetAllGamesForConsole.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GetAllGamesForConsole.cs deleted file mode 100644 index 01f966e4da..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/GetAllGamesForConsole.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AutoMapper; -using MediatR; -using VerticalSliceArchitecture.Features.Games.Exceptions; -using VerticalSliceArchitecture.ServiceManager; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class GetAllGamesForConsole - { - public class GetGamesQuery : IRequest> - { - public int ConsoleId { get; set; } - } - - public class GameResult - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Publisher { get; set; } - } - - public class Handler : IRequestHandler> - { - private readonly IServiceManager _serviceManager; - private readonly IMapper _mapper; - - public Handler(IServiceManager serviceManager, IMapper mapper) - { - _serviceManager = serviceManager; - _mapper = mapper; - } - - public async Task> Handle(GetGamesQuery request, CancellationToken cancellationToken) - { - var console = await _serviceManager.Console.GetConsoleByIdAsync(request.ConsoleId); - - if (console is null) - throw new NoConsoleExistsException(request.ConsoleId); - - var games = await _serviceManager.Game.GetAllGamesAsync(console.Id); - - var result = _mapper.Map>(games); - - return result; - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/IGameService.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/IGameService.cs deleted file mode 100644 index eead99ea99..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/IGameService.cs +++ /dev/null @@ -1,12 +0,0 @@ -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Games -{ - public interface IGameService - { - Task> GetAllGamesAsync(int consoleId); - Task GetGameAsync(int consoleId, int gameId); - void AddGameToConsole(int consoleId, Game game); - void DeleteGame(Game game); - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/MapperProfile.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/MapperProfile.cs deleted file mode 100644 index 0c6f42f952..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/MapperProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using VerticalSliceArchitecture.Domain; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class MapperProfile : Profile - { - public MapperProfile() - { - CreateMap(); - CreateMap(); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/RemoveGameFromConsole.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/RemoveGameFromConsole.cs deleted file mode 100644 index 1e5867c216..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/RemoveGameFromConsole.cs +++ /dev/null @@ -1,44 +0,0 @@ -using MediatR; -using VerticalSliceArchitecture.Features.Games.Exceptions; -using VerticalSliceArchitecture.ServiceManager; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class RemoveGameFromConsole - { - public class RemoveGameCommand : IRequest - { - public int ConsoleId { get; set; } - public int GameId { get; set; } - } - - public class Handler : IRequestHandler - { - private readonly IServiceManager _serviceManager; - - public Handler(IServiceManager serviceManager) - { - _serviceManager = serviceManager; - } - - public async Task Handle(RemoveGameCommand request, CancellationToken cancellationToken) - { - var console = await _serviceManager.Console.GetConsoleByIdAsync(request.ConsoleId); - - if (console is null) - throw new NoConsoleExistsException(request.ConsoleId); - - var game = await _serviceManager.Game.GetGameAsync(request.ConsoleId, request.GameId); - - if (game is null) - throw new NoGameExistsException(request.ConsoleId, request.GameId); - - _serviceManager.Game.DeleteGame(game); - - await _serviceManager.SaveAsync(); - - return; - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/UpdateGameForConsole.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/UpdateGameForConsole.cs deleted file mode 100644 index 472ac9d6ad..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Features/Games/UpdateGameForConsole.cs +++ /dev/null @@ -1,48 +0,0 @@ -using AutoMapper; -using MediatR; -using VerticalSliceArchitecture.Features.Games.Exceptions; -using VerticalSliceArchitecture.ServiceManager; - -namespace VerticalSliceArchitecture.Features.Games -{ - public class UpdateGameForConsole - { - public class UpdateGameCommand : IRequest - { - public int Id { get; set; } - public string? Name { get; set; } - public string? Publisher { get; set; } - public int ConsoleId { get; set; } - } - - public class Handler : IRequestHandler - { - private readonly IServiceManager _serviceManager; - - public Handler(IServiceManager serviceManager) - { - _serviceManager = serviceManager; - } - - public async Task Handle(UpdateGameCommand request, CancellationToken cancellationToken) - { - var console = await _serviceManager.Console.GetConsoleByIdAsync(request.ConsoleId); - - if (console is null) - throw new NoConsoleExistsException(request.ConsoleId); - - var game = await _serviceManager.Game.GetGameAsync(request.ConsoleId, request.Id); - - if (game is null) - throw new NoGameExistsException(request.ConsoleId, request.Id); - - game.Name = request.Name!; - game.Publisher = request.Publisher!; - - await _serviceManager.SaveAsync(); - - return; - } - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Program.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Program.cs deleted file mode 100644 index 6016bbdfc0..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using MediatR; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using VerticalSliceArchitecture.Data; -using VerticalSliceArchitecture.ServiceManager; - -var builder = WebApplication.CreateBuilder(args); - -// Add services to the container. - -builder.Services.AddAutoMapper(typeof(Program).Assembly); -builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)); - -builder.Services.AddDbContext(options => -{ - options.UseInMemoryDatabase("GamingDB") - .ConfigureWarnings(builder => builder.Ignore(InMemoryEventId.TransactionIgnoredWarning)); -}); - -builder.Services.AddScoped(); - -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -using (var scope = app.Services.CreateScope()) -{ - var dataContext = scope.ServiceProvider.GetRequiredService(); - new Seed().SeedData(dataContext); -} - -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/IServiceManager.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/IServiceManager.cs deleted file mode 100644 index ba4e3aec7a..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/IServiceManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using VerticalSliceArchitecture.Features.Consoles; -using VerticalSliceArchitecture.Features.Games; - -namespace VerticalSliceArchitecture.ServiceManager -{ - public interface IServiceManager - { - IConsoleService Console { get; } - IGameService Game { get; } - Task SaveAsync(); - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/ServiceManager.cs b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/ServiceManager.cs deleted file mode 100644 index 1d888665c4..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/ServiceManager/ServiceManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -using VerticalSliceArchitecture.Data; -using VerticalSliceArchitecture.Features.Consoles; -using VerticalSliceArchitecture.Features.Games; - -namespace VerticalSliceArchitecture.ServiceManager -{ - public class ServiceManager : IServiceManager - { - private readonly DataContext _context; - private IConsoleService _consoleService; - private IGameService _gameService; - - public ServiceManager(DataContext context) - { - _context = context; - } - - public IConsoleService Console - { - get - { - if (_consoleService == null) - _consoleService = new ConsoleService(_context); - - return _consoleService; - } - } - - public IGameService Game - { - get - { - if (_gameService == null) - _gameService = new GameService(_context); - - return _gameService; - } - } - - public Task SaveAsync() - { - return _context.SaveChangesAsync(); - } - } -} diff --git a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/VerticalSliceArchitecture.csproj b/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/VerticalSliceArchitecture.csproj deleted file mode 100644 index 365dcd1c6a..0000000000 --- a/csharp-architectural-patterns/VerticalSliceArchitecture/VerticalSliceArchitecture/VerticalSliceArchitecture.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - -