Skip to content

Commit

Permalink
implementing filters
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigojap committed Feb 2, 2021
1 parent 32ac20c commit 42fc6f2
Show file tree
Hide file tree
Showing 15 changed files with 328 additions and 5 deletions.
38 changes: 38 additions & 0 deletions VirtualMind.Application/Commom/Middlewares/ValidationBehaviour.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentValidation;
using MediatR;
using ValidationException = VirtualMind.Application.Exceptions.ValidationException;

namespace VirtualMind.Application.Commom.Middlewares
{
public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

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

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

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

var failures = validationResults.SelectMany(r => r.Errors).Where(f => f != null).ToList();

if (failures.Count != 0)
throw new ValidationException(failures);
}

return await next();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Reflection;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using VirtualMind.Application.Commom.Middlewares;

namespace VirtualMind.Application.Configurations
{
public static class ServiceCollectionConfiguration
{
public static IServiceCollection AddApplicationDI(this IServiceCollection services)
{
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));

return services;
}
}
}
11 changes: 11 additions & 0 deletions VirtualMind.Application/DTOs/ExchangeRateDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace VirtualMind.Application.DTOs
{
public class ExchangeRateDTO
{
public string Purchase { get; set; }

public string Sale { get; set; }

public string LastUpdate { get; set; }
}
}
26 changes: 26 additions & 0 deletions VirtualMind.Application/Exceptions/ValidationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using FluentValidation.Results;
using System;
using System.Collections.Generic;
using System.Linq;

namespace VirtualMind.Application.Exceptions
{
public class ValidationException : Exception
{
public ValidationException()
: base("Validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}

public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}

public IDictionary<string, string[]> Errors { get; }
}
}
38 changes: 38 additions & 0 deletions VirtualMind.Application/Queries/GetCurrencyExchange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using MediatR;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using VirtualMind.Application.DTOs;

namespace VirtualMind.Application.Queries
{
public class GetCurrencyExchange : IRequest<List<ExchangeRateDTO>>
{
//public AcceptableCurrencies CurrencyType { get; set; }

public string CurrencyType { get; set; }
}

//TODO: Put this on domain
public enum AcceptableCurrencies
{
USD,
REAL
}

public class GetCurrencyExchangeHandler : IRequestHandler<GetCurrencyExchange, List<ExchangeRateDTO>>
{
public async Task<List<ExchangeRateDTO>> Handle(GetCurrencyExchange request, CancellationToken cancellationToken)
{
var exchange = new ExchangeRateDTO();
exchange.LastUpdate = "today";
exchange.Purchase = "12.00";
exchange.Sale = "10";

var exchangeList = new List<ExchangeRateDTO>();
exchangeList.Add(exchange);

return await Task.FromResult(exchangeList);
}
}
}
14 changes: 14 additions & 0 deletions VirtualMind.Application/Queries/GetCurrencyExchangeValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FluentValidation;

namespace VirtualMind.Application.Queries
{
public class GetCurrencyExchangeValidator : AbstractValidator<GetCurrencyExchange>
{
public GetCurrencyExchangeValidator()
{
RuleFor(input => input.CurrencyType)
.NotNull().WithMessage("[CurrencyType] can't be null!")
.NotEmpty().WithMessage("[CurrencyType] field is required!");
}
}
}
11 changes: 11 additions & 0 deletions VirtualMind.Application/VirtualMind.Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,15 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.5.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="9.5.0" />
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="Commands\" />
</ItemGroup>

</Project>
8 changes: 8 additions & 0 deletions VirtualMind.Domain/Enums/Currency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace VirtualMind.Domain.Enums
{
public enum Currency
{
USD = 0,
REAL= 1
}
}
11 changes: 11 additions & 0 deletions VirtualMind.Domain/VirtualMind.Domain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<Folder Include="Entities\" />
</ItemGroup>

</Project>
12 changes: 9 additions & 3 deletions VirtualMind.WebApp/Controllers/WeatherForecastController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using VirtualMind.Application.Queries;

namespace VirtualMind.WebApp.Controllers
{
Expand All @@ -17,15 +19,19 @@ public class WeatherForecastController : ControllerBase
};

private readonly ILogger<WeatherForecastController> _logger;
private readonly IMediator _mediator;

public WeatherForecastController(ILogger<WeatherForecastController> logger)
public WeatherForecastController(ILogger<WeatherForecastController> logger, IMediator mediator)
{
_logger = logger;
_mediator = mediator;
}

[HttpGet]
public IEnumerable<WeatherForecast> Get()
public async Task<IEnumerable<WeatherForecast>> Get([FromQuery]GetCurrencyExchange getCurrencyExchange)
{
var resposne = await this._mediator.Send(getCurrencyExchange);

var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Expand Down
93 changes: 93 additions & 0 deletions VirtualMind.WebApp/Filters/ApiExceptionFilterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using VirtualMind.Application.Exceptions;

namespace VirtualMind.WebApp.Filters
{
public class ApiExceptionFilterAttribute : ExceptionFilterAttribute
{

private readonly IDictionary<Type, Action<ExceptionContext>> _exceptionHandlers;

public ApiExceptionFilterAttribute()
{
// Register known exception types and handlers.
_exceptionHandlers = new Dictionary<Type, Action<ExceptionContext>>
{
{ typeof(ValidationException), HandleValidationException }
};
}

public override void OnException(ExceptionContext context)
{
HandleException(context);

base.OnException(context);
}

private void HandleException(ExceptionContext context)
{
Type type = context.Exception.GetType();

if (_exceptionHandlers.ContainsKey(type))
{
_exceptionHandlers[type].Invoke(context);
return;
}

if (!context.ModelState.IsValid)
{
HandleInvalidModelStateException(context);
return;
}

HandleUnknownException(context);
}

private void HandleValidationException(ExceptionContext context)
{
var exception = context.Exception as ValidationException;

var details = new ValidationProblemDetails(exception.Errors)
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
};

context.Result = new BadRequestObjectResult(details);

context.ExceptionHandled = true;
}

private void HandleInvalidModelStateException(ExceptionContext context)
{
var details = new ValidationProblemDetails(context.ModelState)
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1"
};

context.Result = new BadRequestObjectResult(details);

context.ExceptionHandled = true;
}

private void HandleUnknownException(ExceptionContext context)
{
var details = new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "An error occurred while processing your request.",
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1"
};

context.Result = new ObjectResult(details)
{
StatusCode = StatusCodes.Status500InternalServerError
};

context.ExceptionHandled = true;
}
}
}
31 changes: 31 additions & 0 deletions VirtualMind.WebApp/Filters/FilterServiceConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace VirtualMind.WebApp.Filters
{
public static class FilterServiceConfiguration
{
public static void RegisterGlobalFilters(this IServiceCollection services)
{
EnableExceptionFilterAtribute(services);
SupressDefaultValidators(services);
}

private static void EnableExceptionFilterAtribute(IServiceCollection services)
{
services.AddControllersWithViews(options =>
options.Filters.Add<ApiExceptionFilterAttribute>())
.AddFluentValidation();

}

private static void SupressDefaultValidators(IServiceCollection services)
{
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
}
}
8 changes: 6 additions & 2 deletions VirtualMind.WebApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using VirtualMind.Application.Configurations;
using VirtualMind.WebApp.Filters;

namespace VirtualMind.WebApp
{
Expand All @@ -18,8 +20,10 @@ public Startup(IConfiguration configuration)

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
{
services.AddApplicationDI();
services.RegisterGlobalFilters();

// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
Expand Down
5 changes: 5 additions & 0 deletions VirtualMind.WebApp/VirtualMind.WebApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="9.5.0" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.8" />
</ItemGroup>

Expand All @@ -23,6 +24,10 @@
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\VirtualMind.Application\VirtualMind.Application.csproj" />
</ItemGroup>

<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
Expand Down
Loading

0 comments on commit 42fc6f2

Please sign in to comment.