Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/raulshma/mediatr #27

Merged
merged 5 commits into from
Jun 25, 2023
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
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