diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs index dc3e32b6fd..37aa768000 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs @@ -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 GetClientValidationRules(ClientModelValidationContext context); + IEnumerable GetClientValidationRules([NotNull] ClientModelValidationContext context); } } diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs index ea697e099a..20d92eeaed 100644 --- a/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs @@ -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 { /// @@ -13,6 +15,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Validation /// by updating . /// /// The associated with this call. - void GetValidators(ClientValidatorProviderContext context); + void GetValidators([NotNull] ClientValidatorProviderContext context); } } diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/ModelClientValidationNumericRule.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/ModelClientValidationNumericRule.cs new file mode 100644 index 0000000000..02e4819b7b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/ModelClientValidationNumericRule.cs @@ -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 +{ + /// + /// This is a for numeric values. + /// + public class ModelClientValidationNumericRule : ModelClientValidationRule + { + private const string NumericValidationType = "number"; + + /// + /// Creates an instance of + /// with the given . + /// + /// The error message to be displayed. + public ModelClientValidationNumericRule([NotNull] string errorMessage) + : base(NumericValidationType, errorMessage) + { + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidator.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidator.cs new file mode 100644 index 0000000000..e664ad815a --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidator.cs @@ -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 +{ + /// + /// An implementation of that provides the rule for validating + /// numeric types. + /// + public class NumericClientModelValidator : IClientModelValidator + { + /// + public IEnumerable GetClientValidationRules(ClientModelValidationContext context) + { + return new[] { new ModelClientValidationNumericRule(GetErrorMessage(context.ModelMetadata)) }; + } + + private string GetErrorMessage([NotNull] ModelMetadata modelMetadata) + { + return Resources.FormatNumericClientModelValidator_FieldMustBeNumber(modelMetadata.GetDisplayName()); + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidatorProvider.cs new file mode 100644 index 0000000000..5863911357 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/NumericClientModelValidatorProvider.cs @@ -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 +{ + /// + /// An implementation of which provides client validators + /// for specific numeric types. + /// + public class NumericClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + 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()); + } + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/Properties/Resources.Designer.cs index 45cfd42e94..2775a55e63 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/Properties/Resources.Designer.cs @@ -42,6 +42,22 @@ namespace Microsoft.AspNet.Mvc.DataAnnotations return GetString("ArgumentCannotBeNullOrEmpty"); } + /// + /// The field {0} must be a number. + /// + internal static string NumericClientModelValidator_FieldMustBeNumber + { + get { return GetString("NumericClientModelValidator_FieldMustBeNumber"); } + } + + /// + /// The field {0} must be a number. + /// + 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); diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/Resources.resx b/src/Microsoft.AspNet.Mvc.DataAnnotations/Resources.resx index ec59448ff3..883057e2a3 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/Resources.resx @@ -123,4 +123,7 @@ Value cannot be null or empty. + + The field {0} must be a number. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/MvcViewOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/MvcViewOptionsSetup.cs index 42108e2943..03ba416e4b 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/MvcViewOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/MvcViewOptionsSetup.cs @@ -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()); } } } diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataTypeClientModelValidatorProviderTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataTypeClientModelValidatorProviderTest.cs new file mode 100644 index 0000000000..ed84fae83c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataTypeClientModelValidatorProviderTest.cs @@ -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(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); + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/NumericClientModelValidatorTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/NumericClientModelValidatorTest.cs new file mode 100644 index 0000000000..a62b0502d6 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/NumericClientModelValidatorTest.cs @@ -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; } + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs index 7f8e2b6aaa..ce765b564a 100644 --- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionsSetupTest.cs @@ -114,9 +114,10 @@ namespace Microsoft.AspNet.Mvc var options = GetOptions(AddDnxServices); // Assert - Assert.Equal(2, options.ClientModelValidatorProviders.Count); + Assert.Equal(3, options.ClientModelValidatorProviders.Count); Assert.IsType(options.ClientModelValidatorProviders[0]); Assert.IsType(options.ClientModelValidatorProviders[1]); + Assert.IsType(options.ClientModelValidatorProviders[2]); } [Fact]