diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 7442a7a239..ccad098973 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -72,6 +72,8 @@ namespace Microsoft.AspNet.Mvc.Razor ExecutionContextAddTagHelperAttributeMethodName = nameof(TagHelperExecutionContext.AddTagHelperAttribute), ExecutionContextAddHtmlAttributeMethodName = nameof(TagHelperExecutionContext.AddHtmlAttribute), + ExecutionContextAddMinimizedHtmlAttributeMethodName = + nameof(TagHelperExecutionContext.AddMinimizedHtmlAttribute), ExecutionContextOutputPropertyName = nameof(TagHelperExecutionContext.Output), RunnerTypeName = typeof(TagHelperRunner).FullName, @@ -85,6 +87,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Can't use nameof because RazorPage is not accessible here. CreateTagHelperMethodName = "CreateTagHelper", + FormatInvalidIndexerAssignmentMethodName = "InvalidTagHelperIndexerAssignment", StartTagHelperWritingScopeMethodName = "StartTagHelperWritingScope", EndTagHelperWritingScopeMethodName = "EndTagHelperWritingScope", @@ -211,7 +214,7 @@ namespace Microsoft.AspNet.Mvc.Razor sourceFileName = _pathNormalizer.NormalizePath(sourceFileName); var inheritedCodeTrees = ChunkInheritanceUtility.GetInheritedCodeTrees(sourceFileName); - return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks); + return new MvcRazorParser(razorParser, inheritedCodeTrees, DefaultInheritedChunks, ModelExpressionType); } /// @@ -224,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.Razor public override CodeBuilder DecorateCodeBuilder([NotNull] CodeBuilder incomingBuilder, [NotNull] CodeBuilderContext context) { - // Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor + // Need the normalized path to resolve inherited chunks only. Full paths are needed for generated Razor // files checksum and line pragmas to enable DesignTime debugging. var normalizedPath = _pathNormalizer.NormalizePath(context.SourceFile); var inheritedChunks = ChunkInheritanceUtility.GetInheritedCodeTrees(normalizedPath); diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs index 0ed1b67dff..60243e108f 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorParser.cs @@ -1,8 +1,10 @@ // 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; using System.Linq; +using Microsoft.AspNet.Mvc.Razor.Host; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; @@ -20,6 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor public class MvcRazorParser : RazorParser { private readonly IEnumerable _globalImportDirectiveDescriptors; + private readonly string _modelExpressionTypeName; /// /// Initializes a new instance of . @@ -32,13 +35,16 @@ namespace Microsoft.AspNet.Mvc.Razor public MvcRazorParser( [NotNull] RazorParser parser, [NotNull] IReadOnlyList inheritedCodeTrees, - [NotNull] IReadOnlyList defaultInheritedChunks) + [NotNull] IReadOnlyList defaultInheritedChunks, + [NotNull] string modelExpressionTypeName) : base(parser) { // Construct tag helper descriptors from @addTagHelper, @removeTagHelper and @tagHelperPrefix chunks _globalImportDirectiveDescriptors = GetTagHelperDirectiveDescriptors( inheritedCodeTrees, defaultInheritedChunks); + + _modelExpressionTypeName = modelExpressionTypeName; } /// @@ -50,7 +56,27 @@ namespace Microsoft.AspNet.Mvc.Razor TagHelperDescriptorResolver, _globalImportDirectiveDescriptors, errorSink); - return visitor.GetDescriptors(documentRoot); + + var descriptors = visitor.GetDescriptors(documentRoot); + foreach (var descriptor in descriptors) + { + foreach (var attributeDescriptor in descriptor.Attributes) + { + if (attributeDescriptor.IsIndexer && + string.Equals( + attributeDescriptor.TypeName, + _modelExpressionTypeName, + StringComparison.Ordinal)) + { + errorSink.OnError(SourceLocation.Undefined, Resources.FormatMvcRazorParser_InvalidPropertyType( + descriptor.TypeName, + attributeDescriptor.Name, + _modelExpressionTypeName)); + } + } + } + + return descriptors; } private static IEnumerable GetTagHelperDirectiveDescriptors( @@ -69,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.Razor var chunksInOrder = defaultInheritedChunks.Concat(chunksFromGlobalImports); foreach (var chunk in chunksInOrder) { - // All TagHelperDirectiveDescriptors created here have undefined source locations because the source + // All TagHelperDirectiveDescriptors created here have undefined source locations because the source // that created them is not in the same file. var addTagHelperChunk = chunk as AddTagHelperChunk; if (addTagHelperChunk != null) @@ -154,8 +180,8 @@ namespace Microsoft.AspNet.Mvc.Razor } } - // We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it - // takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the + // We need to see if the provided descriptors contain a @tagHelperPrefix directive. If so, it + // takes precedence and overrides any provided by the inheritedDescriptors. If not we need to add the // inherited @tagHelperPrefix directive back into the merged list. if (prefixDirectiveDescriptor != null && !descriptors.Any(descriptor => descriptor.DirectiveType == TagHelperDirectiveType.TagHelperPrefix)) diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs index 2fbbab3b6c..272b2a49b8 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Properties/Resources.Designer.cs @@ -106,6 +106,22 @@ namespace Microsoft.AspNet.Mvc.Razor.Host return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0); } + /// + /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + /// + internal static string MvcRazorParser_InvalidPropertyType + { + get { return GetString("MvcRazorParser_InvalidPropertyType"); } + } + + /// + /// Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + /// + internal static string FormatMvcRazorParser_InvalidPropertyType(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorParser_InvalidPropertyType"), p0, p1, p2); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx index 7d3f712692..d3d51166bc 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Resources.resx @@ -135,4 +135,7 @@ Only one '{0}' statement is allowed in a file. + + Invalid tag helper property '{0}.{1}'. Dictionary values must not be of type '{2}'. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs index e57706a956..d2662b654c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs @@ -410,6 +410,22 @@ namespace Microsoft.AspNet.Mvc.Razor return GetString("GeneratedCodeFileName"); } + /// + /// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + /// + internal static string RazorPage_InvalidTagHelperIndexerAssignment + { + get { return GetString("RazorPage_InvalidTagHelperIndexerAssignment"); } + } + + /// + /// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + /// + internal static string FormatRazorPage_InvalidTagHelperIndexerAssignment(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_InvalidTagHelperIndexerAssignment"), p0, p1, p2); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 5eaef99206..41df93e724 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -150,6 +150,24 @@ namespace Microsoft.AspNet.Mvc.Razor } } + /// + /// Format an error message about using an indexer when the tag helper property is null. + /// + /// Name of the HTML attribute associated with the indexer. + /// Full name of the tag helper . + /// Dictionary property in the tag helper. + /// An error message about using an indexer when the tag helper property is null. + public static string InvalidTagHelperIndexerAssignment( + string attributeName, + string tagHelperTypeName, + string propertyName) + { + return Resources.FormatRazorPage_InvalidTagHelperIndexerAssignment( + attributeName, + tagHelperTypeName, + propertyName); + } + /// /// Creates and activates a . /// diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx index c564e69697..7da90b8653 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx @@ -1,17 +1,17 @@  - @@ -192,4 +192,7 @@ Generated Code + + Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs index e52ffd50c9..ba1586170b 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs @@ -21,7 +21,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers private const string HostAttributeName = "asp-host"; private const string ProtocolAttributeName = "asp-protocol"; private const string RouteAttributeName = "asp-route"; - private const string RouteAttributePrefix = "asp-route-"; + private const string RouteValuesDictionaryName = "asp-all-route-data"; + private const string RouteValuesPrefix = "asp-route-"; private const string Href = "href"; [Activate] @@ -69,6 +70,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [HtmlAttributeName(RouteAttributeName)] public string Route { get; set; } + /// + /// Additional parameters for the route. + /// + [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] + public IDictionary RouteValues { get; set; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + /// /// Does nothing if user provides an href attribute. /// @@ -79,8 +87,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// public override void Process(TagHelperContext context, TagHelperOutput output) { - var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); - // If "href" is already set, it means the user is attempting to use a normal anchor. if (output.Attributes.ContainsName(Href)) { @@ -90,7 +96,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Protocol != null || Host != null || Fragment != null || - routePrefixedAttributes.Any()) + RouteValues.Count != 0) { // User specified an href and one of the bound attributes; can't determine the href attribute. throw new InvalidOperationException( @@ -102,15 +108,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers ProtocolAttributeName, HostAttributeName, FragmentAttributeName, - RouteAttributePrefix, + RouteValuesPrefix, Href)); } } else { - TagBuilder tagBuilder; - var routeValues = GetRouteValues(output, routePrefixedAttributes); + // Convert from Dictionary to Dictionary. + var routeValues = RouteValues.ToDictionary( + kvp => kvp.Key, + kvp => (object)kvp.Value, + StringComparer.OrdinalIgnoreCase); + TagBuilder tagBuilder; if (Route == null) { tagBuilder = Generator.GenerateActionLink(linkText: string.Empty, @@ -150,26 +160,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } } } - - // TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed. - private static Dictionary GetRouteValues( - TagHelperOutput output, - IEnumerable routePrefixedAttributes) - { - Dictionary routeValues = null; - if (routePrefixedAttributes.Any()) - { - // Prefixed values should be treated as bound attributes, remove them from the output. - output.RemoveRange(routePrefixedAttributes); - - // Remove prefix from keys and convert all values to strings. HtmlString and similar classes are not - // meaningful to routing. - routeValues = routePrefixedAttributes.ToDictionary( - attribute => attribute.Name.Substring(RouteAttributePrefix.Length), - attribute => (object)attribute.Value.ToString()); - } - - return routeValues; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs index af289b6d1a..b2b5ff2cba 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs @@ -18,7 +18,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers private const string AntiForgeryAttributeName = "asp-anti-forgery"; private const string ControllerAttributeName = "asp-controller"; private const string RouteAttributeName = "asp-route"; - private const string RouteAttributePrefix = "asp-route-"; + private const string RouteValuesDictionaryName = "asp-all-route-data"; + private const string RouteValuesPrefix = "asp-route-"; private const string HtmlActionAttributeName = "action"; [Activate] @@ -57,6 +58,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [HtmlAttributeName(RouteAttributeName)] public string Route { get; set; } + /// + /// Additional parameters for the route. + /// + [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] + public IDictionary RouteValues { get; set; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + /// /// /// Does nothing if user provides an action attribute and is null or @@ -69,12 +77,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public override void Process(TagHelperContext context, TagHelperOutput output) { var antiForgeryDefault = true; - var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); // If "action" is already set, it means the user is attempting to use a normal
. if (output.Attributes.ContainsName(HtmlActionAttributeName)) { - if (Action != null || Controller != null || Route != null || routePrefixedAttributes.Any()) + if (Action != null || Controller != null || Route != null || RouteValues.Count != 0) { // User also specified bound attributes we cannot use. throw new InvalidOperationException( @@ -84,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers ActionAttributeName, ControllerAttributeName, RouteAttributeName, - RouteAttributePrefix)); + RouteValuesPrefix)); } // User is using the FormTagHelper like a normal tag. Anti-forgery default should be false to @@ -93,8 +100,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } else { + // Convert from Dictionary to Dictionary. + var routeValues = RouteValues.ToDictionary( + kvp => kvp.Key, + kvp => (object)kvp.Value, + StringComparer.OrdinalIgnoreCase); + TagBuilder tagBuilder; - var routeValues = GetRouteValues(output, routePrefixedAttributes); if (Route == null) { tagBuilder = Generator.GenerateForm( @@ -142,26 +154,5 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } } } - - // TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed. - private static Dictionary GetRouteValues( - TagHelperOutput output, - IEnumerable routePrefixedAttributes) - { - Dictionary routeValues = null; - if (routePrefixedAttributes.Any()) - { - // Prefixed values should be treated as bound attributes, remove them from the output. - output.RemoveRange(routePrefixedAttributes); - - // Remove prefix from keys and convert all values to strings. HtmlString and similar classes are not - // meaningful to routing. - routeValues = routePrefixedAttributes.ToDictionary( - attribute => attribute.Name.Substring(RouteAttributePrefix.Length), - attribute => (object)attribute.Value.ToString()); - } - - return routeValues; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs index 53edc982a5..be7410a15c 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/TagHelperOutputExtensions.cs @@ -51,28 +51,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } } - /// - /// Returns all attributes from 's - /// that have the given . - /// - /// The this method extends. - /// A prefix to look for. - /// s with - /// starting with the given . - public static IEnumerable FindPrefixedAttributes( - [NotNull] this TagHelperOutput tagHelperOutput, - [NotNull] string prefix) - { - // TODO: https://github.com/aspnet/Razor/issues/89 - We will not need this method once #89 is completed. - - // We're only interested in HTML attributes that have the desired prefix. - var prefixedAttributes = tagHelperOutput.Attributes - .Where(attribute => attribute.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - .ToArray(); - - return prefixedAttributes; - } - /// /// Merges the given 's into the /// . diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs index 2910113789..365407bc18 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/MutableObjectModelBinderTest.cs @@ -1071,9 +1071,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var testableBinder = new TestableMutableObjectModelBinder(); - // The [DefaultValue] on ValueTypeRequiredWithDefaultValue is ignored by model binding. - var expectedValue = 0; - // Make ValueTypeRequired invalid. var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == nameof(Person.ValueTypeRequired)); dto.Results[propertyMetadata] = new ModelBindingResult( @@ -1294,7 +1291,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Assert.Equal(dob, model.DateOfBirth); Assert.True(bindingContext.ModelState.IsValid); - // Ensure that we add child nodes for all the nodes which have a result (irrespective of if they + // Ensure that we add child nodes for all the nodes which have a result (irrespective of if they // are bound or not). Assert.Equal(2, modelValidationNode.ChildNodes.Count()); @@ -1631,7 +1628,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding bindingContext.ModelState["foo"].Errors[0].Exception.Message); } - // This can only really be done by writing an invalid model binder and returning 'isModelSet: true' + // This can only really be done by writing an invalid model binder and returning 'isModelSet: true' // with a null model for a value type. [Fact] public void SetProperty_SettingNonNullableValueTypeToNull_CapturesException() diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorParserTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorParserTest.cs index f0e14b5840..447dc387dc 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorParserTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorParserTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; @@ -230,7 +231,7 @@ namespace Microsoft.AspNet.Mvc.Razor public TestableMvcRazorParser(RazorParser parser, IReadOnlyList codeTrees, IReadOnlyList defaultInheritedChunks) - : base(parser, codeTrees, defaultInheritedChunks) + : base(parser, codeTrees, defaultInheritedChunks, typeof(ModelExpression).FullName) { } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcTagHelperAttributeValueCodeRendererTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcTagHelperAttributeValueCodeRendererTest.cs index 216dd3ce17..5b0728404d 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcTagHelperAttributeValueCodeRendererTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcTagHelperAttributeValueCodeRendererTest.cs @@ -25,7 +25,11 @@ namespace Microsoft.AspNet.Mvc.Razor ModelExpressionTypeName = modelExpressionType, CreateModelExpressionMethodName = "SomeMethod" }); - var attributeDescriptor = new TagHelperAttributeDescriptor("MyAttribute", "SomeProperty", propertyType); + var attributeDescriptor = new TagHelperAttributeDescriptor( + name: "MyAttribute", + propertyName: "SomeProperty", + typeName: propertyType, + isIndexer: false); var writer = new CSharpCodeWriter(); var generatorContext = new CodeGeneratorContext(host: null, className: string.Empty, diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs index 4f519588db..9275d8e6af 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using Microsoft.Framework.WebEncoders; using Microsoft.Framework.WebEncoders.Testing; using Moq; using Xunit; @@ -28,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers allAttributes: new TagHelperAttributeList { { "id", "myanchor" }, - { "asp-route-foo", "bar" }, + { "asp-route-name", "value" }, { "asp-action", "index" }, { "asp-controller", "home" }, { "asp-fragment", "hello=world" }, @@ -48,7 +47,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new TagHelperAttributeList { { "id", "myanchor" }, - { "asp-route-foo", "bar" }, }); output.Content.SetContent("Something"); @@ -68,6 +66,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Generator = htmlGenerator, Host = "contoso.com", Protocol = "http", + RouteValues = + { + { "name", "value" }, + }, }; // Act @@ -106,7 +108,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var generator = new Mock(MockBehavior.Strict); generator .Setup(mock => mock.GenerateRouteLink( - string.Empty, "Default", "http", "contoso.com", "hello=world", null, null)) + string.Empty, + "Default", + "http", + "contoso.com", + "hello=world", + It.IsAny>(), + null)) .Returns(new TagBuilder("a", new CommonTestEncoder())) .Verifiable(); var anchorTagHelper = new AnchorTagHelper @@ -149,7 +157,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var generator = new Mock(); generator .Setup(mock => mock.GenerateActionLink( - string.Empty, "Index", "Home", "http", "contoso.com", "hello=world", null, null)) + string.Empty, + "Index", + "Home", + "http", + "contoso.com", + "hello=world", + It.IsAny>(), + null)) .Returns(new TagBuilder("a", new CommonTestEncoder())) .Verifiable(); var anchorTagHelper = new AnchorTagHelper @@ -190,7 +205,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }); if (propertyName == "asp-route-") { - output.Attributes.Add("asp-route-foo", "bar"); + anchorTagHelper.RouteValues.Add("name", "value"); } else { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs index bc909c4509..df4b616179 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers allAttributes: new TagHelperAttributeList { { "id", "myform" }, - { "asp-route-foo", "bar" }, + { "asp-route-name", "value" }, { "asp-action", "index" }, { "asp-controller", "home" }, { "method", "post" }, @@ -48,7 +48,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new TagHelperAttributeList { { "id", "myform" }, - { "asp-route-foo", "bar" }, }); output.PostContent.SetContent("Something"); var urlHelper = new Mock(); @@ -68,6 +67,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Controller = "home", Generator = htmlGenerator, ViewContext = viewContext, + RouteValues = + { + { "name", "value" }, + }, }; // Act @@ -145,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } [Fact] - public async Task ProcessAsync_BindsRouteValuesFromTagHelperOutput() + public async Task ProcessAsync_BindsRouteValues() { // Arrange var testViewContext = CreateViewContext(); @@ -163,11 +166,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedAttribute = new TagHelperAttribute("asp-ROUTEE-NotRoute", "something"); var output = new TagHelperOutput( "form", - attributes: new TagHelperAttributeList() - { - { "asp-route-val", "hello" }, - { "asp-roUte--Foo", "bar" } - }); + attributes: new TagHelperAttributeList()); output.Attributes.Add(expectedAttribute); var generator = new Mock(MockBehavior.Strict); @@ -190,8 +189,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(2, routeValueDictionary.Count); var routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("val")); Assert.Equal("hello", routeValue.Value); - routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Foo")); - Assert.Equal("bar", routeValue.Value); + routeValue = Assert.Single(routeValueDictionary, attr => attr.Key.Equals("-Name")); + Assert.Equal("Value", routeValue.Value); }) .Returns(new TagBuilder("form", new CommonTestEncoder())) .Verifiable(); @@ -201,6 +200,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers AntiForgery = false, Generator = generator.Object, ViewContext = testViewContext, + RouteValues = + { + { "val", "hello" }, + { "-Name", "Value" }, + }, }; // Act & Assert @@ -237,7 +241,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new TagHelperAttributeList()); var generator = new Mock(MockBehavior.Strict); generator - .Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, null, null)) + .Setup(mock => mock.GenerateForm( + viewContext, + "Index", + "Home", + It.IsAny>(), + null, + null)) .Returns(new TagBuilder("form", new CommonTestEncoder())) .Verifiable(); var formTagHelper = new FormTagHelper @@ -280,16 +290,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }); var output = new TagHelperOutput( "form", - attributes: new TagHelperAttributeList - { - { "asp-route-foo", "bar" } - }); + attributes: new TagHelperAttributeList()); var generator = new Mock(MockBehavior.Strict); generator .Setup(mock => mock.GenerateRouteForm( viewContext, "Default", - It.Is>(m => string.Equals(m["foo"], "bar")), + It.Is>(m => string.Equals(m["name"], "value")), null, null)) .Returns(new TagBuilder("form", new CommonTestEncoder())) @@ -300,6 +307,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Route = "Default", Generator = generator.Object, ViewContext = viewContext, + RouteValues = + { + { "name", "value" }, + }, }; // Act & Assert @@ -384,7 +395,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }); if (propertyName == "asp-route-") { - tagHelperOutput.Attributes.Add("asp-route-foo", "bar"); + formTagHelper.RouteValues.Add("name", "value"); } else { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs index 4ca8d60917..08c27dac7e 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs @@ -222,7 +222,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }); var expectedAttribute = new TagHelperAttribute("type", "btn"); tagHelperOutput.Attributes.Add(expectedAttribute); - var attributes = tagHelperOutput.FindPrefixedAttributes("route-"); + + var attributes = tagHelperOutput.Attributes + .Where(item => item.Name.StartsWith("route-", StringComparison.OrdinalIgnoreCase)) + .ToList(); // Act tagHelperOutput.RemoveRange(attributes); @@ -264,19 +267,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers "p", attributes: new TagHelperAttributeList() { - { "routeHello", "World" }, - { "Routee-I", "Am" } + { "route-Hello", "World" }, + { "Route-I", "Am" } }); + var expectedAttribute = new TagHelperAttribute("type", "btn"); + tagHelperOutput.Attributes.Add(expectedAttribute); + + var attributes = tagHelperOutput.Attributes + .Where(item => item.Name.StartsWith("route-", StringComparison.OrdinalIgnoreCase)); // Act - var attributes = tagHelperOutput.FindPrefixedAttributes("route-"); + tagHelperOutput.RemoveRange(attributes); // Assert - Assert.Empty(attributes); - var attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("routeHello")); - Assert.Equal(attribute.Value, "World"); - attribute = Assert.Single(tagHelperOutput.Attributes, attr => attr.Name.Equals("Routee-I")); - Assert.Equal(attribute.Value, "Am"); + var attribute = Assert.Single(tagHelperOutput.Attributes); + Assert.Equal(expectedAttribute, attribute); } public static TheoryData MultipleAttributeSameNameData