Revised validation.

pull/179/head
Halil İbrahim Kalkan 8 years ago
parent a8c3c9e7db
commit 57256272f2

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.AspNetCore.Mvc.Validation;
using Volo.Abp.Guids;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
@ -20,7 +21,7 @@ namespace Volo.Abp.AspNetCore.Mvc.RazorPages
public ILoggerFactory LoggerFactory { get; set; }
public IObjectValidator ObjectValidator { get; set; }
public IModelStateValidator ModelValidator { get; set; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
@ -31,5 +32,10 @@ namespace Volo.Abp.AspNetCore.Mvc.RazorPages
{
return new NoContentResult();
}
protected virtual void ValidateModel()
{
ModelValidator?.Validate(ModelState);
}
}
}

@ -8,9 +8,9 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
{
private readonly MvcActionInvocationValidator _validator;
private readonly ModelStateValidator _validator;
public AbpValidationActionFilter(MvcActionInvocationValidator validator)
public AbpValidationActionFilter(ModelStateValidator validator)
{
_validator = validator;
}
@ -28,7 +28,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
using (AbpCrossCuttingConcerns.Applying(context.Controller, AbpCrossCuttingConcerns.Validation))
{
_validator.Validate(new MvcActionInvocationValidationContext(context));
_validator.Validate(context.ModelState);
await next();
}
}

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public interface IModelStateValidator
{
void Validate(ModelStateDictionary modelState);
void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState);
}
}

@ -1,9 +0,0 @@
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public interface IMvcModelObjectValidator : IObjectValidator
{
}
}

@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public class ModelStateValidator : IModelStateValidator, ITransientDependency
{
public virtual void Validate(ModelStateDictionary modelState)
{
var validationResult = new AbpValidationResult();
AddErrors(validationResult, modelState);
if (validationResult.Errors.Any())
{
throw new AbpValidationException(
"ModelState is not valid! See ValidationErrors for details.",
validationResult.Errors
);
}
}
public virtual void AddErrors(IAbpValidationResult validationResult, ModelStateDictionary modelState)
{
if (modelState.IsValid)
{
return;
}
foreach (var state in modelState)
{
foreach (var error in state.Value.Errors)
{
validationResult.Errors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
}
}
}
}
}

@ -1,33 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public class MvcActionInvocationValidationContext : MethodInvocationValidationContext
{
public ActionExecutingContext ActionContext { get; }
public MvcActionInvocationValidationContext(ActionExecutingContext actionContext)
: base(actionContext.ActionDescriptor.GetMethodInfo(), GetParameterValues(actionContext))
{
ActionContext = actionContext;
}
private static object[] GetParameterValues(ActionExecutingContext actionContext)
{
var methodInfo = actionContext.ActionDescriptor.GetMethodInfo();
var parameters = methodInfo.GetParameters();
var parameterValues = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
parameterValues[i] = actionContext.ActionArguments.GetOrDefault(parameters[i].Name);
}
return parameterValues;
}
}
}

@ -1,37 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public class MvcActionInvocationValidator : MethodInvocationValidatorBase
{
// ReSharper disable once SuggestBaseTypeForParameter
public MvcActionInvocationValidator(IMvcModelObjectValidator objectValidator)
: base(objectValidator)
{
}
public virtual void Validate(MvcActionInvocationValidationContext context)
{
AddModelStateErrors(context);
ValidateInternal(context);
}
public virtual void AddModelStateErrors(MvcActionInvocationValidationContext context)
{
if (context.ActionContext.ModelState.IsValid)
{
return;
}
foreach (var state in context.ActionContext.ModelState)
{
foreach (var error in state.Value.Errors)
{
context.Errors.Add(new ValidationResult(error.ErrorMessage, new[] { state.Key }));
}
}
}
}
}

@ -1,15 +0,0 @@
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
[ExposeServices(typeof(IMvcModelObjectValidator))]
public class MvcModelValidator : ObjectValidator, IMvcModelObjectValidator
{
public MvcModelValidator(IOptions<AbpValidationOptions> options)
: base(options, NullDataAnnotationValidator.Instance)
{
}
}
}

@ -1,19 +0,0 @@
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
public sealed class NullDataAnnotationValidator : IDataAnnotationValidator
{
public static IDataAnnotationValidator Instance { get; } = new DataAnnotationValidator();
private NullDataAnnotationValidator()
{
}
public void AddDataAnnotationAttributeErrors(IAbpValidationResult validationResult, object validatingObject)
{
}
}
}

@ -35,8 +35,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users
public async Task<NoContentResult> OnPostAsync()
{
ObjectValidator.Validate(UserInfo, nameof(UserInfo));
ObjectValidator.Validate(Roles, nameof(Roles));
ValidateModel();
var input = ObjectMapper.Map<UserInfoViewModel, IdentityUserCreateDto>(UserInfo);
input.RoleNames = Roles.Where(r => r.IsAssigned).Select(r => r.Name).ToArray();

@ -45,8 +45,7 @@ namespace Volo.Abp.Identity.Web.Pages.Identity.Users
public async Task<IActionResult> OnPostAsync()
{
ObjectValidator.Validate(UserInfo, nameof(UserInfo));
ObjectValidator.Validate(Roles, nameof(Roles));
ValidateModel();
var input = ObjectMapper.Map<UserInfoViewModel, IdentityUserUpdateDto>(UserInfo);
input.RoleNames = Roles.Where(r => r.IsAssigned).Select(r => r.Name).ToArray();

@ -7,12 +7,9 @@ namespace Volo.Abp.Validation
{
public List<ValidationResult> Errors { get; }
public List<IShouldNormalize> ObjectsToBeNormalized { get; }
public AbpValidationResult()
{
Errors = new List<ValidationResult>();
ObjectsToBeNormalized = new List<IShouldNormalize>();
}
}
}

@ -8,10 +8,25 @@ namespace Volo.Abp.Validation
{
public class DataAnnotationValidator : IDataAnnotationValidator, ITransientDependency
{
public void Validate(object validatingObject)
{
var validationResult = new AbpValidationResult();
AddErrors(validationResult, validatingObject);
if (validationResult.Errors.Any())
{
throw new AbpValidationException(
"Object state is not valid! See ValidationErrors for details.",
validationResult.Errors
);
}
}
/// <summary>
/// Checks all properties for DataAnnotations attributes.
/// </summary>
public virtual void AddDataAnnotationAttributeErrors(IAbpValidationResult validationResult, object validatingObject)
public virtual void AddErrors(IAbpValidationResult validationResult, object validatingObject)
{
var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
foreach (var property in properties)

@ -6,7 +6,5 @@ namespace Volo.Abp.Validation
public interface IAbpValidationResult
{
List<ValidationResult> Errors { get; }
List<IShouldNormalize> ObjectsToBeNormalized { get; }
}
}

@ -2,6 +2,8 @@ namespace Volo.Abp.Validation
{
public interface IDataAnnotationValidator
{
void AddDataAnnotationAttributeErrors(IAbpValidationResult validationResult, object validatingObject);
void Validate(object validatingObject);
void AddErrors(IAbpValidationResult validationResult, object validatingObject);
}
}

@ -0,0 +1,7 @@
namespace Volo.Abp.Validation
{
public interface IMethodInvocationValidator
{
void Validate(MethodInvocationValidationContext context);
}
}

@ -4,6 +4,6 @@ namespace Volo.Abp.Validation
{
void Validate(object validatingObject, string name = null, bool allowNull = false);
void AddValidatationErrors(IAbpValidationResult validationResult, object validatingObject, string name = null, bool allowNull = false);
void AddErrors(IAbpValidationResult validationResult, object validatingObject, string name = null, bool allowNull = false);
}
}

@ -1,13 +0,0 @@
namespace Volo.Abp.Validation
{
/// <summary>
/// This interface is used to normalize inputs before method execution.
/// </summary>
public interface IShouldNormalize
{
/// <summary>
/// This method is called lastly before method execution (after validation if exists).
/// </summary>
void Normalize();
}
}

@ -15,7 +15,6 @@ namespace Volo.Abp.Validation
Method = method;
ParameterValues = parameterValues;
Parameters = method.GetParameters();
}
}
}

@ -1,19 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.Validation
{
/// <summary>
/// This class is used to validate a method call (invocation) for method arguments.
/// </summary>
public class MethodInvocationValidator : MethodInvocationValidatorBase
public class MethodInvocationValidator : IMethodInvocationValidator, ITransientDependency
{
public MethodInvocationValidator(IObjectValidator objectValidator)
: base(objectValidator)
{
private readonly IObjectValidator _objectValidator;
public MethodInvocationValidator(IObjectValidator objectValidator)
{
_objectValidator = objectValidator;
}
public virtual void Validate(MethodInvocationValidationContext context)
{
ValidateInternal(context);
Check.NotNull(context, nameof(context));
if (context.Parameters.IsNullOrEmpty())
{
return;
}
if (!context.Method.IsPublic)
{
return;
}
if (IsValidationDisabled(context))
{
return;
}
if (context.Parameters.Length != context.ParameterValues.Length)
{
throw new Exception("Method parameter count does not match with argument count!");
}
if (context.Errors.Any() && HasSingleNullArgument(context))
{
ThrowValidationError(context);
}
AddMethodParameterValidationErrors(context);
if (context.Errors.Any())
{
ThrowValidationError(context);
}
}
protected virtual bool IsValidationDisabled(MethodInvocationValidationContext context)
{
if (context.Method.IsDefined(typeof(EnableValidationAttribute), true))
{
return false;
}
return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(context.Method) != null;
}
protected virtual bool HasSingleNullArgument(MethodInvocationValidationContext context)
{
return context.Parameters.Length == 1 && context.ParameterValues[0] == null;
}
protected virtual void ThrowValidationError(MethodInvocationValidationContext context)
{
throw new AbpValidationException(
"Method arguments are not valid! See ValidationErrors for details.",
context.Errors
);
}
protected virtual void AddMethodParameterValidationErrors(MethodInvocationValidationContext context)
{
for (var i = 0; i < context.Parameters.Length; i++)
{
AddMethodParameterValidationErrors(context, context.Parameters[i], context.ParameterValues[i]);
}
}
protected virtual void AddMethodParameterValidationErrors(IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue)
{
var allowNulls = parameterInfo.IsOptional ||
parameterInfo.IsOut ||
TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true);
_objectValidator.AddErrors(context, parameterValue, parameterInfo.Name, allowNulls);
}
}
}
}

@ -1,105 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Reflection;
namespace Volo.Abp.Validation
{
public abstract class MethodInvocationValidatorBase : ITransientDependency
{
private readonly IObjectValidator _objectValidator;
protected MethodInvocationValidatorBase(IObjectValidator objectValidator)
{
_objectValidator = objectValidator;
}
/// <summary>
/// Validates the method invocation.
/// </summary>
protected virtual void ValidateInternal(MethodInvocationValidationContext context)
{
Check.NotNull(context, nameof(context));
if (context.Parameters.IsNullOrEmpty())
{
return;
}
if (!context.Method.IsPublic)
{
return;
}
if (IsValidationDisabled(context))
{
return;
}
if (context.Parameters.Length != context.ParameterValues.Length)
{
throw new Exception("Method parameter count does not match with argument count!");
}
if (context.Errors.Any() && HasSingleNullArgument(context))
{
ThrowValidationError(context);
}
AddMethodParameterValidationErrors(context);
if (context.Errors.Any())
{
ThrowValidationError(context);
}
foreach (var objectToBeNormalized in context.ObjectsToBeNormalized)
{
objectToBeNormalized.Normalize();
}
}
protected virtual bool IsValidationDisabled(MethodInvocationValidationContext context)
{
if (context.Method.IsDefined(typeof(EnableValidationAttribute), true))
{
return false;
}
return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(context.Method) != null;
}
protected virtual bool HasSingleNullArgument(MethodInvocationValidationContext context)
{
return context.Parameters.Length == 1 && context.ParameterValues[0] == null;
}
protected virtual void ThrowValidationError(MethodInvocationValidationContext context)
{
throw new AbpValidationException(
"Method arguments are not valid! See ValidationErrors for details.",
context.Errors
);
}
protected virtual void AddMethodParameterValidationErrors(MethodInvocationValidationContext context)
{
for (var i = 0; i < context.Parameters.Length; i++)
{
AddMethodParameterValidationErrors(context, context.Parameters[i], context.ParameterValues[i]);
}
}
protected virtual void AddMethodParameterValidationErrors(IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue)
{
var allowNulls = parameterInfo.IsOptional ||
parameterInfo.IsOut ||
TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true);
_objectValidator.AddValidatationErrors(context, parameterValue, parameterInfo.Name, allowNulls);
}
}
}

@ -25,23 +25,18 @@ namespace Volo.Abp.Validation
{
var validationResult = new AbpValidationResult();
AddValidatationErrors(validationResult, validatingObject, name, allowNull);
AddErrors(validationResult, validatingObject, name, allowNull);
if (validationResult.Errors.Any())
{
throw new AbpValidationException(
"Method arguments are not valid! See ValidationErrors for details.",
"Object state is not valid! See ValidationErrors for details.",
validationResult.Errors
);
}
foreach (var objectToBeNormalized in validationResult.ObjectsToBeNormalized)
{
objectToBeNormalized.Normalize();
}
}
public virtual void AddValidatationErrors(IAbpValidationResult validationResult, object validatingObject, string name = null, bool allowNull = false)
public virtual void AddErrors(IAbpValidationResult validationResult, object validatingObject, string name = null, bool allowNull = false)
{
if (validatingObject == null && !allowNull)
{
@ -69,7 +64,7 @@ namespace Volo.Abp.Validation
return;
}
_dataAnnotationValidator.AddDataAnnotationAttributeErrors(context, validatingObject);
_dataAnnotationValidator.AddErrors(context, validatingObject);
//Validate items of enumerable
if (validatingObject is IEnumerable && !(validatingObject is IQueryable))
@ -80,12 +75,6 @@ namespace Volo.Abp.Validation
}
}
//Add list to be normalized later
if (validatingObject is IShouldNormalize)
{
context.ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize);
}
//Do not recursively validate for enumerable objects
if (validatingObject is IEnumerable)
{

@ -6,9 +6,9 @@ namespace Volo.Abp.Validation
{
public class ValidationInterceptor : AbpInterceptor, ITransientDependency
{
private readonly MethodInvocationValidator _validator;
private readonly IMethodInvocationValidator _validator;
public ValidationInterceptor(MethodInvocationValidator validator)
public ValidationInterceptor(IMethodInvocationValidator validator)
{
_validator = validator;
}
@ -28,13 +28,12 @@ namespace Volo.Abp.Validation
protected virtual void Validate(IAbpMethodInvocation invocation)
{
_validator
.Validate(
new MethodInvocationValidationContext(
invocation.Method,
invocation.Arguments
)
);
_validator.Validate(
new MethodInvocationValidationContext(
invocation.Method,
invocation.Arguments
)
);
}
}
}

@ -134,22 +134,6 @@ namespace Volo.Abp.Validation
});
}
[Fact]
public void Should_Normalize_Nested_Dtos()
{
var input = new MyMethod7Input
{
Inner = new MyMethod7Input.MyMethod7InputInner
{
Value = 10
}
};
_myAppService.MyMethod7(input);
input.Inner.Value.ShouldBe(12);
}
[Fact]
public void Should_Stop_Recursive_Validation_In_A_Constant_Depth()
{
@ -180,7 +164,6 @@ namespace Volo.Abp.Validation
MyMethodOutput MyMethod4_2(MyMethod4Input input);
MyMethodOutput MyMethod5(MyMethod5Input input);
MyMethodOutput MyMethod6(MyMethod6Input input);
MyMethodOutput MyMethod7(MyMethod7Input input);
MyMethodOutput MyMethod8(MyClassWithRecursiveReference input);
void MyMethodWithNullableEnum(MyEnum? value);
}
@ -223,11 +206,6 @@ namespace Volo.Abp.Validation
return new MyMethodOutput { Result = 42 };
}
public MyMethodOutput MyMethod7(MyMethod7Input input)
{
return new MyMethodOutput { Result = 42 };
}
public MyMethodOutput MyMethod8(MyClassWithRecursiveReference input)
{
return new MyMethodOutput { Result = 42 };
@ -298,26 +276,6 @@ namespace Volo.Abp.Validation
}
}
public class MyMethod7Input : IShouldNormalize
{
public MyMethod7InputInner Inner { get; set; }
public void Normalize()
{
Inner.Value++;
}
public class MyMethod7InputInner : IShouldNormalize
{
public int Value { get; set; }
public void Normalize()
{
Value++;
}
}
}
public class MyClassInList
{
[Required]

Loading…
Cancel
Save