update solution to implement services, repository and unit of work

This commit is contained in:
Liam Pietralla 2024-10-22 16:43:59 +11:00
parent 26c8922752
commit 3a26c007d2
22 changed files with 301 additions and 63 deletions

View File

@ -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<UserController> logger, IUserService userService) : ControllerBase
{
private readonly ILogger<UserController> _logger = logger;
private readonly IUserService _userService = userService;
[HttpPost(Name = "CreateUser")]
public async Task<ActionResult<CreateUserResponseDto>> 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);
}
}
}

View File

@ -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<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> 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();
}
}
}

View File

@ -1,6 +1,11 @@
using UserManager.Application;
using UserManager.Infrastructure;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddApplication();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddControllers(); builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle

View File

@ -7,7 +7,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UserManager.Application\UserManager.Application.csproj" />
<ProjectReference Include="..\UserManager.Infrastructure\UserManager.Infrastructure.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,6 +0,0 @@
@UserManager.API_HostAddress = http://localhost:5110
GET {{UserManager.API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -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; }
}
}

View File

@ -4,5 +4,8 @@
"Default": "Information", "Default": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
},
"ConnectionStrings": {
"UserManagerContext": "Server=localhost;Port=5432;Database=user-manager;User Id=postgres;Password=postgres;"
} }
} }

View File

@ -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<IUserService, UserService>();
}
}
}

View File

@ -2,6 +2,7 @@
{ {
public class CreateUserResponseDto public class CreateUserResponseDto
{ {
public int UserId { get; set; }
public string FirstName { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty;
} }
} }

View File

@ -2,18 +2,32 @@
using UserManager.Application.Features.Users.Requests; using UserManager.Application.Features.Users.Requests;
using UserManager.Application.Features.Users.Responses; using UserManager.Application.Features.Users.Responses;
using UserManager.Application.Interfaces; using UserManager.Application.Interfaces;
using UserManager.Domain.Entities;
namespace UserManager.Application.Features.Users.Services 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<CreateUserResponseDto> CreateUserAsync(CreateUserRequestDto request) public async Task<CreateUserResponseDto> CreateUserAsync(CreateUserRequestDto request)
{ {
// TODO // TODO
// First validate this user does not exist // 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,
};
} }
} }
} }

View File

@ -0,0 +1,8 @@
namespace UserManager.Application.Interfaces
{
public interface IUnitOfWork
{
IUserRepository UserRepository { get; }
Task<int> SaveChangesAsync();
}
}

View File

@ -10,6 +10,10 @@
<Folder Include="Features\Restaurants\" /> <Folder Include="Features\Restaurants\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\UserManager.Domain\UserManager.Domain.csproj" /> <ProjectReference Include="..\UserManager.Domain\UserManager.Domain.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,16 @@
using Microsoft.EntityFrameworkCore;
using UserManager.Application.Interfaces;
namespace UserManager.Infrastructure
{
public class BaseRepository<TEntity>(UserManagerContext context) : IBaseRepository<TEntity> where TEntity : class
{
private readonly UserManagerContext _context = context;
private readonly DbSet<TEntity> _dbSet = context.Set<TEntity>();
public async Task CreateAsync(TEntity entity)
{
await _dbSet.AddAsync(entity);
}
}
}

View File

@ -1,7 +0,0 @@
namespace UserManager.Infrastructure
{
public class Class1
{
}
}

View File

@ -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<UserManagerContext>(options =>
options.UseNpgsql(configuration.GetConnectionString(nameof(UserManagerContext))));
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}

View File

@ -0,0 +1,50 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.HasKey("UserId");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace UserManager.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddedUsers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
UserId = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
FirstName = table.Column<string>(type: "text", nullable: false),
LastName = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.UserId);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@ -0,0 +1,47 @@
// <auto-generated />
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<int>("UserId")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("UserId"));
b.Property<string>("FirstName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.HasKey("UserId");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -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<int> SaveChangesAsync()
{
return await context.SaveChangesAsync();
}
}
}

View File

@ -6,4 +6,20 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UserManager.Application\UserManager.Application.csproj" />
<ProjectReference Include="..\UserManager.Domain\UserManager.Domain.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,10 @@
using Microsoft.EntityFrameworkCore;
using UserManager.Domain.Entities;
namespace UserManager.Infrastructure
{
public class UserManagerContext(DbContextOptions<UserManagerContext> options) : DbContext(options)
{
public DbSet<User> Users { get; set; }
}
}

View File

@ -0,0 +1,7 @@
using UserManager.Application.Interfaces;
using UserManager.Domain.Entities;
namespace UserManager.Infrastructure
{
public class UserRepository(UserManagerContext context) : BaseRepository<User>(context), IUserRepository { }
}