Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion scripts/keycloak/realm-export.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,42 @@
"quickLoginCheckMilliSeconds": 1000,
"maxDeltaTimeSeconds": 43200,
"failureFactor": 30,
"users": [
{
"username": "AdminUser",
"enabled": true,
"emailVerified": true,
"firstName": "admin",
"lastName": "user",
"email": "admin@mail.com",
"credentials": [
{
"type": "password",
"value": "password"
}
],
"groups": [
"Admin"
]
},
{
"username": "StandardUser",
"enabled": true,
"emailVerified": true,
"firstName": "Standard",
"lastName": "User",
"email": "standard@mail.com",
"credentials": [
{
"type": "password",
"value": "password"
}
],
"groups": [
"Standard"
]
}
],
"roles": {
"realm": [
{
Expand Down Expand Up @@ -768,7 +804,8 @@
"phone",
"offline_access",
"organization",
"microprofile-jwt"
"microprofile-jwt",
"minimal-api-aud"
]
},
{
Expand Down
3 changes: 1 addition & 2 deletions src/MinimalApiTemplate.Api/Configurations/Swagger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public static void ConfigureSwagger(this IServiceCollection services, IConfigura
TokenUrl = new Uri(config["AuthorityOptions:TokenUrl"]!),
Scopes = new Dictionary<string, string>
{
{ "openid", "OpenID scope" },
{ "profile", "Profile scope" }
{ "minimal-api-aud", "Minimal Api" }
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/MinimalApiTemplate.Api/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
global using System.Net;
global using Riok.Mapperly.Abstractions;
global using MediatR;
global using Mediator;
global using Microsoft.AspNetCore.Http.HttpResults;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.OutputCaching;
Expand Down
2 changes: 1 addition & 1 deletion src/MinimalApiTemplate.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:8000/swagger"
"applicationUrl": "https://localhost:8901/swagger"
},
"IIS Express": {
"commandName": "IISExpress",
Expand Down
6 changes: 3 additions & 3 deletions src/MinimalApiTemplate.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
"AuthorityOptions": {
"Authority": "http://localhost:8930/realms/my-realm",
"AuthorizationUrl": "http://localhost:8930/realms/my-realm/protocol/openid-connect/auth",
"MetaDataUrl": "http://host.docker.internal:8930/realms/my-realm/.well-known/openid-configuration",
"TokenUrl": "http://host.docker.internal:8930/realms/my-realm/protocol/openid-connect/token",
"MetaDataUrl": "http://localhost:8930/realms/my-realm/.well-known/openid-configuration",
"TokenUrl": "http://localhost:8930/realms/my-realm/protocol/openid-connect/token",
"Issuer": "http://localhost:8930/realms/my-realm",
"Client": "minimal-api-client",
"Audience": "account"
"Audience": "minimal-api-client"
},
"Serilog": {
"Using": [
Expand Down
15 changes: 8 additions & 7 deletions src/MinimalApiTemplate.AppHost/MinimalApiTemplate.AppHost.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.Seq" Version="9.1.0" />
<PackageReference Include="Aspire.Hosting.SqlServer" Version="9.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.3" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.Keycloak" Version="9.2.0-preview.1.25209.2" />
<PackageReference Include="Aspire.Hosting.RabbitMQ" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.Seq" Version="9.2.0" />
<PackageReference Include="Aspire.Hosting.SqlServer" Version="9.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />

</ItemGroup>

Expand Down
9 changes: 8 additions & 1 deletion src/MinimalApiTemplate.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@
.WithLifetime(ContainerLifetime.Persistent)
.WithManagementPlugin(8001);

builder.AddProject<Projects.MinimalApiTemplate_Api>("minimalapitemplate-api")
var keycloak = builder.AddKeycloak("Keycloak", 8930)
.WithDataVolume()
.WithRealmImport("../../scripts/keycloak/")
.WithLifetime(ContainerLifetime.Persistent);

builder.AddProject<Projects.MinimalApiTemplate_Api>("minimalapitemplate-api")
.WithReference(database)
.WaitFor(database)
.WithReference(rabbit)
.WaitFor(rabbit)
.WithReference(redis)
.WaitFor(redis)
.WaitFor(seq)
.WithReference(keycloak)
.WaitFor(keycloak)
.WithEnvironment("MassTransit__PublishEnabled", "true")
.WithEnvironment("SEQ_SERVER_URL", "http://localhost:8002");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using MinimalApiTemplate.Application.Common.Settings;

namespace MinimalApiTemplate.Application.Common.Behaviours;

public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TRequest : IMessage
{
private readonly Stopwatch _timer;
private readonly ILogger<TRequest> _logger;
private readonly ICurrentUserService _currentUserService;
private readonly IServiceScopeFactory _serviceScope;
private readonly AppSettings _appSettings;

public PerformanceBehaviour(
#pragma warning disable S6672 // Generic logger injection should match enclosing type
ILogger<TRequest> logger,
#pragma warning restore S6672 // Generic logger injection should match enclosing type
ICurrentUserService currentUserService,
IServiceScopeFactory serviceScope,
IOptions<AppSettings> appSettings)
{
_timer = new Stopwatch();

_logger = logger;
_currentUserService = currentUserService;
_serviceScope = serviceScope;
_appSettings = appSettings.Value;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
public async ValueTask<TResponse> Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate<TRequest, TResponse> next)
{

_timer.Start();

var response = await next();
var response = await next(message, cancellationToken);

_timer.Stop();

Expand All @@ -39,11 +41,15 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
if (_appSettings.Logs.Performance.LogSlowRunningHandlers
&& elapsedMilliseconds >= _appSettings.Logs.Performance.SlowRunningHandlerThreshold)
{
using var scope = _serviceScope.CreateScope();

var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;

var currentUserService = scope.ServiceProvider.GetRequiredService<ICurrentUserService>();
var userId = currentUserService.UserId ?? string.Empty;

_logger.LogWarning("Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}",
requestName, elapsedMilliseconds, userId, request);
requestName, elapsedMilliseconds, userId, message);
}

return response;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace MinimalApiTemplate.Application.Common.Behaviours;

public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TRequest : IMessage
{
private readonly ILogger<TRequest> _logger;

Expand All @@ -12,17 +12,17 @@ public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
_logger = logger;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
public async ValueTask<TResponse> Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate<TRequest, TResponse> next)
{
try
{
return await next();
return await next(message, cancellationToken);
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;

_logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);
_logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, message);

throw;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace MinimalApiTemplate.Application.Common.Behaviours;

public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TRequest : IMessage
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

Expand All @@ -10,11 +10,11 @@ public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
_validators = validators;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
public async ValueTask<TResponse> Handle(TRequest message, CancellationToken cancellationToken, MessageHandlerDelegate<TRequest, TResponse> next)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);
var context = new ValidationContext<TRequest>(message);

var validationResults = await Task.WhenAll(
_validators.Select(v =>
Expand All @@ -28,6 +28,6 @@ public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TRe
if (failures.Count != 0)
throw new DataValidationFailureException(failures);
}
return await next();
return await next(message, cancellationToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ protected BasePublishExternalEventHander(
_logger = logger;
}

public virtual async Task Handle(TNotification notification, CancellationToken cancellationToken)
public virtual async ValueTask Handle(TNotification notification, CancellationToken cancellationToken)
{
var message = MapMessage(notification);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using MinimalApiTemplate.Domain.Common;
using MinimalApiTemplate.Messages.V1;
using MinimalApiTemplate.Messages.V1;

namespace MinimalApiTemplate.Application.Common.Events;

Expand All @@ -17,5 +16,5 @@
return MapMessage<TSource, TDest>(source);
}

private static partial ToDoItemCreated MapToDoItemCreated(TodoItemCreatedEvent source);

Check warning on line 19 in src/MinimalApiTemplate.Application/Common/Events/EventMapper.cs

View workflow job for this annotation

GitHub Actions / build

The member CorrelationId on the mapping target type MinimalApiTemplate.Messages.V1.ToDoItemCreated was not found on the mapping source type MinimalApiTemplate.Domain.Events.TodoItemCreatedEvent

Check warning on line 19 in src/MinimalApiTemplate.Application/Common/Events/EventMapper.cs

View workflow job for this annotation

GitHub Actions / build

The member CorrelationId on the mapping target type MinimalApiTemplate.Messages.V1.ToDoItemCreated was not found on the mapping source type MinimalApiTemplate.Domain.Events.TodoItemCreatedEvent
}
14 changes: 7 additions & 7 deletions src/MinimalApiTemplate.Application/ConfigureServices.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Reflection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using MinimalApiTemplate.Application.Common.Behaviours;

namespace Microsoft.Extensions.DependencyInjection;
Expand All @@ -8,12 +7,13 @@ public static class ConfigureServices
{
public static IHostApplicationBuilder AddApplicationServices(this IHostApplicationBuilder builder)
{
builder.Services.AddMediatR(options =>
builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
builder.Services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));

builder.Services.AddMediator(options =>
{
options.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
options.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));
options.ServiceLifetime = ServiceLifetime.Scoped;
});

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CreateTodoItemCommandHandler(
_toDoItemMetrics = toDoItemMetrics;
}

public async Task<long> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
public async ValueTask<long> Handle(CreateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = request.MapToEntity();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public DeleteTodoItemCommandHandler(IToDoItemRepository toDoItemRepository)
_toDoItemRepository = toDoItemRepository;
}

public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
public async ValueTask<Unit> Handle(DeleteTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await _toDoItemRepository.GetByIdAsync(request.Id, cancellationToken)
?? throw new NotFoundException(nameof(TodoItem), request.Id);
Expand All @@ -23,5 +23,7 @@ public async Task Handle(DeleteTodoItemCommand request, CancellationToken cancel

await _toDoItemRepository.DeleteAsync(entity, cancellationToken);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public UpdateTodoItemCommandHandler(IToDoItemRepository toDoItemRepository)
_toDoItemRepository = toDoItemRepository;
}

public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
public async ValueTask<Unit> Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await _toDoItemRepository.GetByIdAsync(request.Id, cancellationToken)
?? throw new NotFoundException(nameof(TodoItem), request.Id);
Expand All @@ -29,5 +29,7 @@ public async Task Handle(UpdateTodoItemCommand request, CancellationToken cancel

await _toDoItemRepository.UpdateAsync(entity, cancellationToken);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public TodoItemCreatedEventHandler(ILogger<TodoItemCreatedEventHandler> logger)
_logger = logger;
}

public Task Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken)
public ValueTask Handle(TodoItemCreatedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", nameof(TodoItemCreatedEvent));

return Task.CompletedTask;
return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ public TodoItemDeletedEventHandler(ILogger<TodoItemDeletedEventHandler> logger)
_logger = logger;
}

public Task Handle(TodoItemDeletedEvent notification, CancellationToken cancellationToken)
public ValueTask Handle(TodoItemDeletedEvent notification, CancellationToken cancellationToken)
{
_logger.LogInformation("Domain Event: {DomainEvent}", nameof(TodoItemDeletedEvent));

return Task.CompletedTask;
return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public GetToDoItemQueryHandler(IApplicationDbContext context)
_context = context;
}

public async Task<GetToDoItemDto> Handle(GetToDoItemQuery request, CancellationToken cancellationToken)
public async ValueTask<GetToDoItemDto> Handle(GetToDoItemQuery request, CancellationToken cancellationToken)
{
var data = await _context.TodoItems
.AsNoTracking()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public GetTodoItemsWithPaginationQueryHandler(IApplicationDbContext context)
_context = context;
}

public async Task<PaginatedList<GetTodoItemsDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
public async ValueTask<PaginatedList<GetTodoItemsDto>> Handle(GetTodoItemsWithPaginationQuery request, CancellationToken cancellationToken)
{
var querableToDoItems = _context.TodoItems.AsNoTracking();

Expand Down
Loading
Loading