[Fixes #2817] Support client side validation for all numeric types

This commit is contained in:
Ajay Bhargav Baaskaran 2015-08-10 16:26:06 -07:00
parent 7aa5967cd4
commit 4295a57504
11 changed files with 209 additions and 3 deletions

View File

@ -2,11 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public interface IClientModelValidator
{
IEnumerable<ModelClientValidationRule> GetClientValidationRules(ClientModelValidationContext context);
IEnumerable<ModelClientValidationRule> GetClientValidationRules([NotNull] ClientModelValidationContext context);
}
}

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
@ -13,6 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
/// by updating <see cref="ClientValidatorProviderContext.Validators"/>.
/// </summary>
/// <param name="context">The <see cref="ClientModelValidationContext"/> associated with this call.</param>
void GetValidators(ClientValidatorProviderContext context);
void GetValidators([NotNull] ClientValidatorProviderContext context);
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// This is a <see cref="ModelClientValidationRule"/> for numeric values.
/// </summary>
public class ModelClientValidationNumericRule : ModelClientValidationRule
{
private const string NumericValidationType = "number";
/// <summary>
/// Creates an instance of <see cref="ModelClientValidationNumericRule"/>
/// with the given <paramref name="errorMessage"/>.
/// </summary>
/// <param name="errorMessage">The error message to be displayed.</param>
public ModelClientValidationNumericRule([NotNull] string errorMessage)
: base(NumericValidationType, errorMessage)
{
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.DataAnnotations;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An implementation of <see cref="IClientModelValidator"/> that provides the rule for validating
/// numeric types.
/// </summary>
public class NumericClientModelValidator : IClientModelValidator
{
/// <inheritdoc />
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ClientModelValidationContext context)
{
return new[] { new ModelClientValidationNumericRule(GetErrorMessage(context.ModelMetadata)) };
}
private string GetErrorMessage([NotNull] ModelMetadata modelMetadata)
{
return Resources.FormatNumericClientModelValidator_FieldMustBeNumber(modelMetadata.GetDisplayName());
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
/// <summary>
/// An implementation of <see cref="IClientModelValidatorProvider"/> which provides client validators
/// for specific numeric types.
/// </summary>
public class NumericClientModelValidatorProvider : IClientModelValidatorProvider
{
/// <inheritdoc />
public void GetValidators(ClientValidatorProviderContext context)
{
var type = context.ModelMetadata.ModelType;
var typeToValidate = Nullable.GetUnderlyingType(type) ?? type;
// Check only the numeric types for which we set type='text'.
if (typeToValidate == typeof(float) ||
typeToValidate == typeof(double) ||
typeToValidate == typeof(decimal))
{
context.Validators.Add(new NumericClientModelValidator());
}
}
}
}

View File

@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.DataAnnotations
return GetString("ArgumentCannotBeNullOrEmpty");
}
/// <summary>
/// The field {0} must be a number.
/// </summary>
internal static string NumericClientModelValidator_FieldMustBeNumber
{
get { return GetString("NumericClientModelValidator_FieldMustBeNumber"); }
}
/// <summary>
/// The field {0} must be a number.
/// </summary>
internal static string FormatNumericClientModelValidator_FieldMustBeNumber(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("NumericClientModelValidator_FieldMustBeNumber"), p0);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -123,4 +123,7 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value>
</data>
<data name="NumericClientModelValidator_FieldMustBeNumber" xml:space="preserve">
<value>The field {0} must be a number.</value>
</data>
</root>

View File

@ -25,6 +25,7 @@ namespace Microsoft.AspNet.Mvc
// Set up client validators
options.ClientModelValidatorProviders.Add(new DefaultClientModelValidatorProvider());
options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider());
options.ClientModelValidatorProviders.Add(new NumericClientModelValidatorProvider());
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class DataTypeClientModelValidatorProviderTest
{
private readonly IModelMetadataProvider _metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
[Theory]
[InlineData(typeof(float))]
[InlineData(typeof(double))]
[InlineData(typeof(decimal))]
[InlineData(typeof(float?))]
[InlineData(typeof(double?))]
[InlineData(typeof(decimal?))]
public void GetValidators_GetsNumericValidator_ForNumericType(Type modelType)
{
// Arrange
var provider = new NumericClientModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForType(modelType);
var providerContext = new ClientValidatorProviderContext(metadata);
// Act
provider.GetValidators(providerContext);
// Assert
var validator = Assert.Single(providerContext.Validators);
Assert.IsType<NumericClientModelValidator>(validator);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(short))]
[InlineData(typeof(byte))]
[InlineData(typeof(uint?))]
[InlineData(typeof(long?))]
[InlineData(typeof(string))]
[InlineData(typeof(DateTime))]
public void GetValidators_DoesNotGetsNumericValidator_ForUnsupportedTypes(Type modelType)
{
// Arrange
var provider = new NumericClientModelValidatorProvider();
var metadata = _metadataProvider.GetMetadataForType(modelType);
var providerContext = new ClientValidatorProviderContext(metadata);
// Act
provider.GetValidators(providerContext);
// Assert
Assert.Empty(providerContext.Validators);
}
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Testing;
using Microsoft.Framework.DependencyInjection;
using Xunit;
namespace Microsoft.AspNet.Mvc.ModelBinding.Validation
{
public class NumericClientModelValidatorTest
{
[Fact]
[ReplaceCulture]
public void ClientRulesWithCorrectValidationTypeAndErrorMessage()
{
// Arrange
var provider = TestModelMetadataProvider.CreateDefaultProvider();
var metadata = provider.GetMetadataForProperty(typeof(TypeWithNumericProperty), "Id");
var adapter = new NumericClientModelValidator();
var serviceCollection = new ServiceCollection();
var requestServices = serviceCollection.BuildServiceProvider();
var context = new ClientModelValidationContext(metadata, provider, requestServices);
var expectedMessage = "The field DisplayId must be a number.";
// Act
var rules = adapter.GetClientValidationRules(context);
// Assert
var rule = Assert.Single(rules);
Assert.Equal("number", rule.ValidationType);
Assert.Equal(expectedMessage, rule.ErrorMessage);
}
private class TypeWithNumericProperty
{
[Display(Name = "DisplayId")]
public float Id { get; set; }
}
}
}

View File

@ -114,9 +114,10 @@ namespace Microsoft.AspNet.Mvc
var options = GetOptions<MvcViewOptions>(AddDnxServices);
// Assert
Assert.Equal(2, options.ClientModelValidatorProviders.Count);
Assert.Equal(3, options.ClientModelValidatorProviders.Count);
Assert.IsType<DefaultClientModelValidatorProvider>(options.ClientModelValidatorProviders[0]);
Assert.IsType<DataAnnotationsClientModelValidatorProvider>(options.ClientModelValidatorProviders[1]);
Assert.IsType<NumericClientModelValidatorProvider>(options.ClientModelValidatorProviders[2]);
}
[Fact]