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