Skip to content

Commit

Permalink
Add AbpFluentValidationActionFilter
Browse files Browse the repository at this point in the history
  • Loading branch information
JadynWong committed May 23, 2024
1 parent 1a750c3 commit eec5290
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 3 deletions.
5 changes: 5 additions & 0 deletions aspnet-core/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@
<Content Remove="$(UserProfile)\.nuget\packages\*\*\contentFiles\any\*\*.abppkg*" />
</ItemGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CopyDocumentationFilesFromPackages>true</CopyDocumentationFilesFromPackages>
<CopyDebugSymbolFilesFromPackages>true</CopyDebugSymbolFilesFromPackages>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\common.props" />

Expand Down
13 changes: 13 additions & 0 deletions aspnet-core/src/BookStore.Application.Contracts/Books/BookDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Volo.Abp.Application.Dtos;

namespace BookStore.Books;

public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }

public float Price { get; set; }

public Guid AuthorId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

namespace BookStore.Books;

public class CreateBookDto
{
public string Name { get; set; }

public float Price { get; set; }

public Guid AuthorId { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Volo.Abp.Application.Services;

namespace BookStore.Books;

public interface IBookAppService : IApplicationService
{
Task CreateAsync(CreateBookDto input);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\common.props" />

Expand All @@ -15,6 +15,7 @@

<ItemGroup>
<PackageReference Include="Volo.Abp.Account.Application" Version="8.1.3" />
<PackageReference Include="Volo.Abp.FluentValidation" Version="8.1.3" />
<PackageReference Include="Volo.Abp.Identity.Application" Version="8.1.3" />
<PackageReference Include="Volo.Abp.PermissionManagement.Application" Version="8.1.3" />
<PackageReference Include="Volo.Abp.TenantManagement.Application" Version="8.1.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoMapper;
using BookStore.Books;

namespace BookStore;

Expand All @@ -9,5 +10,6 @@ public BookStoreApplicationAutoMapperProfile()
/* You can configure your AutoMapper mapping configuration here.
* Alternatively, you can split your mapping configurations
* into multiple profile classes for a better organization. */
CreateMap<CreateBookDto, BookDto>();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Volo.Abp.Account;
using Volo.Abp.AutoMapper;
using Volo.Abp.FeatureManagement;
using Volo.Abp.FluentValidation;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
using Volo.Abp.PermissionManagement;
Expand All @@ -17,7 +18,8 @@ namespace BookStore;
typeof(AbpPermissionManagementApplicationModule),
typeof(AbpTenantManagementApplicationModule),
typeof(AbpFeatureManagementApplicationModule),
typeof(AbpSettingManagementApplicationModule)
typeof(AbpSettingManagementApplicationModule),
typeof(AbpFluentValidationModule)
)]
public class BookStoreApplicationModule : AbpModule
{
Expand Down
12 changes: 12 additions & 0 deletions aspnet-core/src/BookStore.Application/Books/BookAppService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Threading.Tasks;

namespace BookStore.Books;

public class BookAppService : BookStoreAppService, IBookAppService
{
public Task CreateAsync(CreateBookDto input)
{
return Task.CompletedTask;
}
}
19 changes: 19 additions & 0 deletions aspnet-core/src/BookStore.Application/Books/CreateBookValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentValidation;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;

namespace BookStore.Books;

public class CreateBookValidator : AbstractValidator<CreateBookDto>
{
public CreateBookValidator()
{
RuleFor(o => o.Name).NotNull().WithMessage("TEst");
RuleFor(o => o.Price).NotEmpty();
RuleFor(o => o.AuthorId).NotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Reflection;
using Volo.Abp.Validation;

namespace BookStore;

public class AbpFluentValidationActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ActionDescriptor.IsControllerAction())
{
await next();
return;
}

if (!context.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value.AutoModelValidation)
{
await next();
return;
}

if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(context.ActionDescriptor.GetMethodInfo()) != null)
{
await next();
return;
}

if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(context.Controller.GetType()) != null)
{
await next();
return;
}

if (context.ActionDescriptor.GetMethodInfo().DeclaringType != context.Controller.GetType())
{
var baseMethod = context.ActionDescriptor.GetMethodInfo();

var overrideMethod = context.Controller.GetType().GetMethods().FirstOrDefault(x =>
x.DeclaringType == context.Controller.GetType() &&
x.Name == baseMethod.Name &&
x.ReturnType == baseMethod.ReturnType &&
x.GetParameters().Select(p => p.ToString()).SequenceEqual(baseMethod.GetParameters().Select(p => p.ToString())));

if (overrideMethod != null)
{
if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(overrideMethod) != null)
{
await next();
return;
}
}
}

var controllerActionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor;
var serviceProvider = context.HttpContext.RequestServices;

foreach (var parameter in controllerActionDescriptor.Parameters)
{
if (context.ActionArguments.TryGetValue(parameter.Name, out var value))
{
var parameterInfo = (parameter as ControllerParameterDescriptor)?.ParameterInfo;
var parameterType = parameter.ParameterType;

if (value != null &&
!TypeHelper.IsPrimitiveExtended(parameterType) &&
serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(parameterType)) is IValidator validator)
{
var validationContext = new ValidationContext<object>(value);

var validationResult = await validator.ValidateAsync(validationContext, context.HttpContext.RequestAborted);

if (!validationResult.IsValid)
{
foreach (var error in validationResult.Errors)
{
context.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
}
}

await next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
using Volo.Abp.Swashbuckle;
using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;

namespace BookStore;

Expand Down Expand Up @@ -70,6 +73,15 @@ public override void ConfigureServices(ServiceConfigurationContext context)
ConfigureVirtualFileSystem(context);
ConfigureCors(context, configuration);
ConfigureSwaggerServices(context, configuration);

var modelStateInvalidFilter = new ModelStateInvalidFilter(new ApiBehaviorOptions
{
InvalidModelStateResponseFactory = (ActionContext context) => new OkResult()
}, NullLogger.Instance);
Configure<MvcOptions>(options =>
{
options.Filters.Add<AbpFluentValidationActionFilter>(modelStateInvalidFilter.Order - 1);
});
}

private void ConfigureAuthentication(ServiceConfigurationContext context)
Expand Down

0 comments on commit eec5290

Please sign in to comment.