From eec5290d5c1ac20c0ae7abedb2141fade058e61b Mon Sep 17 00:00:00 2001 From: Jadyn Date: Thu, 23 May 2024 15:16:42 +0800 Subject: [PATCH] Add `AbpFluentValidationActionFilter` --- aspnet-core/common.props | 5 + .../BookStore.Application.Contracts.csproj | 2 +- .../Books/BookDto.cs | 13 +++ .../Books/CreateBookDto.cs | 17 ++++ .../Books/IBookAppService.cs | 9 ++ .../BookStore.Application.csproj | 3 +- .../BookStoreApplicationAutoMapperProfile.cs | 2 + .../BookStoreApplicationModule.cs | 4 +- .../Books/BookAppService.cs | 12 +++ .../Books/CreateBookValidator.cs | 19 ++++ .../AbpFluentValidationActionFilter.cs | 94 +++++++++++++++++++ .../BookStoreHttpApiHostModule.cs | 12 +++ 12 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 aspnet-core/src/BookStore.Application.Contracts/Books/BookDto.cs create mode 100644 aspnet-core/src/BookStore.Application.Contracts/Books/CreateBookDto.cs create mode 100644 aspnet-core/src/BookStore.Application.Contracts/Books/IBookAppService.cs create mode 100644 aspnet-core/src/BookStore.Application/Books/BookAppService.cs create mode 100644 aspnet-core/src/BookStore.Application/Books/CreateBookValidator.cs create mode 100644 aspnet-core/src/BookStore.HttpApi.Host/AbpFluentValidationActionFilter.cs diff --git a/aspnet-core/common.props b/aspnet-core/common.props index 22d34c4..03d453c 100644 --- a/aspnet-core/common.props +++ b/aspnet-core/common.props @@ -16,4 +16,9 @@ + + true + true + + \ No newline at end of file diff --git a/aspnet-core/src/BookStore.Application.Contracts/BookStore.Application.Contracts.csproj b/aspnet-core/src/BookStore.Application.Contracts/BookStore.Application.Contracts.csproj index f544d0a..3ff8fed 100644 --- a/aspnet-core/src/BookStore.Application.Contracts/BookStore.Application.Contracts.csproj +++ b/aspnet-core/src/BookStore.Application.Contracts/BookStore.Application.Contracts.csproj @@ -1,4 +1,4 @@ - + diff --git a/aspnet-core/src/BookStore.Application.Contracts/Books/BookDto.cs b/aspnet-core/src/BookStore.Application.Contracts/Books/BookDto.cs new file mode 100644 index 0000000..8acb3f8 --- /dev/null +++ b/aspnet-core/src/BookStore.Application.Contracts/Books/BookDto.cs @@ -0,0 +1,13 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace BookStore.Books; + +public class BookDto : AuditedEntityDto +{ + public string Name { get; set; } + + public float Price { get; set; } + + public Guid AuthorId { get; set; } +} \ No newline at end of file diff --git a/aspnet-core/src/BookStore.Application.Contracts/Books/CreateBookDto.cs b/aspnet-core/src/BookStore.Application.Contracts/Books/CreateBookDto.cs new file mode 100644 index 0000000..aef4b4d --- /dev/null +++ b/aspnet-core/src/BookStore.Application.Contracts/Books/CreateBookDto.cs @@ -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; } +} diff --git a/aspnet-core/src/BookStore.Application.Contracts/Books/IBookAppService.cs b/aspnet-core/src/BookStore.Application.Contracts/Books/IBookAppService.cs new file mode 100644 index 0000000..059febb --- /dev/null +++ b/aspnet-core/src/BookStore.Application.Contracts/Books/IBookAppService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace BookStore.Books; + +public interface IBookAppService : IApplicationService +{ + Task CreateAsync(CreateBookDto input); +} \ No newline at end of file diff --git a/aspnet-core/src/BookStore.Application/BookStore.Application.csproj b/aspnet-core/src/BookStore.Application/BookStore.Application.csproj index 5c7f0e2..2c245ba 100644 --- a/aspnet-core/src/BookStore.Application/BookStore.Application.csproj +++ b/aspnet-core/src/BookStore.Application/BookStore.Application.csproj @@ -1,4 +1,4 @@ - + @@ -15,6 +15,7 @@ + diff --git a/aspnet-core/src/BookStore.Application/BookStoreApplicationAutoMapperProfile.cs b/aspnet-core/src/BookStore.Application/BookStoreApplicationAutoMapperProfile.cs index 2f15e6a..736ae67 100644 --- a/aspnet-core/src/BookStore.Application/BookStoreApplicationAutoMapperProfile.cs +++ b/aspnet-core/src/BookStore.Application/BookStoreApplicationAutoMapperProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using BookStore.Books; namespace BookStore; @@ -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(); } } diff --git a/aspnet-core/src/BookStore.Application/BookStoreApplicationModule.cs b/aspnet-core/src/BookStore.Application/BookStoreApplicationModule.cs index 565103a..a716186 100644 --- a/aspnet-core/src/BookStore.Application/BookStoreApplicationModule.cs +++ b/aspnet-core/src/BookStore.Application/BookStoreApplicationModule.cs @@ -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; @@ -17,7 +18,8 @@ namespace BookStore; typeof(AbpPermissionManagementApplicationModule), typeof(AbpTenantManagementApplicationModule), typeof(AbpFeatureManagementApplicationModule), - typeof(AbpSettingManagementApplicationModule) + typeof(AbpSettingManagementApplicationModule), + typeof(AbpFluentValidationModule) )] public class BookStoreApplicationModule : AbpModule { diff --git a/aspnet-core/src/BookStore.Application/Books/BookAppService.cs b/aspnet-core/src/BookStore.Application/Books/BookAppService.cs new file mode 100644 index 0000000..66518cc --- /dev/null +++ b/aspnet-core/src/BookStore.Application/Books/BookAppService.cs @@ -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; + } +} \ No newline at end of file diff --git a/aspnet-core/src/BookStore.Application/Books/CreateBookValidator.cs b/aspnet-core/src/BookStore.Application/Books/CreateBookValidator.cs new file mode 100644 index 0000000..7e258df --- /dev/null +++ b/aspnet-core/src/BookStore.Application/Books/CreateBookValidator.cs @@ -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 +{ + public CreateBookValidator() + { + RuleFor(o => o.Name).NotNull().WithMessage("TEst"); + RuleFor(o => o.Price).NotEmpty(); + RuleFor(o => o.AuthorId).NotEmpty(); + } +} diff --git a/aspnet-core/src/BookStore.HttpApi.Host/AbpFluentValidationActionFilter.cs b/aspnet-core/src/BookStore.HttpApi.Host/AbpFluentValidationActionFilter.cs new file mode 100644 index 0000000..0792b61 --- /dev/null +++ b/aspnet-core/src/BookStore.HttpApi.Host/AbpFluentValidationActionFilter.cs @@ -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>().Value.AutoModelValidation) + { + await next(); + return; + } + + if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(context.ActionDescriptor.GetMethodInfo()) != null) + { + await next(); + return; + } + + if (ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(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(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(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(); + } +} \ No newline at end of file diff --git a/aspnet-core/src/BookStore.HttpApi.Host/BookStoreHttpApiHostModule.cs b/aspnet-core/src/BookStore.HttpApi.Host/BookStoreHttpApiHostModule.cs index 275d415..3e2d68c 100644 --- a/aspnet-core/src/BookStore.HttpApi.Host/BookStoreHttpApiHostModule.cs +++ b/aspnet-core/src/BookStore.HttpApi.Host/BookStoreHttpApiHostModule.cs @@ -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; @@ -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(options => + { + options.Filters.Add(modelStateInvalidFilter.Order - 1); + }); } private void ConfigureAuthentication(ServiceConfigurationContext context)