From 4951235eefcac9bfeebfddce6b6e0f8654e64728 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Wed, 8 Apr 2015 16:54:48 -0700 Subject: [PATCH] [Fixes #2337] Added support for file types in input taghelper and htmlhelper --- .../Rendering/Html/DefaultDisplayTemplates.cs | 1 + .../Rendering/Html/DefaultEditorTemplates.cs | 22 ++++- .../Rendering/Html/HtmlHelper.cs | 1 + .../{Html => Internal}/TemplateBuilder.cs | 4 +- .../{Html => Internal}/TemplateRenderer.cs | 80 ++++++++++++------- .../InputTagHelper.cs | 66 ++++----------- .../Rendering/DefaultEditorTemplatesTest.cs | 5 ++ .../Rendering/TemplateRendererTest.cs | 67 ++++++++++++++++ .../MvcTagHelpersTest.cs | 2 + ...elpersWebSite.MvcTagHelper_Home.Input.html | 22 +++++ .../InputTagHelperTest.cs | 80 ++++++++++++------- .../MvcTagHelper_HomeController.cs | 5 ++ .../MvcTagHelpersWebSite/Models/Folder.cs | 23 ++++++ .../Views/MvcTagHelper_Home/Input.cshtml | 26 ++++++ 14 files changed, 297 insertions(+), 107 deletions(-) rename src/Microsoft.AspNet.Mvc.Core/Rendering/{Html => Internal}/TemplateBuilder.cs (97%) rename src/Microsoft.AspNet.Mvc.Core/Rendering/{Html => Internal}/TemplateRenderer.cs (78%) create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/Rendering/TemplateRendererTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Input.html create mode 100644 test/WebSites/MvcTagHelpersWebSite/Models/Folder.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Input.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs index b906b4c7e1..bad2efd4c9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Text; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc.Rendering diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs index 5d5b192fd2..b2eab509e6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultEditorTemplates.cs @@ -5,11 +5,12 @@ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Mvc.Rendering { @@ -374,6 +375,20 @@ namespace Microsoft.AspNet.Mvc.Rendering return GenerateTextBox(htmlHelper, inputType: "number"); } + public static string FileInputTemplate([NotNull] IHtmlHelper htmlHelper) + { + return GenerateTextBox(htmlHelper, inputType: "file"); + } + + public static string FileCollectionInputTemplate([NotNull] IHtmlHelper htmlHelper) + { + var htmlAttributes = + CreateHtmlAttributes(htmlHelper, className: "text-box single-line", inputType: "file"); + htmlAttributes["multiple"] = "multiple"; + + return GenerateTextBox(htmlHelper, htmlHelper.ViewData.TemplateInfo.FormattedModelValue, htmlAttributes); + } + private static void ApplyRfc3339DateFormattingIfNeeded(IHtmlHelper htmlHelper, string format) { if (htmlHelper.Html5DateRenderingMode != Html5DateRenderingMode.Rfc3339) @@ -405,6 +420,11 @@ namespace Microsoft.AspNet.Mvc.Rendering var htmlAttributes = CreateHtmlAttributes(htmlHelper, className: "text-box single-line", inputType: inputType); + return GenerateTextBox(htmlHelper, value, htmlAttributes); + } + + private static string GenerateTextBox(IHtmlHelper htmlHelper, object value, object htmlAttributes) + { return htmlHelper.TextBox( current: null, value: value, diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs index 5510e41f90..6be011b78e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs @@ -11,6 +11,7 @@ using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Rendering.Expressions; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.Framework.Internal; using Microsoft.Framework.WebEncoders; diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateBuilder.cs similarity index 97% rename from src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs rename to src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateBuilder.cs index 4d1c3a55be..efaf250554 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateBuilder.cs @@ -5,9 +5,9 @@ using System.Globalization; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.Framework.Internal; -namespace Microsoft.AspNet.Mvc.Rendering +namespace Microsoft.AspNet.Mvc.Rendering.Internal { - internal class TemplateBuilder + public class TemplateBuilder { private IViewEngine _viewEngine; private ViewContext _viewContext; diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateRenderer.cs similarity index 78% rename from src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs rename to src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateRenderer.cs index 76c273d117..eccd17c451 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Internal/TemplateRenderer.cs @@ -7,16 +7,19 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Internal; -namespace Microsoft.AspNet.Mvc.Rendering +namespace Microsoft.AspNet.Mvc.Rendering.Internal { - internal class TemplateRenderer + public class TemplateRenderer { - private static readonly string DisplayTemplateViewPath = "DisplayTemplates"; - private static readonly string EditorTemplateViewPath = "EditorTemplates"; + private const string DisplayTemplateViewPath = "DisplayTemplates"; + private const string EditorTemplateViewPath = "EditorTemplates"; + public const string IEnumerableOfIFormFileName = "IEnumerable`" + nameof(IFormFile); private static readonly Dictionary> _defaultDisplayActions = new Dictionary>(StringComparer.OrdinalIgnoreCase) @@ -62,6 +65,8 @@ namespace Microsoft.AspNet.Mvc.Rendering { typeof(decimal).Name, DefaultEditorTemplates.DecimalTemplate }, { typeof(string).Name, DefaultEditorTemplates.StringTemplate }, { typeof(object).Name, DefaultEditorTemplates.ObjectTemplate }, + { typeof(IFormFile).Name, DefaultEditorTemplates.FileInputTemplate }, + { IEnumerableOfIFormFileName, DefaultEditorTemplates.FileCollectionInputTemplate }, }; private ViewContext _viewContext; @@ -136,7 +141,7 @@ namespace Microsoft.AspNet.Mvc.Rendering metadata.DataTypeName }; - foreach (string templateHint in templateHints.Where(s => !string.IsNullOrEmpty(s))) + foreach (var templateHint in templateHints.Where(s => !string.IsNullOrEmpty(s))) { yield return templateHint; } @@ -146,14 +151,27 @@ namespace Microsoft.AspNet.Mvc.Rendering var modelType = _viewData.ModelExplorer.ModelType; var fieldType = Nullable.GetUnderlyingType(modelType) ?? modelType; - yield return fieldType.Name; + foreach (var typeName in GetTypeNames(_viewData.ModelExplorer.Metadata, fieldType)) + { + yield return typeName; + } + } + + public static IEnumerable GetTypeNames(ModelMetadata modelMetadata, Type fieldType) + { + // Not returning type name here for IEnumerable since we will be returning + // a more specific name, IEnumerableOfIFormFileName. + if (typeof(IEnumerable) != fieldType) + { + yield return fieldType.Name; + } if (fieldType == typeof(string)) { // Nothing more to provide yield break; } - else if (!metadata.IsComplexType) + else if (!modelMetadata.IsComplexType) { // IsEnum is false for the Enum class itself if (fieldType.IsEnum()) @@ -167,36 +185,44 @@ namespace Microsoft.AspNet.Mvc.Rendering } yield return "String"; + yield break; } - else if (fieldType.IsInterface()) + else if (!fieldType.IsInterface()) { - if (typeof(IEnumerable).IsAssignableFrom(fieldType)) - { - yield return "Collection"; - } - - yield return "Object"; - } - else - { - var isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType); - + var type = fieldType; while (true) { - fieldType = fieldType.BaseType(); - if (fieldType == null) + type = type.BaseType(); + if (type == null || type == typeof(object)) { break; } - if (isEnumerable && fieldType == typeof(Object)) - { - yield return "Collection"; - } - - yield return fieldType.Name; + yield return type.Name; } } + + if (typeof(IEnumerable).IsAssignableFrom(fieldType)) + { + if (typeof(IEnumerable).IsAssignableFrom(fieldType)) + { + yield return IEnumerableOfIFormFileName; + + // Specific name has already been returned, now return the generic name. + if (typeof(IEnumerable) == fieldType) + { + yield return fieldType.Name; + } + } + + yield return "Collection"; + } + else if (typeof(IFormFile) != fieldType && typeof(IFormFile).IsAssignableFrom(fieldType)) + { + yield return nameof(IFormFile); + } + + yield return "Object"; } private static IHtmlHelper MakeHtmlHelper(ViewContext viewContext, ViewDataDictionary viewData) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs index 2bf9e59d65..f9f44b3618 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/InputTagHelper.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; -using System.Collections; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace Microsoft.AspNet.Mvc.TagHelpers @@ -47,6 +48,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { nameof(Boolean), InputType.CheckBox.ToString().ToLowerInvariant() }, { nameof(Decimal), InputType.Text.ToString().ToLowerInvariant() }, { nameof(String), InputType.Text.ToString().ToLowerInvariant() }, + { nameof(IFormFile), "file" }, + { TemplateRenderer.IEnumerableOfIFormFileName, "file" }, }; // Mapping from element's type to RFC 3339 date and time formats. @@ -271,13 +274,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers format = GetFormat(modelExplorer, inputTypeHint, inputType); } + object htmlAttributes = null; + if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName)) + { + htmlAttributes = new Dictionary + { + { "multiple", "multiple" } + }; + } + return Generator.GenerateTextBox( ViewContext, modelExplorer, For.Name, value: modelExplorer.Model, format: format, - htmlAttributes: null); + htmlAttributes: htmlAttributes); } // Get a fall-back format based on the metadata. @@ -353,55 +365,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } } - yield return fieldType.Name; - - if (fieldType == typeof(string)) + foreach (string typeName in TemplateRenderer.GetTypeNames(modelExplorer.Metadata, fieldType)) { - // Nothing more to provide - yield break; - } - else if (!modelExplorer.Metadata.IsComplexType) - { - // IsEnum is false for the Enum class itself - if (fieldType.IsEnum()) - { - // Same as fieldType.BaseType.Name in this case - yield return "Enum"; - } - else if (fieldType == typeof(DateTimeOffset)) - { - yield return "DateTime"; - } - - yield return "String"; - } - else if (fieldType.IsInterface()) - { - if (typeof(IEnumerable).IsAssignableFrom(fieldType)) - { - yield return "Collection"; - } - - yield return "Object"; - } - else - { - var isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType); - while (true) - { - fieldType = fieldType.BaseType(); - if (fieldType == null) - { - break; - } - - if (isEnumerable && fieldType == typeof(Object)) - { - yield return "Collection"; - } - - yield return fieldType.Name; - } + yield return typeName; } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs index d195231ce2..0cd34ab7a0 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/DefaultEditorTemplatesTest.cs @@ -8,9 +8,11 @@ using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding.Validation; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.AspNet.Testing; using Microsoft.Framework.Internal; using Microsoft.Framework.WebEncoders; @@ -78,6 +80,9 @@ namespace Microsoft.AspNet.Mvc.Core { "decimal", "__TextBox__ class='text-box single-line'" }, { "String", "__TextBox__ class='text-box single-line'" }, { "STRING", "__TextBox__ class='text-box single-line'" }, + { typeof(IFormFile).Name, "__TextBox__ class='text-box single-line' type='file'" }, + { TemplateRenderer.IEnumerableOfIFormFileName, + "__TextBox__ class='text-box single-line' type='file' multiple='multiple'" }, }; } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/TemplateRendererTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/TemplateRendererTest.cs new file mode 100644 index 0000000000..22442c83d0 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/TemplateRendererTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; +using System.Linq; +using System.Net; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Http.Core.Collections; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Rendering.Internal +{ + public class TemplateRendererTest + { + public static TheoryData TypeNameData + { + get + { + return new TheoryData + { + { typeof(string), new string[] { "String" } }, + { typeof(bool), new string[] { "Boolean", "String" } }, + { typeof(DateTime), new string[] { "DateTime", "String" } }, + { typeof(float), new string[] { "Single", "String" } }, + { typeof(double), new string[] { "Double", "String" } }, + { typeof(Guid), new string[] { "Guid", "String" } }, + { typeof(TimeSpan), new string[] { "TimeSpan", "String" } }, + { typeof(int), new string[] { "Int32", "String" } }, + { typeof(ulong), new string[] { "UInt64", "String" } }, + + { typeof(Enum), new string[] { "Enum", "String" } }, + { typeof(HttpStatusCode), new string[] { "HttpStatusCode", "Enum", "String" } }, + + { typeof(FormFile), new string[] { "FormFile", "IFormFile", "Object" } }, + { typeof(IFormFile), new string[] { "IFormFile", "Object" } }, + + { typeof(FormFileCollection), new string[] { "FormFileCollection", typeof(List).Name, + TemplateRenderer.IEnumerableOfIFormFileName, "Collection", "Object" } }, + { typeof(IFormFileCollection), new string[] { "IFormFileCollection", + TemplateRenderer.IEnumerableOfIFormFileName, "Collection", "Object" } }, + { typeof(IEnumerable), new string[] { TemplateRenderer.IEnumerableOfIFormFileName, + typeof(IEnumerable).Name, "Collection", "Object" } }, + }; + } + } + + [Theory] + [MemberData(nameof(TypeNameData))] + public void GetTypeNames_ReturnsExpectedResults(Type fieldType, string[] expectedResult) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var metadata = metadataProvider.GetMetadataForType(fieldType); + + // Act + var typeNames = TemplateRenderer.GetTypeNames(metadata, fieldType); + + // Assert + var collectionAssertions = expectedResult.Select>(expected => + actual => Assert.Equal(expected, actual)); + Assert.Collection(typeNames, collectionAssertions.ToArray()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs index 975cc4db83..46fce8b7cb 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTest.cs @@ -47,6 +47,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests [InlineData("Link", null)] // Testing the ScriptTagHelper [InlineData("Script", null)] + // Testing InputTagHelper with File + [InlineData("Input", null)] public async Task MvcTagHelpers_GeneratesExpectedResults(string action, string antiForgeryPath) { // Arrange diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Input.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Input.html new file mode 100644 index 0000000000..0cdec96faa --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Input.html @@ -0,0 +1,22 @@ + + + + File Input + + + +

Input Tag Helper Test

+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs index 087865ee98..ce3e89cd1b 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -5,8 +5,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Mvc.Rendering.Internal; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.Framework.WebEncoders; using Moq; @@ -618,33 +620,48 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(expectedTagName, output.TagName); } + public static TheoryData InputTypeData + { + get + { + return new TheoryData + { + { null, null, "text" }, + { "Byte", null, "number" }, + { null, null, "text" }, + { "Byte", null, "number" }, + { "custom-datatype", null, "text" }, + { "Custom-Datatype", null, "text" }, + { "date", null, "date" }, // No date/time special cases since ModelType is string. + { "datetime", null, "datetime" }, + { "datetime-local", null, "datetime-local" }, + { "DATETIME-local", null, "datetime-local" }, + { "Decimal", "{0:0.00}", "text" }, + { "Double", null, "number" }, + { "Int16", null, "number" }, + { "Int32", null, "number" }, + { "int32", null, "number" }, + { "Int64", null, "number" }, + { "SByte", null, "number" }, + { "Single", null, "number" }, + { "SINGLE", null, "number" }, + { "string", null, "text" }, + { "STRING", null, "text" }, + { "text", null, "text" }, + { "TEXT", null, "text" }, + { "time", null, "time" }, + { "UInt16", null, "number" }, + { "uint16", null, "number" }, + { "UInt32", null, "number" }, + { "UInt64", null, "number" }, + { nameof(IFormFile), null, "file" }, + { TemplateRenderer.IEnumerableOfIFormFileName, null, "file" }, + }; + } + } + [Theory] - [InlineData(null, null, "text")] - [InlineData("Byte", null, "number")] - [InlineData("custom-datatype", null, "text")] - [InlineData("Custom-Datatype", null, "text")] - [InlineData("date", null, "date")] // No date/time special cases since ModelType is string. - [InlineData("datetime", null, "datetime")] - [InlineData("datetime-local", null, "datetime-local")] - [InlineData("DATETIME-local", null, "datetime-local")] - [InlineData("Decimal", "{0:0.00}", "text")] - [InlineData("Double", null, "number")] - [InlineData("Int16", null, "number")] - [InlineData("Int32", null, "number")] - [InlineData("int32", null, "number")] - [InlineData("Int64", null, "number")] - [InlineData("SByte", null, "number")] - [InlineData("Single", null, "number")] - [InlineData("SINGLE", null, "number")] - [InlineData("string", null, "text")] - [InlineData("STRING", null, "text")] - [InlineData("text", null, "text")] - [InlineData("TEXT", null, "text")] - [InlineData("time", null, "time")] - [InlineData("UInt16", null, "number")] - [InlineData("uint16", null, "number")] - [InlineData("UInt32", null, "number")] - [InlineData("UInt64", null, "number")] + [MemberData(nameof(InputTypeData))] public async Task ProcessAsync_CallsGenerateTextBox_AddsExpectedAttributes( string dataTypeName, string expectedFormat, @@ -679,6 +696,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers metadataProvider: metadataProvider); var tagBuilder = new TagBuilder("input", new HtmlEncoder()); + + Dictionary htmlAttributes = null; + if (string.Equals(dataTypeName, TemplateRenderer.IEnumerableOfIFormFileName)) + { + htmlAttributes = new Dictionary + { + { "multiple", "multiple" } + }; + } htmlGenerator .Setup(mock => mock.GenerateTextBox( tagHelper.ViewContext, @@ -686,7 +712,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers tagHelper.For.Name, null, // value expectedFormat, - null)) // htmlAttributes + htmlAttributes)) // htmlAttributes .Returns(tagBuilder) .Verifiable(); diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs index 478b2baf3b..d1ea11bc77 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs @@ -171,5 +171,10 @@ namespace MvcTagHelpersWebSite.Controllers { return View(); } + + public IActionResult Input() + { + return View(); + } } } diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Folder.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Folder.cs new file mode 100644 index 0000000000..2ed2b7e596 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Folder.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Http; +using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Http.Core.Collections; + +namespace MvcTagHelpersWebSite.Models +{ + public class Folder + { + public IFormFile InterfaceFile { get; set; } + + public IFormFileCollection InterfaceFiles { get; set; } + + public FormFile ConcreteFile { get; set; } + + public FormFileCollection ConcreteFiles { get; set; } + + public IEnumerable EnumerableFiles { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Input.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Input.cshtml new file mode 100644 index 0000000000..0a22f9f5e7 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Input.cshtml @@ -0,0 +1,26 @@ +@using MvcTagHelpersWebSite.Models +@model Folder +@addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers" + + + + + File Input + + + +

Input Tag Helper Test

+ + + + + + + + @Html.EditorFor(m => m.InterfaceFile) + @Html.EditorFor(m => m.InterfaceFiles) + @Html.EditorFor(m => m.ConcreteFile) + @Html.EditorFor(m => m.ConcreteFiles) + @Html.EditorFor(m => m.EnumerableFiles) + +