0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-30 02:31:08 +00:00

Add first endpoint

This commit is contained in:
Alexander Konietzko 2023-03-06 19:01:21 +01:00
parent d884b03336
commit b58c74c666
No known key found for this signature in database
GPG Key ID: BA6905F37AEC2B5B
19 changed files with 353 additions and 32 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea
.idea
.vs

View File

@ -22,6 +22,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" />
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" />
</ItemGroup>

View File

@ -1,4 +1,6 @@
using System;
using System.Threading.Tasks;
using CleanArchitecture.Application.Interfaces;
using CleanArchitecture.Domain.Notifications;
using MediatR;
using Microsoft.AspNetCore.Mvc;
@ -9,8 +11,13 @@ namespace CleanArchitecture.Api.Controllers;
[Route("[controller]")]
public class UserController : ApiController
{
public UserController(NotificationHandler<DomainNotification> notifications) : base(notifications)
private readonly IUserService _userService;
public UserController(
INotificationHandler<DomainNotification> notifications,
IUserService userService) : base(notifications)
{
_userService = userService;
}
[HttpGet]
@ -20,9 +27,10 @@ public class UserController : ApiController
}
[HttpGet("{id}")]
public string GetUserByIdAsync([FromRoute] Guid id)
public async Task<IActionResult> GetUserByIdAsync([FromRoute] Guid id)
{
return "test";
var user = await _userService.GetUserByUserIdAsync(id);
return Response(user);
}
[HttpPost]

View File

@ -1,9 +1,10 @@
using CleanArchitecture.Application.Extensions;
using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.Infrastructure.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
@ -11,13 +12,19 @@ builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseLazyLoadingProxies();
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly("netgo.centralhub.TenantService.Infrastructure"));
b => b.MigrationsAssembly("CleanArchitecture.Infrastructure"));
});
builder.Services.AddInfrastructure();
builder.Services.AddQueryHandlers();
builder.Services.AddServices();
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly);
@ -25,12 +32,8 @@ builder.Services.AddMediatR(cfg =>
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
@ -38,6 +41,14 @@ app.UseAuthorization();
app.MapControllers();
using (IServiceScope scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
ApplicationDbContext appDbContext = services.GetRequiredService<ApplicationDbContext>();
appDbContext.EnsureMigrationsApplied();
}
app.Run();
// Needed for integration tests webapplication factory

View File

@ -9,20 +9,10 @@
}
},
"profiles": {
"http": {
"CleanArchitecture.Api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5201",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7260;http://localhost:5201",
"environmentVariables": {

View File

@ -5,5 +5,8 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=clean-architecture;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
}
}

View File

@ -6,12 +6,12 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="Queries" />
<Folder Include="ViewModels" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,8 @@
using CleanArchitecture.Application.Interfaces;
using CleanArchitecture.Application.Queries.Users.GetUserById;
using CleanArchitecture.Application.Services;
using CleanArchitecture.Application.ViewModels;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Application.Extensions;
@ -15,7 +18,7 @@ public static class ServiceCollectionExtension
public static IServiceCollection AddQueryHandlers(this IServiceCollection services)
{
// services.AddScoped<IQueryHandler<GetUserByIdQuery, User>, GetUserByIdQueryHandler>();
services.AddScoped<IRequestHandler<GetUserByIdQuery, UserViewModel?>, GetUserByIdQueryHandler>();
return services;
}

View File

@ -1,6 +1,10 @@
using CleanArchitecture.Application.ViewModels;
using System.Threading.Tasks;
using System;
namespace CleanArchitecture.Application.Interfaces;
public interface IUserService
{
public Task<UserViewModel?> GetUserByUserIdAsync(Guid userId);
}

View File

@ -0,0 +1,7 @@
using System;
using CleanArchitecture.Application.ViewModels;
using MediatR;
namespace CleanArchitecture.Application.Queries.Users.GetUserById;
public sealed record GetUserByIdQuery(Guid UserId) : IRequest<UserViewModel?>;

View File

@ -0,0 +1,44 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CleanArchitecture.Application.ViewModels;
using CleanArchitecture.Domain.Errors;
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.Interfaces.Repositories;
using CleanArchitecture.Domain.Notifications;
using MediatR;
namespace CleanArchitecture.Application.Queries.Users.GetUserById;
public sealed class GetUserByIdQueryHandler :
IRequestHandler<GetUserByIdQuery, UserViewModel?>
{
private readonly IUserRepository _userRepository;
private readonly IMediatorHandler _bus;
public GetUserByIdQueryHandler(IUserRepository userRepository, IMediatorHandler bus)
{
_userRepository = userRepository;
_bus = bus;
}
public async Task<UserViewModel?> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = _userRepository
.GetAllNoTracking()
.Where(x => x.Id == request.UserId)
.FirstOrDefault();
if (user == null)
{
await _bus.RaiseEventAsync(
new DomainNotification(
nameof(GetUserByIdQuery),
$"User with id {request.UserId} could not be found",
ErrorCodes.ObjectNotFound));
return null;
}
return UserViewModel.FromUser(user);
}
}

View File

@ -1,7 +1,23 @@
using System;
using System.Threading.Tasks;
using CleanArchitecture.Application.Interfaces;
using CleanArchitecture.Application.Queries.Users.GetUserById;
using CleanArchitecture.Application.ViewModels;
using CleanArchitecture.Domain.Interfaces;
namespace CleanArchitecture.Application.Services;
public sealed class UserService : IUserService
{
private readonly IMediatorHandler _bus;
public UserService(IMediatorHandler bus)
{
_bus = bus;
}
public async Task<UserViewModel?> GetUserByUserIdAsync(Guid userId)
{
return await _bus.QueryAsync(new GetUserByIdQuery(userId));
}
}

View File

@ -0,0 +1,23 @@
using System;
using CleanArchitecture.Domain.Entities;
namespace CleanArchitecture.Application.ViewModels;
public sealed class UserViewModel
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string GivenName { get; set; } = string.Empty;
public string Surname { get; set; } = string.Empty;
public static UserViewModel FromUser(User user)
{
return new UserViewModel
{
Id = user.Id,
Email = user.Email,
GivenName = user.GivenName,
Surname = user.Surname
};
}
}

View File

@ -12,6 +12,12 @@
<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace CleanArchitecture.Infrastructure.Extensions;
public static class DbContextExtension
{
public static void EnsureMigrationsApplied(this DbContext context)
{
var applied = context.GetService<IHistoryRepository>().GetAppliedMigrations().Select(m => m.MigrationId);
var total = context.GetService<IMigrationsAssembly>().Migrations.Select(m => m.Key);
if (total.Except(applied).Any())
{
context.Database.Migrate();
}
}
}

View File

@ -0,0 +1,25 @@
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.Interfaces.Repositories;
using CleanArchitecture.Domain.Notifications;
using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.Infrastructure.Repositories;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.Infrastructure.Extensions;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services)
{
// Core Infra
services.AddScoped<IUnitOfWork, UnitOfWork<ApplicationDbContext>>();
services.AddScoped<INotificationHandler<DomainNotification>, DomainNotificationHandler>();
services.AddScoped<IMediatorHandler, InMemoryBus>();
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
return services;
}
}

View File

@ -0,0 +1,62 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230306172301_InitialMigration")]
partial class InitialMigration
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("Deleted")
.HasColumnType("bit");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(320)
.HasColumnType("nvarchar(320)");
b.Property<string>("GivenName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Surname")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class InitialMigration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Email = table.Column<string>(type: "nvarchar(320)", maxLength: 320, nullable: false),
GivenName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Surname = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Deleted = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Users");
}
}
}

View File

@ -0,0 +1,59 @@
// <auto-generated />
using System;
using CleanArchitecture.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CleanArchitecture.Infrastructure.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.3")
.HasAnnotation("Proxies:ChangeTracking", false)
.HasAnnotation("Proxies:CheckEquality", false)
.HasAnnotation("Proxies:LazyLoading", true)
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("CleanArchitecture.Domain.Entities.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier");
b.Property<bool>("Deleted")
.HasColumnType("bit");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(320)
.HasColumnType("nvarchar(320)");
b.Property<string>("GivenName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("Surname")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.ToTable("Users");
});
#pragma warning restore 612, 618
}
}
}