From 3a26c007d28906e3dfdcd79a960df0b8a538c920 Mon Sep 17 00:00:00 2001 From: Liam Pietralla Date: Tue, 22 Oct 2024 16:43:59 +1100 Subject: [PATCH] update solution to implement services, repository and unit of work --- .../Controllers/UserController.cs | 23 +++++++++ .../Controllers/WeatherForecastController.cs | 33 ------------ src/UserManager.API/Program.cs | 5 ++ src/UserManager.API/UserManager.API.csproj | 9 ++++ src/UserManager.API/UserManager.API.http | 6 --- src/UserManager.API/WeatherForecast.cs | 13 ----- .../appsettings.Development.json | 3 ++ .../DependancyInjection.cs | 14 ++++++ .../Users/Responses/CreateUserResponseDto.cs | 1 + .../Features/Users/Services/UserService.cs | 22 ++++++-- .../Interfaces/IUnitOfWork.cs | 8 +++ .../UserManager.Application.csproj | 4 ++ .../BaseRepository.cs | 16 ++++++ src/UserManager.Infrastructure/Class1.cs | 7 --- .../DependancyInjection.cs | 19 +++++++ .../20241022053245_Added Users.Designer.cs | 50 +++++++++++++++++++ .../Migrations/20241022053245_Added Users.cs | 36 +++++++++++++ .../UserManagerContextModelSnapshot.cs | 47 +++++++++++++++++ src/UserManager.Infrastructure/UnitOfWork.cs | 15 ++++++ .../UserManager.Infrastructure.csproj | 16 ++++++ .../UserManagerContext.cs | 10 ++++ .../UserRepository.cs | 7 +++ 22 files changed, 301 insertions(+), 63 deletions(-) create mode 100644 src/UserManager.API/Controllers/UserController.cs delete mode 100644 src/UserManager.API/Controllers/WeatherForecastController.cs delete mode 100644 src/UserManager.API/UserManager.API.http delete mode 100644 src/UserManager.API/WeatherForecast.cs create mode 100644 src/UserManager.Application/DependancyInjection.cs create mode 100644 src/UserManager.Application/Interfaces/IUnitOfWork.cs create mode 100644 src/UserManager.Infrastructure/BaseRepository.cs delete mode 100644 src/UserManager.Infrastructure/Class1.cs create mode 100644 src/UserManager.Infrastructure/DependancyInjection.cs create mode 100644 src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.Designer.cs create mode 100644 src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.cs create mode 100644 src/UserManager.Infrastructure/Migrations/UserManagerContextModelSnapshot.cs create mode 100644 src/UserManager.Infrastructure/UnitOfWork.cs create mode 100644 src/UserManager.Infrastructure/UserManagerContext.cs create mode 100644 src/UserManager.Infrastructure/UserRepository.cs diff --git a/src/UserManager.API/Controllers/UserController.cs b/src/UserManager.API/Controllers/UserController.cs new file mode 100644 index 0000000..54ffc0f --- /dev/null +++ b/src/UserManager.API/Controllers/UserController.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; +using UserManager.Application.Features.Users.Interfaces; +using UserManager.Application.Features.Users.Requests; +using UserManager.Application.Features.Users.Responses; + +namespace UserManager.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class UserController(ILogger logger, IUserService userService) : ControllerBase + { + private readonly ILogger _logger = logger; + private readonly IUserService _userService = userService; + + [HttpPost(Name = "CreateUser")] + public async Task> CreateUserAsync(CreateUserRequestDto request) + { + _logger.LogInformation("Creating user with first name {FirstName} and last name {LastName}", request.FirstName, request.LastName); + CreateUserResponseDto response = await _userService.CreateUserAsync(request); + return Ok(response); + } + } +} diff --git a/src/UserManager.API/Controllers/WeatherForecastController.cs b/src/UserManager.API/Controllers/WeatherForecastController.cs deleted file mode 100644 index 16433fc..0000000 --- a/src/UserManager.API/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace UserManager.API.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/src/UserManager.API/Program.cs b/src/UserManager.API/Program.cs index 48863a6..3b69458 100644 --- a/src/UserManager.API/Program.cs +++ b/src/UserManager.API/Program.cs @@ -1,6 +1,11 @@ +using UserManager.Application; +using UserManager.Infrastructure; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. +builder.Services.AddApplication(); +builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle diff --git a/src/UserManager.API/UserManager.API.csproj b/src/UserManager.API/UserManager.API.csproj index 5419ef0..2ab0969 100644 --- a/src/UserManager.API/UserManager.API.csproj +++ b/src/UserManager.API/UserManager.API.csproj @@ -7,7 +7,16 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/src/UserManager.API/UserManager.API.http b/src/UserManager.API/UserManager.API.http deleted file mode 100644 index da05126..0000000 --- a/src/UserManager.API/UserManager.API.http +++ /dev/null @@ -1,6 +0,0 @@ -@UserManager.API_HostAddress = http://localhost:5110 - -GET {{UserManager.API_HostAddress}}/weatherforecast/ -Accept: application/json - -### diff --git a/src/UserManager.API/WeatherForecast.cs b/src/UserManager.API/WeatherForecast.cs deleted file mode 100644 index 9a85090..0000000 --- a/src/UserManager.API/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace UserManager.API -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} diff --git a/src/UserManager.API/appsettings.Development.json b/src/UserManager.API/appsettings.Development.json index 0c208ae..9afc207 100644 --- a/src/UserManager.API/appsettings.Development.json +++ b/src/UserManager.API/appsettings.Development.json @@ -4,5 +4,8 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "UserManagerContext": "Server=localhost;Port=5432;Database=user-manager;User Id=postgres;Password=postgres;" } } diff --git a/src/UserManager.Application/DependancyInjection.cs b/src/UserManager.Application/DependancyInjection.cs new file mode 100644 index 0000000..75dc564 --- /dev/null +++ b/src/UserManager.Application/DependancyInjection.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using UserManager.Application.Features.Users.Interfaces; +using UserManager.Application.Features.Users.Services; + +namespace UserManager.Application +{ + public static class DependancyInjection + { + public static void AddApplication(this IServiceCollection services) + { + services.AddScoped(); + } + } +} diff --git a/src/UserManager.Application/Features/Users/Responses/CreateUserResponseDto.cs b/src/UserManager.Application/Features/Users/Responses/CreateUserResponseDto.cs index 1c53e4c..8459b19 100644 --- a/src/UserManager.Application/Features/Users/Responses/CreateUserResponseDto.cs +++ b/src/UserManager.Application/Features/Users/Responses/CreateUserResponseDto.cs @@ -2,6 +2,7 @@ { public class CreateUserResponseDto { + public int UserId { get; set; } public string FirstName { get; set; } = string.Empty; } } diff --git a/src/UserManager.Application/Features/Users/Services/UserService.cs b/src/UserManager.Application/Features/Users/Services/UserService.cs index 5ca0f2c..1dc7431 100644 --- a/src/UserManager.Application/Features/Users/Services/UserService.cs +++ b/src/UserManager.Application/Features/Users/Services/UserService.cs @@ -2,18 +2,32 @@ using UserManager.Application.Features.Users.Requests; using UserManager.Application.Features.Users.Responses; using UserManager.Application.Interfaces; +using UserManager.Domain.Entities; namespace UserManager.Application.Features.Users.Services { - class UserService(IUserRepository userRepository) : IUserService + class UserService(IUnitOfWork unitOfWork) : IUserService { - private readonly IUserRepository _userRepository = userRepository; + private readonly IUnitOfWork _unitOfWork = unitOfWork; - public Task CreateUserAsync(CreateUserRequestDto request) + public async Task CreateUserAsync(CreateUserRequestDto request) { // TODO // First validate this user does not exist - // Then create the user + User user = new() + { + FirstName = request.FirstName, + LastName = request.LastName, + }; + + await _unitOfWork.UserRepository.CreateAsync(user); + await _unitOfWork.SaveChangesAsync(); + + return new CreateUserResponseDto() + { + UserId = user.UserId, + FirstName = user.FirstName, + }; } } } diff --git a/src/UserManager.Application/Interfaces/IUnitOfWork.cs b/src/UserManager.Application/Interfaces/IUnitOfWork.cs new file mode 100644 index 0000000..2075409 --- /dev/null +++ b/src/UserManager.Application/Interfaces/IUnitOfWork.cs @@ -0,0 +1,8 @@ +namespace UserManager.Application.Interfaces +{ + public interface IUnitOfWork + { + IUserRepository UserRepository { get; } + Task SaveChangesAsync(); + } +} diff --git a/src/UserManager.Application/UserManager.Application.csproj b/src/UserManager.Application/UserManager.Application.csproj index 2481cb1..4084818 100644 --- a/src/UserManager.Application/UserManager.Application.csproj +++ b/src/UserManager.Application/UserManager.Application.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/src/UserManager.Infrastructure/BaseRepository.cs b/src/UserManager.Infrastructure/BaseRepository.cs new file mode 100644 index 0000000..679eb54 --- /dev/null +++ b/src/UserManager.Infrastructure/BaseRepository.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using UserManager.Application.Interfaces; + +namespace UserManager.Infrastructure +{ + public class BaseRepository(UserManagerContext context) : IBaseRepository where TEntity : class + { + private readonly UserManagerContext _context = context; + private readonly DbSet _dbSet = context.Set(); + + public async Task CreateAsync(TEntity entity) + { + await _dbSet.AddAsync(entity); + } + } +} diff --git a/src/UserManager.Infrastructure/Class1.cs b/src/UserManager.Infrastructure/Class1.cs deleted file mode 100644 index f3cd240..0000000 --- a/src/UserManager.Infrastructure/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace UserManager.Infrastructure -{ - public class Class1 - { - - } -} diff --git a/src/UserManager.Infrastructure/DependancyInjection.cs b/src/UserManager.Infrastructure/DependancyInjection.cs new file mode 100644 index 0000000..fe6c404 --- /dev/null +++ b/src/UserManager.Infrastructure/DependancyInjection.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using UserManager.Application.Interfaces; + +namespace UserManager.Infrastructure +{ + public static class DependancyInjection + { + public static void AddInfrastructure(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(options => + options.UseNpgsql(configuration.GetConnectionString(nameof(UserManagerContext)))); + + services.AddScoped(); + services.AddScoped(); + } + } +} diff --git a/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.Designer.cs b/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.Designer.cs new file mode 100644 index 0000000..4ebf3d9 --- /dev/null +++ b/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.Designer.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using UserManager.Infrastructure; + +#nullable disable + +namespace UserManager.Infrastructure.Migrations +{ + [DbContext(typeof(UserManagerContext))] + [Migration("20241022053245_Added Users")] + partial class AddedUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("UserManager.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.cs b/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.cs new file mode 100644 index 0000000..74c2cd8 --- /dev/null +++ b/src/UserManager.Infrastructure/Migrations/20241022053245_Added Users.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace UserManager.Infrastructure.Migrations +{ + /// + public partial class AddedUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + UserId = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + FirstName = table.Column(type: "text", nullable: false), + LastName = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.UserId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/UserManager.Infrastructure/Migrations/UserManagerContextModelSnapshot.cs b/src/UserManager.Infrastructure/Migrations/UserManagerContextModelSnapshot.cs new file mode 100644 index 0000000..ce89bb3 --- /dev/null +++ b/src/UserManager.Infrastructure/Migrations/UserManagerContextModelSnapshot.cs @@ -0,0 +1,47 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using UserManager.Infrastructure; + +#nullable disable + +namespace UserManager.Infrastructure.Migrations +{ + [DbContext(typeof(UserManagerContext))] + partial class UserManagerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("UserManager.Domain.Entities.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("UserId")); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/UserManager.Infrastructure/UnitOfWork.cs b/src/UserManager.Infrastructure/UnitOfWork.cs new file mode 100644 index 0000000..416c8ed --- /dev/null +++ b/src/UserManager.Infrastructure/UnitOfWork.cs @@ -0,0 +1,15 @@ +using UserManager.Application.Interfaces; + +namespace UserManager.Infrastructure +{ + public class UnitOfWork(UserManagerContext context) : IUnitOfWork + { + private IUserRepository? _userRepository = null; + public IUserRepository UserRepository => _userRepository ??= new UserRepository(context); + + public async Task SaveChangesAsync() + { + return await context.SaveChangesAsync(); + } + } +} diff --git a/src/UserManager.Infrastructure/UserManager.Infrastructure.csproj b/src/UserManager.Infrastructure/UserManager.Infrastructure.csproj index fa71b7a..ae25e75 100644 --- a/src/UserManager.Infrastructure/UserManager.Infrastructure.csproj +++ b/src/UserManager.Infrastructure/UserManager.Infrastructure.csproj @@ -6,4 +6,20 @@ enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/UserManager.Infrastructure/UserManagerContext.cs b/src/UserManager.Infrastructure/UserManagerContext.cs new file mode 100644 index 0000000..840debc --- /dev/null +++ b/src/UserManager.Infrastructure/UserManagerContext.cs @@ -0,0 +1,10 @@ +using Microsoft.EntityFrameworkCore; +using UserManager.Domain.Entities; + +namespace UserManager.Infrastructure +{ + public class UserManagerContext(DbContextOptions options) : DbContext(options) + { + public DbSet Users { get; set; } + } +} diff --git a/src/UserManager.Infrastructure/UserRepository.cs b/src/UserManager.Infrastructure/UserRepository.cs new file mode 100644 index 0000000..3a8490e --- /dev/null +++ b/src/UserManager.Infrastructure/UserRepository.cs @@ -0,0 +1,7 @@ +using UserManager.Application.Interfaces; +using UserManager.Domain.Entities; + +namespace UserManager.Infrastructure +{ + public class UserRepository(UserManagerContext context) : BaseRepository(context), IUserRepository { } +}