From 72eb546329669e80dbe539d2758a7d453a714cc2 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 15 Sep 2017 17:37:01 -0700 Subject: [PATCH] Added attribute adapter for DataAnnotation's FileExtensionsAttribute --- .../FileExtensionsAttributeAdapter.cs | 45 +++++++ .../ValidationAttributeAdapterProvider.cs | 4 + .../FileExtensionsAttributeAdapterTest.cs | 121 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs new file mode 100644 index 0000000000..be8fe59a2e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs @@ -0,0 +1,45 @@ +// 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class FileExtensionsAttributeAdapter : AttributeAdapterBase + { + public FileExtensionsAttributeAdapter(FileExtensionsAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + /// + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-fileextensions", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-fileextensions-extensions", Attribute.Extensions.ToLowerInvariant()); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Extensions); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs index 8e7b33922c..a16b405a5f 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs @@ -74,6 +74,10 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations { adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-url", stringLocalizer); } + else if (type == typeof(FileExtensionsAttribute)) + { + adapter = new FileExtensionsAttributeAdapter((FileExtensionsAttribute)attribute, stringLocalizer); + } else { adapter = null; diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs new file mode 100644 index 0000000000..db1b8dd556 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs @@ -0,0 +1,121 @@ +// 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.Linq; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Localization; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + public class FileExtensionsAttributeAdapterTest + { + [Theory] + [InlineData("jpg,png")] + [InlineData("jpg, png")] + [InlineData("JPEG, Png")] + [ReplaceCulture] + public void AddValidation_WithoutLocalization(string extensions) + { + // Arrange + var provider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadata = provider.GetMetadataForProperty(typeof(Profile), "PhotoFileName"); + + var attribute = new FileExtensionsAttribute() { Extensions = extensions }; + attribute.ErrorMessage = "{0} expects only the following extensions: {1}"; + + var expectedExtensions = string.Join(", ", extensions.Split(',').Select(s => $".{s.Trim().ToLowerInvariant()}")); + var expectedMessage = $"PhotoFileName expects only the following extensions: {expectedExtensions}"; + + var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: null); + + var actionContext = new ActionContext(); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + + // Act + adapter.AddValidation(context); + + // Assert + Assert.Collection( + context.Attributes, + kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions-extensions", kvp.Key); Assert.Equal(extensions.ToLowerInvariant(), kvp.Value); }); + } + + [Fact] + [ReplaceCulture] + public void AddValidation_WithLocalization() + { + // Arrange + var provider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadata = provider.GetMetadataForProperty(typeof(Profile), "PhotoFileName"); + + var attribute = new FileExtensionsAttribute() { Extensions = "jpg" }; + attribute.ErrorMessage = "{0} expects only the following extensions: {1}"; + + var expectedProperties = new object[] { "PhotoFileName", "jpg" }; + var expectedMessage = "PhotoFileName expects only the following extensions: jpg"; + + var stringLocalizer = new Mock(); + stringLocalizer + .Setup(s => s[attribute.ErrorMessage, expectedProperties]) + .Returns(new LocalizedString(attribute.ErrorMessage, expectedMessage)); + + var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); + + var actionContext = new ActionContext(); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + + // Act + adapter.AddValidation(context); + + // Assert + Assert.Collection( + context.Attributes, + kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions-extensions", kvp.Key); Assert.Equal("jpg", kvp.Value); }); + } + + [Fact] + [ReplaceCulture] + public void AddValidation_DoesNotTrounceExistingAttributes() + { + // Arrange + var provider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadata = provider.GetMetadataForProperty(typeof(Profile), "PhotoFileName"); + + var attribute = new FileExtensionsAttribute() { Extensions = "jpg" }; + attribute.ErrorMessage = "{0} expects only the following extensions: {1}"; + + var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: null); + + var actionContext = new ActionContext(); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + + context.Attributes.Add("data-val", "original"); + context.Attributes.Add("data-val-fileextensions", "original"); + context.Attributes.Add("data-val-fileextensions-extensions", "original"); + + // Act + adapter.AddValidation(context); + + // Assert + Assert.Collection( + context.Attributes, + kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("original", kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions", kvp.Key); Assert.Equal("original", kvp.Value); }, + kvp => { Assert.Equal("data-val-fileextensions-extensions", kvp.Key); Assert.Equal("original", kvp.Value); }); + } + + private class Profile + { + public string PhotoFileName { get; set; } + } + } +}