Skip to content

Commit 92501eb

Browse files
authored
Merge pull request dotnet-architecture#133 from dotnet/Validation-decorator-integration
dotnet-architecture#39 Validation decorator integration
2 parents 352739c + 007525b commit 92501eb

5 files changed

Lines changed: 124 additions & 5 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using FluentValidation;
2+
using MediatR;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace Ordering.API.Application.Decorators
9+
{
10+
public class ValidatorDecorator<TRequest, TResponse>
11+
: IAsyncRequestHandler<TRequest, TResponse>
12+
where TRequest : IAsyncRequest<TResponse>
13+
{
14+
private readonly IAsyncRequestHandler<TRequest, TResponse> _inner;
15+
private readonly IValidator<TRequest>[] _validators;
16+
17+
18+
public ValidatorDecorator(
19+
IAsyncRequestHandler<TRequest, TResponse> inner,
20+
IValidator<TRequest>[] validators)
21+
{
22+
_inner = inner;
23+
_validators = validators;
24+
}
25+
26+
public async Task<TResponse> Handle(TRequest message)
27+
{
28+
var failures = _validators
29+
.Select(v => v.Validate(message))
30+
.SelectMany(result => result.Errors)
31+
.Where(error => error != null)
32+
.ToList();
33+
34+
if (failures.Any())
35+
{
36+
throw new ValidationException(
37+
$"Command Validation Errors for type {typeof(TRequest).Name}", failures);
38+
}
39+
40+
var response = await _inner.Handle(message);
41+
42+
return response;
43+
}
44+
}
45+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using FluentValidation;
2+
using MediatR;
3+
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Threading.Tasks;
8+
using static Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands.CreateOrderCommand;
9+
10+
namespace Ordering.API.Application.Validations
11+
{
12+
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
13+
{
14+
public CreateOrderCommandValidator()
15+
{
16+
RuleFor(order => order.City).NotEmpty();
17+
RuleFor(order => order.Street).NotEmpty();
18+
RuleFor(order => order.State).NotEmpty();
19+
RuleFor(order => order.Country).NotEmpty();
20+
RuleFor(order => order.ZipCode).NotEmpty();
21+
RuleFor(order => order.CardNumber).NotEmpty().Length(12, 19);
22+
RuleFor(order => order.CardHolderName).NotEmpty();
23+
RuleFor(order => order.CardExpiration).NotEmpty().Must(BeValidExpirationDate).WithMessage("Please specify a valid card expiration date");
24+
RuleFor(order => order.CardSecurityNumber).NotEmpty().Length(3);
25+
RuleFor(order => order.CardTypeId).NotEmpty();
26+
RuleFor(order => order.OrderItems).Must(ContainOrderItems).WithMessage("No order items found");
27+
}
28+
29+
private bool BeValidExpirationDate(DateTime dateTime)
30+
{
31+
return dateTime >= DateTime.UtcNow;
32+
}
33+
34+
private bool ContainOrderItems(IEnumerable<OrderItemDTO> orderItems)
35+
{
36+
return orderItems.Any();
37+
}
38+
}
39+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using FluentValidation;
2+
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace Ordering.API.Application.Validations
9+
{
10+
public class IdentifierCommandValidator : AbstractValidator<IdentifiedCommand<CreateOrderCommand,bool>>
11+
{
12+
public IdentifierCommandValidator()
13+
{
14+
RuleFor(customer => customer.Id).NotEmpty();
15+
}
16+
}
17+
}

src/Services/Ordering/Ordering.API/Infrastructure/AutofacModules/MediatorModule.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using Autofac;
22
using Autofac.Core;
3+
using FluentValidation;
34
using MediatR;
45
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
56
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Decorators;
7+
using Ordering.API.Application.Decorators;
68
using Ordering.API.Application.DomainEventHandlers.OrderStartedEvent;
9+
using Ordering.API.Application.Validations;
710
using Ordering.Domain.Events;
811
using System.Collections.Generic;
912
using System.Linq;
@@ -24,11 +27,17 @@ protected override void Load(ContainerBuilder builder)
2427
.Where(i => i.IsClosedTypeOf(typeof(IAsyncRequestHandler<,>)))
2528
.Select(i => new KeyedService("IAsyncRequestHandler", i)));
2629

27-
// Register all the Domain Event Handler classes (they implement IAsyncNotificationHandler<>) in assembly holding the Domain Events
30+
// Register all the event classes (they implement IAsyncNotificationHandler) in assembly holding the Commands
31+
builder.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly)
32+
.As(o => o.GetInterfaces()
33+
.Where(i => i.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
34+
.Select(i => new KeyedService("IAsyncNotificationHandler", i)));
35+
2836
builder
29-
.RegisterAssemblyTypes(typeof(ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler).GetTypeInfo().Assembly)
30-
.Where(t => t.IsClosedTypeOf(typeof(IAsyncNotificationHandler<>)))
31-
.AsImplementedInterfaces();
37+
.RegisterAssemblyTypes(typeof(CreateOrderCommandValidator).GetTypeInfo().Assembly)
38+
.Where(t => t.IsClosedTypeOf(typeof(IValidator<>)))
39+
.AsImplementedInterfaces();
40+
3241

3342
builder.Register<SingleInstanceFactory>(context =>
3443
{
@@ -44,9 +53,16 @@ protected override void Load(ContainerBuilder builder)
4453
return t => (IEnumerable<object>)componentContext.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
4554
});
4655

56+
57+
4758
builder.RegisterGenericDecorator(typeof(LogDecorator<,>),
4859
typeof(IAsyncRequestHandler<,>),
49-
"IAsyncRequestHandler");
60+
"IAsyncRequestHandler")
61+
.Keyed("handlerDecorator", typeof(IAsyncRequestHandler<,>));
62+
63+
builder.RegisterGenericDecorator(typeof(ValidatorDecorator<,>),
64+
typeof(IAsyncRequestHandler<,>),
65+
fromKey: "handlerDecorator");
5066
}
5167
}
5268
}

src/Services/Ordering/Ordering.API/Ordering.API.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
</ItemGroup>
2929

3030
<ItemGroup>
31+
<PackageReference Include="FluentValidation.AspNetCore" Version="6.4.0" />
32+
<PackageReference Include="FluentValidation.MVC6" Version="6.4.0" />
3133
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="1.1.0" />
3234
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.0.0" />
3335
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.0" />

0 commit comments

Comments
 (0)