Skip to content

Commit

Permalink
Feature/raulshma/mediatr (#27)
Browse files Browse the repository at this point in the history
* Added mediatr structure and some sample.

* Upgrade EF Core to 7.0.7

* Added back the dbcontext as quick fix for the di, will be removed later.

* User exp years calc.
  • Loading branch information
raulshma authored Jun 25, 2023
1 parent fca869c commit 1f8037c
Show file tree
Hide file tree
Showing 29 changed files with 535 additions and 46 deletions.
54 changes: 54 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"name": ".NET Core Launch (API)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/backend/src/ThreeTee.Api/bin/Debug/net7.0/ThreeTee.Api.dll",
"args": [],
"cwd": "${workspaceFolder}/backend/src/ThreeTee.Api",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Launch (Authentication)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/backend/src/ThreeTee.Authentication/bin/Debug/net7.0/ThreeTee.Authentication.dll",
"args": [],
"cwd": "${workspaceFolder}/backend/src/ThreeTee.Authentication",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dotnet.defaultSolution": "backend\\ThreeTee.sln"
}
41 changes: 41 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/backend/ThreeTee.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/backend/ThreeTee.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/backend/ThreeTee.sln"
],
"problemMatcher": "$msCompile"
}
]
}
13 changes: 13 additions & 0 deletions backend/src/ThreeTee.Api/Controllers/ApiControllerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using MediatR;

namespace ThreeTee.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
public abstract class ApiControllerBase : ControllerBase
{
private ISender? _mediator;

protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetRequiredService<ISender>();
}
9 changes: 5 additions & 4 deletions backend/src/ThreeTee.Api/Controllers/ProjectUserController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ThreeTee.Application.Cqrs.ProjectUsers.Queries;
using ThreeTee.Application.Interfaces;
using ThreeTee.Application.Models.ProjectUser;

Expand All @@ -10,20 +11,20 @@ namespace ThreeTee.Api.Controllers
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ProjectUserController : ControllerBase
public class ProjectUserController : ApiControllerBase
{
private readonly IProjectUserService _projectUserService;

public ProjectUserController(IProjectUserService projectUserService)
{
_projectUserService = projectUserService;
_projectUserService = projectUserService;
}
// GET: api/<ClientController>
[HttpGet]
[Produces(typeof(List<ProjectUserResponse>))]
public async Task<IResult> Get()
public async Task<IResult> Get([FromQuery] GetProjectUsersWithPaginationQuery query)
{
var items = await _projectUserService.GetAsync();
var items = await Mediator.Send(query);
return TypedResults.Ok(items);
}

Expand Down
7 changes: 4 additions & 3 deletions backend/src/ThreeTee.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using ThreeTee.Application.Interfaces;
using ThreeTee.Application.Extension;
using ThreeTee.Infrastructure.Persistence.Npgsql.Data;
using ThreeTee.Infrastructure.Repositories;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -58,16 +57,18 @@
{
option.Configuration = builder.Configuration["Redis"];
});
builder.Services.AddDbContext<EntitiesContext>(options =>
//Remove this later when all controllers are using mediatr
builder.Services.AddDbContext<DbContext, EntitiesContext>(options =>
{
options.UseNpgsql(connectionString);
});

builder.Services.AddHealthChecks()
.AddDbContextCheck<EntitiesContext>();

builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
builder.Services.AddInfrastructurePersistenceServices(connectionString!);
builder.Services.AddApplicationServices();
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));

builder.Host.UseSerilog((context, configuration) =>
{
Expand Down
7 changes: 4 additions & 3 deletions backend/src/ThreeTee.Api/ThreeTee.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MediatR" Version="12.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
19 changes: 19 additions & 0 deletions backend/src/ThreeTee.Application/Behaviours/LoggingBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;

public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
{
private readonly ILogger _logger;

public LoggingBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}

public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
_logger.LogInformation("Request: {Name} {@Request}",
requestName, request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;

public class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
private readonly Stopwatch _timer;
private readonly ILogger<TRequest> _logger;

public PerformanceBehaviour(
ILogger<TRequest> logger)
{
_timer = new Stopwatch();

_logger = logger;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
_timer.Start();

var response = await next();

_timer.Stop();

var elapsedMilliseconds = _timer.ElapsedMilliseconds;

if (elapsedMilliseconds > 500)
{
var requestName = typeof(TRequest).Name;
var userName = string.Empty;


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

return response;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using MediatR;
using Microsoft.Extensions.Logging;

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

public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}

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

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

throw;
}
}
}
34 changes: 34 additions & 0 deletions backend/src/ThreeTee.Application/Behaviours/ValidationBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using FluentValidation;
using MediatR;

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

public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}

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

var validationResults = await Task.WhenAll(
_validators.Select(v =>
v.ValidateAsync(context, cancellationToken)));

var failures = validationResults
.Where(r => r.Errors.Any())
.SelectMany(r => r.Errors)
.ToList();

if (failures.Any())
throw new ValidationException(failures);
}
return await next();
}
}
42 changes: 42 additions & 0 deletions backend/src/ThreeTee.Application/ConfigureServices.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Reflection;
using FluentValidation;
using Mapster;
using MapsterMapper;
using MediatR;
// using MapsterMapper;
using ThreeTee.Application.Interfaces;
using ThreeTee.Application.Services;

namespace Microsoft.Extensions.DependencyInjection;

public static class ConfigureServices
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
//remove these services later
services.AddScoped<IProjectService, ProjectService>();
services.AddScoped<IBillingTypeService, BillingTypeService>();
services.AddScoped<IClientService, ClientService>();
services.AddScoped<IStatusService, StatusService>();
services.AddScoped<IDepartmentService, DepartmentService>();
services.AddScoped<IDesignationService, DesignationService>();
services.AddScoped<IProjectUserService, ProjectUserService>();
//Mapster
var config = new TypeAdapterConfig();
services.AddSingleton(config);
services.AddScoped<IMapper, ServiceMapper>();

//Fluent validation
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
//MediatR
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(PerformanceBehaviour<,>));
});
return services;
}
}
Loading

1 comment on commit 1f8037c

@vercel
Copy link

@vercel vercel bot commented on 1f8037c Jun 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.