Browse Source

Resolved #4496: Introduce the DynamicStringLengthAttribute.

pull/4498/head
Halil İbrahim Kalkan 6 years ago
parent
commit
fe4857481c
  1. 5
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  2. 1
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  3. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs
  4. 54
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicStringLengthAttributeAdapter.cs
  5. 58
      framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicStringLengthAttribute.cs
  6. 24
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs
  7. 16
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs

5
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -16,6 +16,7 @@ using System.Net;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -25,6 +26,7 @@ using Microsoft.Extensions.Localization;
using Volo.Abp.ApiVersioning;
using Volo.Abp.AspNetCore.Mvc.ApiExploring;
using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.DataAnnotations;
using Volo.Abp.AspNetCore.Mvc.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Json;
using Volo.Abp.AspNetCore.Mvc.Localization;
@ -164,6 +166,9 @@ namespace Volo.Abp.AspNetCore.Mvc
partManager.FeatureProviders.Add(new AbpConventionalControllerFeatureProvider(application));
partManager.ApplicationParts.AddIfNotContains(typeof(AbpAspNetCoreMvcModule).Assembly);
context.Services.Replace(ServiceDescriptor.Singleton<IValidationAttributeAdapterProvider, AbpValidationAttributeAdapterProvider>());
context.Services.AddSingleton<ValidationAttributeAdapterProvider>();
Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(context.Services);

1
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs

@ -5,7 +5,6 @@ using Volo.Abp.AspNetCore.Mvc.Conventions;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Features;
using Volo.Abp.AspNetCore.Mvc.ModelBinding;
using Volo.Abp.AspNetCore.Mvc.ModelBinding.Metadata;
using Volo.Abp.AspNetCore.Mvc.Response;
using Volo.Abp.AspNetCore.Mvc.Uow;
using Volo.Abp.AspNetCore.Mvc.Validation;

29
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/AbpValidationAttributeAdapterProvider.cs

@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.Extensions.Localization;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations
{
public class AbpValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
private readonly ValidationAttributeAdapterProvider _defaultAdapter;
public AbpValidationAttributeAdapterProvider(ValidationAttributeAdapterProvider defaultAdapter)
{
_defaultAdapter = defaultAdapter;
}
public virtual IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
var type = attribute.GetType();
if (type == typeof(DynamicStringLengthAttribute))
{
return new DynamicStringLengthAttributeAdapter((DynamicStringLengthAttribute) attribute, stringLocalizer);
}
return _defaultAdapter.GetAttributeAdapter(attribute, stringLocalizer);
}
}
}

54
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/DataAnnotations/DynamicStringLengthAttributeAdapter.cs

@ -0,0 +1,54 @@
using System;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.DataAnnotations
{
public class DynamicStringLengthAttributeAdapter : AttributeAdapterBase<DynamicStringLengthAttribute>
{
private readonly string _max;
private readonly string _min;
public DynamicStringLengthAttributeAdapter(
DynamicStringLengthAttribute attribute,
IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
_max = Attribute.MaximumLength.ToString(CultureInfo.InvariantCulture);
_min = Attribute.MinimumLength.ToString(CultureInfo.InvariantCulture);
}
public override void AddValidation(ClientModelValidationContext context)
{
Check.NotNull(context, nameof(context));
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context));
if (Attribute.MaximumLength != int.MaxValue)
{
MergeAttribute(context.Attributes, "data-val-length-max", _max);
}
if (Attribute.MinimumLength != 0)
{
MergeAttribute(context.Attributes, "data-val-length-min", _min);
}
}
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
Check.NotNull(validationContext, nameof(validationContext));
return GetErrorMessage(
validationContext.ModelMetadata,
validationContext.ModelMetadata.GetDisplayName(),
Attribute.MaximumLength,
Attribute.MinimumLength
);
}
}
}

58
framework/src/Volo.Abp.Core/Volo/Abp/Validation/DynamicStringLengthAttribute.cs

@ -0,0 +1,58 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.Validation
{
/// <summary>
/// Used to determine <see cref="StringLengthAttribute.MaximumLength"/> and <see cref="StringLengthAttribute.MinimumLength"/>
/// properties on the runtime.
/// </summary>
public class DynamicStringLengthAttribute : StringLengthAttribute
{
private static readonly FieldInfo MaximumLengthField;
static DynamicStringLengthAttribute()
{
MaximumLengthField = typeof(StringLengthAttribute).GetField(
"<MaximumLength>k__BackingField",
BindingFlags.Instance | BindingFlags.NonPublic
);
Debug.Assert(MaximumLengthField != null, nameof(MaximumLengthField) + " != null");
}
/// <param name="sourceType">A type to get the values of the properties</param>
/// <param name="maximumLengthPropertyName">The name of the public static property for the <see cref="StringLengthAttribute.MaximumLength"/></param>
/// <param name="minimumLengthPropertyName">The name of the public static property for the <see cref="StringLengthAttribute.MinimumLength"/></param>
public DynamicStringLengthAttribute(
[NotNull] Type sourceType,
[CanBeNull] string maximumLengthPropertyName,
[CanBeNull] string minimumLengthPropertyName = null)
: base(0)
{
Check.NotNull(sourceType, nameof(sourceType));
if (maximumLengthPropertyName != null)
{
var maximumLengthProperty = sourceType.GetProperty(
maximumLengthPropertyName,
BindingFlags.Static | BindingFlags.Public
);
Debug.Assert(maximumLengthProperty != null, nameof(maximumLengthProperty) + " != null");
MaximumLengthField.SetValue(this, (int) maximumLengthProperty.GetValue(null));
}
if (minimumLengthPropertyName != null)
{
var minimumLengthProperty = sourceType.GetProperty(
minimumLengthPropertyName,
BindingFlags.Static | BindingFlags.Public
);
Debug.Assert(minimumLengthProperty != null, nameof(minimumLengthProperty) + " != null");
MinimumLength = (int) minimumLengthProperty.GetValue(null);
}
}
}
}

24
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController.cs

@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Shouldly;
using Volo.Abp.Validation;
namespace Volo.Abp.AspNetCore.Mvc.Validation
{
@ -31,6 +32,14 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
{
return Content("ModelState.IsValid: " + ModelState.IsValid.ToString().ToLowerInvariant());
}
[HttpGet]
[Route("object-result-action-dynamic-length")]
public Task<string> ObjectResultActionDynamicLength(ValidationDynamicTestModel model)
{
ModelState.IsValid.ShouldBeTrue(); //AbpValidationFilter throws exception otherwise
return Task.FromResult(model.Value1);
}
public class ValidationTest1Model
{
@ -38,6 +47,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
[StringLength(5, MinimumLength = 2)]
public string Value1 { get; set; }
}
public class ValidationDynamicTestModel
{
[DynamicStringLength(typeof(Consts), nameof(Consts.MaxValue2Length), nameof(Consts.MinValue2Length))]
public string Value1 { get; set; }
public static class Consts
{
public static int MinValue2Length { get; set; } = 2;
public static int MaxValue2Length { get; set; } = 7;
}
}
public class CustomValidateModel : IValidatableObject
{
@ -51,6 +72,5 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
}
}
}
}
}
}

16
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Validation/ValidationTestController_Tests.cs

@ -58,5 +58,21 @@ namespace Volo.Abp.AspNetCore.Mvc.Validation
result.Error.ValidationErrors.ShouldContain(x => x.Message == "Value1 should be hello");
}
[Fact]
public async Task Should_Validate_Dynamic_Length_Object_Result_Success()
{
var result = await GetResponseAsStringAsync("/api/validation-test/object-result-action-dynamic-length?value1=hello");
result.ShouldBe("hello");
}
[Fact]
public async Task Should_Validate_Dynamic_Length_Object_Result_Failing()
{
var result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=a", HttpStatusCode.BadRequest); //value1 has min length of 2 chars.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
result = await GetResponseAsObjectAsync<RemoteServiceErrorResponse>("/api/validation-test/object-result-action-dynamic-length?value1=12345678", HttpStatusCode.BadRequest); //value1 has max length of 7 chars.
result.Error.ValidationErrors.Length.ShouldBeGreaterThan(0);
}
}
}

Loading…
Cancel
Save