diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index d9af39476d..1378da8876 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -12,10 +12,13 @@ namespace Volo.Abp.AspNetCore.Mvc public HashSet IgnoredControllersOnModelExclusion { get; } + public bool AutoModelValidation { get; set; } + public AbpAspNetCoreMvcOptions() { ConventionalControllers = new AbpConventionalControllerOptions(); IgnoredControllersOnModelExclusion = new HashSet(); + AutoModelValidation = true; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs index 751afe84d0..b24e7f554d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Validation/AbpValidationActionFilter.cs @@ -1,7 +1,11 @@ -using System.Threading.Tasks; +using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; +using Volo.Abp.Reflection; +using Volo.Abp.Validation; namespace Volo.Abp.AspNetCore.Mvc.Validation { @@ -9,8 +13,6 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - //TODO: Configuration to disable validation for controllers..? - if (!context.ActionDescriptor.IsControllerAction() || !context.ActionDescriptor.HasObjectResult()) { @@ -18,6 +20,44 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation 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; + } + } + } + context.GetRequiredService().Validate(context.ModelState); await next(); } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs index de0e24002f..3f3ee73549 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs @@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Shouldly; +using Volo.Abp.DependencyInjection; using Volo.Abp.Validation; namespace Volo.Abp.AspNetCore.Mvc.Validation @@ -42,6 +43,31 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation return Task.FromResult(model.Value1); } + [HttpGet] + [Route("disable-validation-object-result-action")] + [DisableValidation] + public Task DisableValidationObjectResultAction(ValidationTest1Model model) + { + ModelState.IsValid.ShouldBeFalse(); + return Task.FromResult("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant()); + } + + [HttpGet] + [Route("object-result-action2")] + public virtual Task ObjectResultAction2(ValidationTest1Model model) + { + ModelState.IsValid.ShouldBeTrue(); //AbpValidationFilter throws exception otherwise + return Task.FromResult(model.Value1); + } + + [HttpGet] + [Route("object-result-action3")] + public virtual Task ObjectResultAction3(ValidationTest1Model model) + { + ModelState.IsValid.ShouldBeFalse(); + return Task.FromResult("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant()); + } + public class ValidationTest1Model { [Required] @@ -107,4 +133,23 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation } } } + + [DisableValidation] + [Route("api/sub1-validation-test")] + public class Sub1ValidationTestController : ValidationTestController + { + + } + + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(ValidationTestController))] + public class Sub2ValidationTestController : ValidationTestController + { + [DisableValidation] + public override Task ObjectResultAction2(ValidationTest1Model model) + { + ModelState.IsValid.ShouldBeFalse(); + return Task.FromResult("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant()); + } + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs index 715e55799a..1ae7373b61 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs @@ -1,5 +1,8 @@ using System.Net; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using Shouldly; using Volo.Abp.Http; using Volo.Abp.Localization; @@ -90,5 +93,45 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation result = await GetResponseAsObjectAsync("/api/validation-test/object-result-action-dynamic-length?value1=123458&value3[0]=53&value3[1]=54&value4=2&value5=1.2&value6=2004-05-04", HttpStatusCode.BadRequest); //value4 has max date of 3/4/2004. result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0); } + + [Fact] + public async Task Should_Disable_Validate() + { + var result = await GetResponseAsStringAsync("/api/validation-test/disable-validation-object-result-action"); + result.ShouldBe("ModelState.IsValid: false"); + } + + [Fact] public async Task SubClass_Should_Disable_Validate_If_Class_Has_DisableValidationAttribute() + { + var result = await GetResponseAsStringAsync("/api/sub1-validation-test/disable-validation-object-result-action"); + result.ShouldBe("ModelState.IsValid: false"); + } + + [Fact] + public async Task SubClass_Should_Disable_Validate_If_Action_Has_DisableValidationAttribute() + { + var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action2"); + result.ShouldBe("ModelState.IsValid: false"); + } + } + + public class DisableAutoModelValidationTestController_Tests : AspNetCoreMvcTestBase + { + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + services.Configure(options => + { + options.AutoModelValidation = false; + }); + + base.ConfigureServices(context, services); + } + + [Fact] + public async Task Should_Disable_Validate_If_AutoModelValidation_Is_False() + { + var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action3"); + result.ShouldBe("ModelState.IsValid: false"); + } } }