diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Contracts/ReserveTicketsRequest.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Contracts/ReserveTicketsRequest.cs
new file mode 100644
index 0000000000..43839a1b5c
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Contracts/ReserveTicketsRequest.cs
@@ -0,0 +1,5 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace EventTicketing.Api.Contracts;
+
+public sealed record ReserveTicketsRequest([property: Range(1, 20)] int Quantity);
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Endpoints/EventEndpoints.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Endpoints/EventEndpoints.cs
new file mode 100644
index 0000000000..0f649dc290
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Endpoints/EventEndpoints.cs
@@ -0,0 +1,37 @@
+using EventTicketing.Api.Contracts;
+using EventTicketing.Api.Extensions;
+using EventTicketing.Application.Events;
+
+namespace EventTicketing.Api.Endpoints;
+
+public static class EventEndpoints
+{
+ public static IEndpointRouteBuilder MapEventEndpoints(this IEndpointRouteBuilder app)
+ {
+ var events = app.MapGroup("/api/events");
+
+ events.MapGet("/{eventId:int}", async (
+ int eventId,
+ GetEventAvailabilityHandler handler,
+ CancellationToken cancellationToken) =>
+ {
+ var result = await handler.HandleAsync(new GetEventAvailabilityQuery(eventId), cancellationToken);
+
+ return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblem();
+ });
+
+ events.MapPost("/{eventId:int}/reservations", async (
+ int eventId,
+ ReserveTicketsRequest request,
+ ReserveTicketsHandler handler,
+ CancellationToken cancellationToken) =>
+ {
+ var command = new ReserveTicketsCommand(eventId, request.Quantity);
+ var result = await handler.HandleAsync(command, cancellationToken);
+
+ return result.IsSuccess ? Results.Ok(result.Value) : result.ToProblem();
+ });
+
+ return app;
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/EventTicketing.Api.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/EventTicketing.Api.csproj
new file mode 100644
index 0000000000..08002b12d5
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/EventTicketing.Api.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Extensions/ResultExtensions.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Extensions/ResultExtensions.cs
new file mode 100644
index 0000000000..8e65ecdaba
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Extensions/ResultExtensions.cs
@@ -0,0 +1,25 @@
+using EventTicketing.Domain.Common;
+
+namespace EventTicketing.Api.Extensions;
+
+public static class ResultExtensions
+{
+ public static IResult ToProblem(this Result result)
+ {
+ if (result.IsSuccess)
+ throw new InvalidOperationException("A successful result is not a problem.");
+
+ var statusCode = result.Error.Type switch
+ {
+ ErrorType.Validation => StatusCodes.Status400BadRequest,
+ ErrorType.NotFound => StatusCodes.Status404NotFound,
+ ErrorType.Conflict => StatusCodes.Status409Conflict,
+ _ => StatusCodes.Status500InternalServerError
+ };
+
+ return TypedResults.Problem(
+ statusCode: statusCode,
+ title: result.Error.Code,
+ detail: result.Error.Description);
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Program.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Program.cs
new file mode 100644
index 0000000000..fcd3298901
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Program.cs
@@ -0,0 +1,27 @@
+using EventTicketing.Api.Endpoints;
+using EventTicketing.Application;
+using EventTicketing.Infrastructure;
+using EventTicketing.Infrastructure.Persistence;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddApplication();
+builder.Services.AddInfrastructure(builder.Configuration.GetConnectionString("Ticketing")!);
+
+builder.Services.AddProblemDetails();
+builder.Services.AddValidation();
+builder.Services.AddOpenApi();
+
+var app = builder.Build();
+
+app.UseExceptionHandler();
+
+if (app.Environment.IsDevelopment())
+{
+ app.MapOpenApi();
+ await app.Services.SeedDatabaseAsync();
+}
+
+app.MapEventEndpoints();
+
+app.Run();
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Properties/launchSettings.json b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Properties/launchSettings.json
new file mode 100644
index 0000000000..4fa2de2c5e
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/appsettings.json b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.Development.json
similarity index 80%
rename from csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/appsettings.json
rename to csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.Development.json
index 10f68b8c8b..0c208ae918 100644
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/appsettings.json
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.Development.json
@@ -4,6 +4,5 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
- },
- "AllowedHosts": "*"
+ }
}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.json b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.json
new file mode 100644
index 0000000000..f0d3f98f07
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Api/appsettings.json
@@ -0,0 +1,12 @@
+{
+ "ConnectionStrings": {
+ "Ticketing": "Data Source=tickets.db"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Abstractions/IUnitOfWork.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Abstractions/IUnitOfWork.cs
new file mode 100644
index 0000000000..c56b8fcf61
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Abstractions/IUnitOfWork.cs
@@ -0,0 +1,6 @@
+namespace EventTicketing.Application.Abstractions;
+
+public interface IUnitOfWork
+{
+ Task SaveChangesAsync(CancellationToken cancellationToken = default);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/DependencyInjection.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/DependencyInjection.cs
new file mode 100644
index 0000000000..66c8325f41
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/DependencyInjection.cs
@@ -0,0 +1,15 @@
+using EventTicketing.Application.Events;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace EventTicketing.Application;
+
+public static class DependencyInjection
+{
+ public static IServiceCollection AddApplication(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+
+ return services;
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoApp.Persistence.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/EventTicketing.Application.csproj
similarity index 54%
rename from csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoApp.Persistence.csproj
rename to csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/EventTicketing.Application.csproj
index 5173ed2f34..af1bc6ab45 100644
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoApp.Persistence.csproj
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/EventTicketing.Application.csproj
@@ -1,18 +1,17 @@
-
- net8.0
- enable
- enable
-
-
-
-
+
-
+
+
+ net10.0
+ enable
+ enable
+
+
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/GetEventAvailability.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/GetEventAvailability.cs
new file mode 100644
index 0000000000..b1ffb87d30
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/GetEventAvailability.cs
@@ -0,0 +1,21 @@
+using EventTicketing.Domain.Common;
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.Application.Events;
+
+public sealed record GetEventAvailabilityQuery(int EventId);
+
+public sealed record EventAvailabilityResponse(int EventId, string Name, int Capacity, int TicketsLeft);
+
+public sealed class GetEventAvailabilityHandler(IEventReadRepository reads)
+{
+ public async Task> HandleAsync(
+ GetEventAvailabilityQuery query, CancellationToken cancellationToken = default)
+ {
+ var availability = await reads.GetAvailabilityAsync(query.EventId, cancellationToken);
+ if (availability is null)
+ return EventErrors.NotFound(query.EventId);
+
+ return availability;
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventReadRepository.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventReadRepository.cs
new file mode 100644
index 0000000000..cda14d952d
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventReadRepository.cs
@@ -0,0 +1,7 @@
+namespace EventTicketing.Application.Events;
+
+public interface IEventReadRepository
+{
+ Task GetAvailabilityAsync(
+ int eventId, CancellationToken cancellationToken = default);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventRepository.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventRepository.cs
new file mode 100644
index 0000000000..024f94fe70
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/IEventRepository.cs
@@ -0,0 +1,8 @@
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.Application.Events;
+
+public interface IEventRepository
+{
+ Task GetByIdAsync(int eventId, CancellationToken cancellationToken = default);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/ReserveTickets.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/ReserveTickets.cs
new file mode 100644
index 0000000000..a00c951839
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Application/Events/ReserveTickets.cs
@@ -0,0 +1,28 @@
+using EventTicketing.Application.Abstractions;
+using EventTicketing.Domain.Common;
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.Application.Events;
+
+public sealed record ReserveTicketsCommand(int EventId, int Quantity);
+
+public sealed record ReservationResponse(int EventId, int TicketsReserved, int TicketsLeft);
+
+public sealed class ReserveTicketsHandler(IEventRepository events, IUnitOfWork unitOfWork)
+{
+ public async Task> HandleAsync(
+ ReserveTicketsCommand command, CancellationToken cancellationToken = default)
+ {
+ var ev = await events.GetByIdAsync(command.EventId, cancellationToken);
+ if (ev is null)
+ return EventErrors.NotFound(command.EventId);
+
+ var reservation = ev.Reserve(command.Quantity);
+ if (reservation.IsFailure)
+ return reservation.Error;
+
+ await unitOfWork.SaveChangesAsync(cancellationToken);
+
+ return new ReservationResponse(ev.Id, command.Quantity, ev.TicketsLeft);
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/DependencyRuleTests.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/DependencyRuleTests.cs
new file mode 100644
index 0000000000..76f3357484
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/DependencyRuleTests.cs
@@ -0,0 +1,50 @@
+using EventTicketing.Application.Events;
+using EventTicketing.Domain.Events;
+using EventTicketing.Infrastructure.Persistence;
+using NetArchTest.Rules;
+using TestResult = NetArchTest.Rules.TestResult;
+
+namespace EventTicketing.ArchitectureTests;
+
+public class DependencyRuleTests
+{
+ private const string Application = "EventTicketing.Application";
+ private const string Infrastructure = "EventTicketing.Infrastructure";
+ private const string Api = "EventTicketing.Api";
+
+ [Fact]
+ public void Domain_DependsOnNoOtherLayer()
+ {
+ var result = Types.InAssembly(typeof(Event).Assembly)
+ .ShouldNot()
+ .HaveDependencyOnAny(Application, Infrastructure, Api)
+ .GetResult();
+
+ Assert.True(result.IsSuccessful, Describe(result));
+ }
+
+ [Fact]
+ public void Application_DoesNotDependOnInfrastructureOrTheWeb()
+ {
+ var result = Types.InAssembly(typeof(ReserveTicketsHandler).Assembly)
+ .ShouldNot()
+ .HaveDependencyOnAny(Infrastructure, Api, "Microsoft.EntityFrameworkCore", "Microsoft.AspNetCore")
+ .GetResult();
+
+ Assert.True(result.IsSuccessful, Describe(result));
+ }
+
+ [Fact]
+ public void Infrastructure_DoesNotDependOnTheApi()
+ {
+ var result = Types.InAssembly(typeof(TicketingDbContext).Assembly)
+ .ShouldNot()
+ .HaveDependencyOn(Api)
+ .GetResult();
+
+ Assert.True(result.IsSuccessful, Describe(result));
+ }
+
+ private static string Describe(TestResult result) =>
+ "Offending types: " + string.Join(", ", result.FailingTypeNames ?? []);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/EventTicketing.ArchitectureTests.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/EventTicketing.ArchitectureTests.csproj
new file mode 100644
index 0000000000..1e027d8a6d
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.ArchitectureTests/EventTicketing.ArchitectureTests.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/DomainException.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/DomainException.cs
new file mode 100644
index 0000000000..0ea30629b5
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/DomainException.cs
@@ -0,0 +1,3 @@
+namespace EventTicketing.Domain.Common;
+
+public sealed class DomainException(string message) : Exception(message);
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Error.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Error.cs
new file mode 100644
index 0000000000..c0e7687a2a
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Error.cs
@@ -0,0 +1,23 @@
+namespace EventTicketing.Domain.Common;
+
+public enum ErrorType
+{
+ Failure,
+ Validation,
+ NotFound,
+ Conflict
+}
+
+public sealed record Error(string Code, string Description, ErrorType Type)
+{
+ public static readonly Error None = new(string.Empty, string.Empty, ErrorType.Failure);
+
+ public static Error Validation(string code, string description) =>
+ new(code, description, ErrorType.Validation);
+
+ public static Error NotFound(string code, string description) =>
+ new(code, description, ErrorType.NotFound);
+
+ public static Error Conflict(string code, string description) =>
+ new(code, description, ErrorType.Conflict);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Result.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Result.cs
new file mode 100644
index 0000000000..67147a661b
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Common/Result.cs
@@ -0,0 +1,43 @@
+namespace EventTicketing.Domain.Common;
+
+public class Result
+{
+ protected Result(bool isSuccess, Error error)
+ {
+ if (isSuccess && error != Error.None)
+ throw new ArgumentException("A successful result cannot carry an error.", nameof(error));
+
+ if (!isSuccess && error == Error.None)
+ throw new ArgumentException("A failed result needs an error.", nameof(error));
+
+ IsSuccess = isSuccess;
+ Error = error;
+ }
+
+ public bool IsSuccess { get; }
+ public bool IsFailure => !IsSuccess;
+ public Error Error { get; }
+
+ public static Result Success() => new(true, Error.None);
+ public static Result Failure(Error error) => new(false, error);
+
+ public static implicit operator Result(Error error) => Failure(error);
+}
+
+public sealed class Result : Result
+{
+ private readonly TValue? _value;
+
+ private Result(TValue? value, bool isSuccess, Error error)
+ : base(isSuccess, 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, true, Error.None);
+
+ public static implicit operator Result(Error error) =>
+ new(default, false, error);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/ToDoApp.Domain.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/EventTicketing.Domain.csproj
similarity index 77%
rename from csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/ToDoApp.Domain.csproj
rename to csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/EventTicketing.Domain.csproj
index fa71b7ae6a..b760144708 100644
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/ToDoApp.Domain.csproj
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/EventTicketing.Domain.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net10.0
enable
enable
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/Event.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/Event.cs
new file mode 100644
index 0000000000..4b4caa21c8
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/Event.cs
@@ -0,0 +1,43 @@
+using EventTicketing.Domain.Common;
+
+namespace EventTicketing.Domain.Events;
+
+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)
+ {
+ if (string.IsNullOrWhiteSpace(name))
+ throw new DomainException("An event needs a name.");
+
+ if (capacity <= 0)
+ throw new DomainException("Capacity must be positive.");
+
+ return new Event(name, capacity);
+ }
+
+ public Result Reserve(int quantity)
+ {
+ if (quantity <= 0)
+ throw new DomainException("Quantity must be positive.");
+
+ if (quantity > TicketsLeft)
+ return EventErrors.SoldOut(TicketsLeft, quantity);
+
+ TicketsSold += quantity;
+
+ return Result.Success();
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/EventErrors.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/EventErrors.cs
new file mode 100644
index 0000000000..c3f9286a7c
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Domain/Events/EventErrors.cs
@@ -0,0 +1,12 @@
+using EventTicketing.Domain.Common;
+
+namespace EventTicketing.Domain.Events;
+
+public static class EventErrors
+{
+ public static Error NotFound(int eventId) =>
+ Error.NotFound("Event.NotFound", $"Event {eventId} was not found.");
+
+ public static Error SoldOut(int ticketsLeft, int requested) =>
+ Error.Conflict("Event.SoldOut", $"Only {ticketsLeft} tickets left, {requested} requested.");
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/DependencyInjection.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/DependencyInjection.cs
new file mode 100644
index 0000000000..050854fe79
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/DependencyInjection.cs
@@ -0,0 +1,23 @@
+using EventTicketing.Application.Abstractions;
+using EventTicketing.Application.Events;
+using EventTicketing.Infrastructure.Events;
+using EventTicketing.Infrastructure.Persistence;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace EventTicketing.Infrastructure;
+
+public static class DependencyInjection
+{
+ public static IServiceCollection AddInfrastructure(
+ this IServiceCollection services, string connectionString)
+ {
+ services.AddDbContext(options => options.UseSqlite(connectionString));
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ return services;
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/EventTicketing.Infrastructure.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/EventTicketing.Infrastructure.csproj
new file mode 100644
index 0000000000..0a49afecdd
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/EventTicketing.Infrastructure.csproj
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventReadRepository.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventReadRepository.cs
new file mode 100644
index 0000000000..9db8ad92cf
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventReadRepository.cs
@@ -0,0 +1,17 @@
+using EventTicketing.Application.Events;
+using EventTicketing.Infrastructure.Persistence;
+using Microsoft.EntityFrameworkCore;
+
+namespace EventTicketing.Infrastructure.Events;
+
+public sealed class EventReadRepository(TicketingDbContext dbContext) : IEventReadRepository
+{
+ public Task GetAvailabilityAsync(
+ int eventId, CancellationToken cancellationToken = default) =>
+ dbContext.Events
+ .AsNoTracking()
+ .Where(e => e.Id == eventId)
+ .Select(e => new EventAvailabilityResponse(
+ e.Id, e.Name, e.Capacity, e.Capacity - e.TicketsSold))
+ .FirstOrDefaultAsync(cancellationToken);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventRepository.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventRepository.cs
new file mode 100644
index 0000000000..0309acfbab
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Events/EventRepository.cs
@@ -0,0 +1,12 @@
+using EventTicketing.Application.Events;
+using EventTicketing.Domain.Events;
+using EventTicketing.Infrastructure.Persistence;
+using Microsoft.EntityFrameworkCore;
+
+namespace EventTicketing.Infrastructure.Events;
+
+public sealed class EventRepository(TicketingDbContext dbContext) : IEventRepository
+{
+ public Task GetByIdAsync(int eventId, CancellationToken cancellationToken = default) =>
+ dbContext.Events.FirstOrDefaultAsync(e => e.Id == eventId, cancellationToken);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/DatabaseSeeder.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/DatabaseSeeder.cs
new file mode 100644
index 0000000000..90fde28419
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/DatabaseSeeder.cs
@@ -0,0 +1,25 @@
+using EventTicketing.Domain.Events;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace EventTicketing.Infrastructure.Persistence;
+
+public static class DatabaseSeeder
+{
+ public static async Task SeedDatabaseAsync(this IServiceProvider services)
+ {
+ using var scope = services.CreateScope();
+ var dbContext = scope.ServiceProvider.GetRequiredService();
+
+ await dbContext.Database.EnsureCreatedAsync();
+
+ if (!await dbContext.Events.AnyAsync())
+ {
+ dbContext.Events.AddRange(
+ Event.Create("Clean Architecture Live", capacity: 100),
+ Event.Create("Tiny Jazz Club Night", capacity: 2));
+
+ await dbContext.SaveChangesAsync();
+ }
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/TicketingDbContext.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/TicketingDbContext.cs
new file mode 100644
index 0000000000..bb9e6806b2
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/TicketingDbContext.cs
@@ -0,0 +1,19 @@
+using EventTicketing.Domain.Events;
+using Microsoft.EntityFrameworkCore;
+
+namespace EventTicketing.Infrastructure.Persistence;
+
+public sealed class TicketingDbContext(DbContextOptions options)
+ : DbContext(options)
+{
+ public DbSet Events => Set();
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity(builder =>
+ {
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Name).HasMaxLength(200);
+ });
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/UnitOfWork.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/UnitOfWork.cs
new file mode 100644
index 0000000000..dddcbcdef5
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.Infrastructure/Persistence/UnitOfWork.cs
@@ -0,0 +1,9 @@
+using EventTicketing.Application.Abstractions;
+
+namespace EventTicketing.Infrastructure.Persistence;
+
+public sealed class UnitOfWork(TicketingDbContext dbContext) : IUnitOfWork
+{
+ public Task SaveChangesAsync(CancellationToken cancellationToken = default) =>
+ dbContext.SaveChangesAsync(cancellationToken);
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/Fakes.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/Fakes.cs
new file mode 100644
index 0000000000..a2b91f2c22
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/Fakes.cs
@@ -0,0 +1,22 @@
+using EventTicketing.Application.Abstractions;
+using EventTicketing.Application.Events;
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.UnitTests.Application;
+
+internal sealed class FakeEventRepository(Event? ev) : IEventRepository
+{
+ public Task GetByIdAsync(int eventId, CancellationToken cancellationToken = default) =>
+ Task.FromResult(ev);
+}
+
+internal sealed class FakeUnitOfWork : IUnitOfWork
+{
+ public int SaveCount { get; private set; }
+
+ public Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ SaveCount++;
+ return Task.CompletedTask;
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/ReserveTicketsHandlerTests.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/ReserveTicketsHandlerTests.cs
new file mode 100644
index 0000000000..12a26bd66b
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Application/ReserveTicketsHandlerTests.cs
@@ -0,0 +1,49 @@
+using EventTicketing.Application.Events;
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.UnitTests.Application;
+
+public class ReserveTicketsHandlerTests
+{
+ [Fact]
+ public async Task HandleAsync_WhenTicketsAreAvailable_ReservesAndSaves()
+ {
+ var unitOfWork = new FakeUnitOfWork();
+ var ev = Event.Create("Clean Architecture Live", capacity: 100);
+ var handler = new ReserveTicketsHandler(new FakeEventRepository(ev), unitOfWork);
+
+ var result = await handler.HandleAsync(
+ new ReserveTicketsCommand(EventId: 1, Quantity: 2), TestContext.Current.CancellationToken);
+
+ Assert.True(result.IsSuccess);
+ Assert.Equal(98, result.Value.TicketsLeft);
+ Assert.Equal(1, unitOfWork.SaveCount);
+ }
+
+ [Fact]
+ public async Task HandleAsync_WhenEventIsSoldOut_ReturnsConflictAndSavesNothing()
+ {
+ var unitOfWork = new FakeUnitOfWork();
+ var ev = Event.Create("Clean Architecture Live", capacity: 1);
+ var handler = new ReserveTicketsHandler(new FakeEventRepository(ev), unitOfWork);
+
+ var result = await handler.HandleAsync(
+ new ReserveTicketsCommand(EventId: 1, Quantity: 2), TestContext.Current.CancellationToken);
+
+ Assert.True(result.IsFailure);
+ Assert.Equal("Event.SoldOut", result.Error.Code);
+ Assert.Equal(0, unitOfWork.SaveCount);
+ }
+
+ [Fact]
+ public async Task HandleAsync_WhenEventDoesNotExist_ReturnsNotFound()
+ {
+ var handler = new ReserveTicketsHandler(new FakeEventRepository(null), new FakeUnitOfWork());
+
+ var result = await handler.HandleAsync(
+ new ReserveTicketsCommand(EventId: 42, Quantity: 2), TestContext.Current.CancellationToken);
+
+ Assert.True(result.IsFailure);
+ Assert.Equal("Event.NotFound", result.Error.Code);
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Domain/EventTests.cs b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Domain/EventTests.cs
new file mode 100644
index 0000000000..ada11ff2aa
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/Domain/EventTests.cs
@@ -0,0 +1,38 @@
+using EventTicketing.Domain.Common;
+using EventTicketing.Domain.Events;
+
+namespace EventTicketing.UnitTests.Domain;
+
+public class EventTests
+{
+ [Fact]
+ public void Reserve_WhenEnoughTicketsAreLeft_SellsThem()
+ {
+ var ev = Event.Create("Clean Architecture Live", capacity: 100);
+
+ var result = ev.Reserve(3);
+
+ Assert.True(result.IsSuccess);
+ Assert.Equal(97, ev.TicketsLeft);
+ }
+
+ [Fact]
+ public void Reserve_WhenTooFewTicketsAreLeft_ReturnsSoldOutAndSellsNothing()
+ {
+ var ev = Event.Create("Clean Architecture Live", capacity: 2);
+
+ var result = ev.Reserve(3);
+
+ Assert.True(result.IsFailure);
+ Assert.Equal("Event.SoldOut", result.Error.Code);
+ Assert.Equal(0, ev.TicketsSold);
+ }
+
+ [Fact]
+ public void Reserve_WithNonPositiveQuantity_Throws()
+ {
+ var ev = Event.Create("Clean Architecture Live", capacity: 100);
+
+ Assert.Throws(() => ev.Reserve(0));
+ }
+}
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/EventTicketing.UnitTests.csproj b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/EventTicketing.UnitTests.csproj
new file mode 100644
index 0000000000..5fdfd0c23f
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.UnitTests/EventTicketing.UnitTests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net10.0
+ enable
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp-architectural-patterns/CleanArchitecture/EventTicketing.sln b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.sln
new file mode 100644
index 0000000000..629ee5d067
--- /dev/null
+++ b/csharp-architectural-patterns/CleanArchitecture/EventTicketing.sln
@@ -0,0 +1,104 @@
+
+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.Domain", "EventTicketing.Domain\EventTicketing.Domain.csproj", "{159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.Application", "EventTicketing.Application\EventTicketing.Application.csproj", "{A446F8F5-A9D2-4B20-B400-634C8601F953}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.Infrastructure", "EventTicketing.Infrastructure\EventTicketing.Infrastructure.csproj", "{7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.Api", "EventTicketing.Api\EventTicketing.Api.csproj", "{CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.UnitTests", "EventTicketing.UnitTests\EventTicketing.UnitTests.csproj", "{DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventTicketing.ArchitectureTests", "EventTicketing.ArchitectureTests\EventTicketing.ArchitectureTests.csproj", "{2EA7392E-A820-4312-84E1-5F35C413019E}"
+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
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|x64.Build.0 = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Debug|x86.Build.0 = Debug|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|x64.ActiveCfg = Release|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|x64.Build.0 = Release|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|x86.ActiveCfg = Release|Any CPU
+ {159B8B27-7ECF-4DEE-AA8E-975ABCDE12C9}.Release|x86.Build.0 = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|x64.Build.0 = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Debug|x86.Build.0 = Debug|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|x64.ActiveCfg = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|x64.Build.0 = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|x86.ActiveCfg = Release|Any CPU
+ {A446F8F5-A9D2-4B20-B400-634C8601F953}.Release|x86.Build.0 = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|x64.Build.0 = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Debug|x86.Build.0 = Debug|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|x64.ActiveCfg = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|x64.Build.0 = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|x86.ActiveCfg = Release|Any CPU
+ {7E3F5A24-15AC-4C27-8E6C-AC8EFB695138}.Release|x86.Build.0 = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|x64.Build.0 = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Debug|x86.Build.0 = Debug|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|x64.ActiveCfg = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|x64.Build.0 = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|x86.ActiveCfg = Release|Any CPU
+ {CDDE590D-17CF-4039-A4E5-8A1EC7A245F7}.Release|x86.Build.0 = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Debug|x86.Build.0 = Debug|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|x64.Build.0 = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE452D9A-440B-44EE-8D6F-CE2D89B5A6E4}.Release|x86.Build.0 = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|x64.Build.0 = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Debug|x86.Build.0 = Debug|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|x64.ActiveCfg = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|x64.Build.0 = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|x86.ActiveCfg = Release|Any CPU
+ {2EA7392E-A820-4312-84E1-5F35C413019E}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Controllers/ToDoItemController.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Controllers/ToDoItemController.cs
deleted file mode 100644
index ad3775ce93..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Controllers/ToDoItemController.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using MediatR;
-using Microsoft.AspNetCore.Mvc;
-using ToDoApp.Application.Commands.CreateToDo;
-using ToDoApp.Application.Queries.ToDoItem;
-
-namespace ToDoApp.API.Controllers;
-
-[Route("api/[controller]")]
-[ApiController]
-public class ToDoItemController(IMediator mediator) : ControllerBase
-{
- [HttpGet]
- public async Task Get()
- {
- return Ok(await mediator.Send(new ToDoItemQuery()));
- }
-
- [HttpPost]
- public async Task Post([FromBody] CreateToDoItemCommand command)
- {
- await mediator.Send(command);
-
- return Created();
- }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Program.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Program.cs
deleted file mode 100644
index 458cd1c224..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Program.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-
-using ToDoApp.Domain.Interfaces;
-using ToDoApp.Persistence;
-using ToDoApp.Application.IoC;
-
-var builder = WebApplication.CreateBuilder(args);
-
-builder.Services.AddControllers();
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-
-builder.Services.AddApplicationDependencies();
-builder.Services.AddSingleton();
-
-var app = builder.Build();
-
-if (app.Environment.IsDevelopment())
-{
- app.UseSwagger();
- app.UseSwaggerUI();
-}
-
-app.UseHttpsRedirection();
-
-app.UseAuthorization();
-
-app.MapControllers();
-
-app.Run();
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Properties/launchSettings.json b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Properties/launchSettings.json
deleted file mode 100644
index 3de382f131..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/Properties/launchSettings.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/launchsettings.json",
- "profiles": {
- "https": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "https://localhost:7170;http://localhost:5157",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
- }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/ToDoApp.API.csproj b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/ToDoApp.API.csproj
deleted file mode 100644
index 7a0d5f1ff9..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.API/ToDoApp.API.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/CreateTodoItemCommandHandlerTests.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/CreateTodoItemCommandHandlerTests.cs
deleted file mode 100644
index 1f9c6e43e3..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/CreateTodoItemCommandHandlerTests.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using ToDoApp.Application.Commands.CreateToDo;
-using ToDoApp.Domain.Entities;
-
-namespace ToDoApp.Application.Tests;
-
-public class CreateToDoItemCommandHandlerTests
-{
- [Fact]
- public void GivenCreateToDoItemCommandHandler_WhenHandleCalled_ThenCreateNewToDoItem()
- {
- // Arrange
- var toDoRepositoryMock = new Mock();
- toDoRepositoryMock.Setup(x => x.CreateAsync(It.IsAny()))
- .ReturnsAsync(1);
- var createToDoItemCommandHandler = new CreateToDoItemCommandHandler(toDoRepositoryMock.Object);
-
- var createToDoItemCommand = new CreateToDoItemCommand
- {
- Description = "Test Description"
- };
-
- // Act
- var result = createToDoItemCommandHandler.Handle(createToDoItemCommand, CancellationToken.None).Result;
-
- // Assert
- Assert.Equal(1, result);
- }
-}
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/GlobalUsings.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/GlobalUsings.cs
deleted file mode 100644
index 4ff6ad3448..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/GlobalUsings.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-global using Xunit;
-global using Moq;
-global using ToDoApp.Domain.Interfaces;
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoApp.Application.Tests.csproj b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoApp.Application.Tests.csproj
deleted file mode 100644
index 84532e8f70..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoApp.Application.Tests.csproj
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
-
-
-
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoItemQueryHandlerTests.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoItemQueryHandlerTests.cs
deleted file mode 100644
index c93d4a670a..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application.Tests/ToDoItemQueryHandlerTests.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using ToDoApp.Application.Queries.ToDoItem;
-
-namespace ToDoApp.Application.Tests;
-
-public class ToDoItemQueryHandlerTests
-{
- [Fact]
- public async Task GivenToDoItemQueryHandler_WhenHandleCalled_ThenReturnToDoItems()
- {
- // Arrange
- var mockRepository = new Mock();
- mockRepository.Setup(x => x.GetAllAsync())
- .ReturnsAsync(
- [
- new() { Description = "Item 1" },
- new() { Description = "Item 2" }
- ]);
-
- var handler = new ToDoItemQueryHandler(mockRepository.Object);
- var query = new ToDoItemQuery();
-
- // Act
- var result = await handler.Handle(query, CancellationToken.None);
-
- // Assert
- Assert.Equal(2, result.Count);
- Assert.Equal("Item 1", result[0].Description);
- Assert.Equal("Item 2", result[^1].Description);
- }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateToDoItemCommand.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateToDoItemCommand.cs
deleted file mode 100644
index 92e5a804d9..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateToDoItemCommand.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using MediatR;
-
-namespace ToDoApp.Application.Commands.CreateToDo;
-
-public class CreateToDoItemCommand : IRequest
-{
- public required string Description { get; set; }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateTodoItemCommandHandler.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateTodoItemCommandHandler.cs
deleted file mode 100644
index 8f2cb57b7a..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Commands/CreateToDo/CreateTodoItemCommandHandler.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using MediatR;
-using ToDoApp.Domain.Entities;
-using ToDoApp.Domain.Interfaces;
-
-namespace ToDoApp.Application.Commands.CreateToDo;
-
-public class CreateToDoItemCommandHandler(IToDoRepository toDoRepository)
- : IRequestHandler
-{
- public Task Handle(
- CreateToDoItemCommand request, CancellationToken cancellationToken)
- {
- var item = new ToDoItem
- {
- Description = request.Description
- };
-
- return toDoRepository.CreateAsync(item);
- }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/IoC/ServiceCollectionExtensions.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/IoC/ServiceCollectionExtensions.cs
deleted file mode 100644
index 20a4b4eac6..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/IoC/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace ToDoApp.Application.IoC;
-
-public static class ServiceCollectionExtensions
-{
- public static IServiceCollection AddApplicationDependencies(this IServiceCollection services)
- {
- services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(AppDomain.CurrentDomain.GetAssemblies()));
-
- return services;
- }
-}
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQuery.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQuery.cs
deleted file mode 100644
index 706a453433..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQuery.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using MediatR;
-namespace ToDoApp.Application.Queries.ToDoItem;
-
-public class ToDoItemQuery
- : IRequest>
-{
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQueryHandler.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQueryHandler.cs
deleted file mode 100644
index 784c302f94..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/Queries/ToDoItem/ToDoItemQueryHandler.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using MediatR;
-using ToDoApp.Domain.Interfaces;
-
-namespace ToDoApp.Application.Queries.ToDoItem;
-
-public class ToDoItemQueryHandler(IToDoRepository toDoRepository)
- : IRequestHandler>
-{
- public Task> Handle(
- ToDoItemQuery request, CancellationToken cancellationToken)
- {
- return toDoRepository.GetAllAsync();
- }
-}
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/ToDoApp.Application.csproj b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/ToDoApp.Application.csproj
deleted file mode 100644
index d127c92f63..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Application/ToDoApp.Application.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Entities/ToDoItem.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Entities/ToDoItem.cs
deleted file mode 100644
index ed4089f00f..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Entities/ToDoItem.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace ToDoApp.Domain.Entities;
-
-public class ToDoItem
-{
- public int Id { get; set; }
- public required string Description { get; set; }
- public bool IsDone { get; set; }
-}
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Interfaces/IToDoRepository.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Interfaces/IToDoRepository.cs
deleted file mode 100644
index e5a7b9d1ec..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Domain/Interfaces/IToDoRepository.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using ToDoApp.Domain.Entities;
-
-namespace ToDoApp.Domain.Interfaces;
-
-public interface IToDoRepository
-{
- Task> GetAllAsync();
- Task CreateAsync(ToDoItem item);
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Infrastructure/ToDoApp.Infrastructure.csproj b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Infrastructure/ToDoApp.Infrastructure.csproj
deleted file mode 100644
index fa71b7ae6a..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Infrastructure/ToDoApp.Infrastructure.csproj
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/InMemoryToDoRepository.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/InMemoryToDoRepository.cs
deleted file mode 100644
index 9db30cc000..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/InMemoryToDoRepository.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using ToDoApp.Domain.Entities;
-using ToDoApp.Domain.Interfaces;
-
-namespace ToDoApp.Persistence;
-
-public class InMemoryToDoRepository : IToDoRepository
-{
- private static readonly List _items = [];
-
- public Task CreateAsync(ToDoItem item)
- {
- _items.Add(item);
-
- return Task.FromResult(item.Id);
- }
-
- public Task> GetAllAsync()
- {
- return Task.FromResult(_items);
- }
-}
\ No newline at end of file
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/SqlTodoRepository.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/SqlTodoRepository.cs
deleted file mode 100644
index 4215e690ed..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/SqlTodoRepository.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using ToDoApp.Domain.Entities;
-using ToDoApp.Domain.Interfaces;
-
-namespace ToDoApp.Persistence;
-
-public class SqlToDoRepository : IToDoRepository
-{
- private readonly ToDoDbContext _context;
-
- public SqlToDoRepository(ToDoDbContext context)
- {
- _context = context;
- }
-
- public Task CreateAsync(ToDoItem item)
- {
- _context.ToDoItems.Add(item);
-
- return _context.SaveChangesAsync();
- }
-
- public Task> GetAllAsync()
- {
- return _context.ToDoItems.ToListAsync();
- }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoDbContext.cs b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoDbContext.cs
deleted file mode 100644
index e9f82a24f7..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.Persistence/ToDoDbContext.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using ToDoApp.Domain.Entities;
-
-namespace ToDoApp.Persistence;
-
-public class ToDoDbContext : DbContext
-{
- public ToDoDbContext(DbContextOptions options)
- : base(options)
- {
- }
-
- public DbSet ToDoItems { get; set; }
-}
diff --git a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.sln b/csharp-architectural-patterns/CleanArchitecture/ToDoApp.sln
deleted file mode 100644
index e0cce71573..0000000000
--- a/csharp-architectural-patterns/CleanArchitecture/ToDoApp.sln
+++ /dev/null
@@ -1,71 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.8.34309.116
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp.API", "ToDoApp.API\ToDoApp.API.csproj", "{FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Presentation", "Presentation", "{AEE51D5C-AAC9-41D5-98FE-1C9EC4FEB36B}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{8F778E12-2BD5-489A-BF4C-D1E90ECDF4FD}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{753910C3-3BA3-4933-AF53-62631C7E521F}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp.Domain", "ToDoApp.Domain\ToDoApp.Domain.csproj", "{9C7A1444-55A8-4666-A2A8-232FF90A27EF}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp.Application", "ToDoApp.Application\ToDoApp.Application.csproj", "{24041317-18D4-4351-A84F-1F1C83438EC2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp.Persistence", "ToDoApp.Persistence\ToDoApp.Persistence.csproj", "{8205DAAB-E1E2-4098-B9F7-03B9900BDC41}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToDoApp.Infrastructure", "ToDoApp.Infrastructure\ToDoApp.Infrastructure.csproj", "{9CC7CB49-58D3-42EB-A1B9-A021CF621910}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8BFD18FA-5AC2-4A02-AA0E-77F65C079165}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToDoApp.Application.Tests", "ToDoApp.Application.Tests\ToDoApp.Application.Tests.csproj", "{DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5}.Release|Any CPU.Build.0 = Release|Any CPU
- {9C7A1444-55A8-4666-A2A8-232FF90A27EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9C7A1444-55A8-4666-A2A8-232FF90A27EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9C7A1444-55A8-4666-A2A8-232FF90A27EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9C7A1444-55A8-4666-A2A8-232FF90A27EF}.Release|Any CPU.Build.0 = Release|Any CPU
- {24041317-18D4-4351-A84F-1F1C83438EC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {24041317-18D4-4351-A84F-1F1C83438EC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {24041317-18D4-4351-A84F-1F1C83438EC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {24041317-18D4-4351-A84F-1F1C83438EC2}.Release|Any CPU.Build.0 = Release|Any CPU
- {8205DAAB-E1E2-4098-B9F7-03B9900BDC41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8205DAAB-E1E2-4098-B9F7-03B9900BDC41}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8205DAAB-E1E2-4098-B9F7-03B9900BDC41}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8205DAAB-E1E2-4098-B9F7-03B9900BDC41}.Release|Any CPU.Build.0 = Release|Any CPU
- {9CC7CB49-58D3-42EB-A1B9-A021CF621910}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9CC7CB49-58D3-42EB-A1B9-A021CF621910}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9CC7CB49-58D3-42EB-A1B9-A021CF621910}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9CC7CB49-58D3-42EB-A1B9-A021CF621910}.Release|Any CPU.Build.0 = Release|Any CPU
- {DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {FEE4EF92-E8AC-464F-B63C-DDDAABD1A0D5} = {AEE51D5C-AAC9-41D5-98FE-1C9EC4FEB36B}
- {9C7A1444-55A8-4666-A2A8-232FF90A27EF} = {8F778E12-2BD5-489A-BF4C-D1E90ECDF4FD}
- {24041317-18D4-4351-A84F-1F1C83438EC2} = {8F778E12-2BD5-489A-BF4C-D1E90ECDF4FD}
- {8205DAAB-E1E2-4098-B9F7-03B9900BDC41} = {753910C3-3BA3-4933-AF53-62631C7E521F}
- {9CC7CB49-58D3-42EB-A1B9-A021CF621910} = {753910C3-3BA3-4933-AF53-62631C7E521F}
- {DA33F6F0-BF91-4DEF-80BE-8B2E2DDC4CC9} = {8BFD18FA-5AC2-4A02-AA0E-77F65C079165}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {6B81A701-CA9A-4EEB-8583-C471B446F564}
- EndGlobalSection
-EndGlobal