-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Add AnchorTagHelper. #1334
Add AnchorTagHelper. #1334
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| // 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 Microsoft.AspNet.Mvc.Rendering; | ||
| using Microsoft.AspNet.Razor.Runtime.TagHelpers; | ||
|
|
||
| namespace Microsoft.AspNet.Mvc.TagHelpers | ||
| { | ||
| /// <summary> | ||
| /// <see cref="ITagHelper"/> implementation targeting <a> elements. | ||
| /// </summary> | ||
| [TagName("a")] | ||
| public class AnchorTagHelper : TagHelper | ||
| { | ||
| private const string RouteAttributePrefix = "route-"; | ||
| private const string Href = "href"; | ||
|
|
||
| [Activate] | ||
| private IHtmlGenerator Generator { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The name of the action method. | ||
| /// </summary> | ||
| /// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks> | ||
| public string Action { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The name of the controller. | ||
| /// </summary> | ||
| /// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks> | ||
| public string Controller { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The protocol for the URL, such as "http" or "https". | ||
| /// </summary> | ||
| public string Protocol { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The host name. | ||
| /// </summary> | ||
| public string Host { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The URL fragment name. | ||
| /// </summary> | ||
| public string Fragment { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Name of the route. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>. | ||
| /// </remarks> | ||
| public string Route { get; set; } | ||
|
|
||
| /// <inheritdoc /> | ||
| /// <remarks>Does nothing if user provides an "href" attribute. Throws an | ||
| /// <see cref="InvalidOperationException"/> if "href" attribute is provided and <see cref="Action"/>, | ||
| /// <see cref="Controller"/>, or <see cref="Route"/> are non-<c>null</c>.</remarks> | ||
| public override void Process(TagHelperContext context, TagHelperOutput output) | ||
| { | ||
| var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); | ||
|
|
||
| // If there's an "href" on the tag it means it's being used as a normal anchor. | ||
| if (output.Attributes.ContainsKey(Href)) | ||
| { | ||
| if (Action != null || | ||
| Controller != null || | ||
| Route != null || | ||
| Protocol != null || | ||
| Host != null || | ||
| Fragment != null || | ||
| routePrefixedAttributes.Any()) | ||
| { | ||
| // User specified an href and one of the bound attributes; can't determine the href attribute. | ||
| throw new InvalidOperationException( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we shouldn't throw here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 this is another case where, like "controller" and "route-value" in the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Talked in person. |
||
| Resources.FormatAnchorTagHelper_CannotOverrideSpecifiedHref( | ||
| "<a>", | ||
| nameof(Action).ToLowerInvariant(), | ||
| nameof(Controller).ToLowerInvariant(), | ||
| nameof(Route).ToLowerInvariant(), | ||
| nameof(Protocol).ToLowerInvariant(), | ||
| nameof(Host).ToLowerInvariant(), | ||
| nameof(Fragment).ToLowerInvariant(), | ||
| RouteAttributePrefix, | ||
| Href)); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| TagBuilder tagBuilder; | ||
| var routeValues = GetRouteValues(output, routePrefixedAttributes); | ||
|
|
||
| if (Route == null) | ||
| { | ||
| tagBuilder = Generator.GenerateActionLink(linkText: string.Empty, | ||
| actionName: Action, | ||
| controllerName: Controller, | ||
| protocol: Protocol, | ||
| hostname: Host, | ||
| fragment: Fragment, | ||
| routeValues: routeValues, | ||
| htmlAttributes: null); | ||
| } | ||
| else if (Action != null || Controller != null) | ||
| { | ||
| // Route and Action or Controller were specified. Can't determine the href attribute. | ||
| throw new InvalidOperationException( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree this is an odd mix but throwing seems harsh. if the user cares, they should see their attribute values are ignored. or, restore the values and they'll see attributes intended for the tag helper just carry through into the final HTML.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Talked in person. |
||
| Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified( | ||
| "<a>", | ||
| nameof(Route).ToLowerInvariant(), | ||
| nameof(Action).ToLowerInvariant(), | ||
| nameof(Controller).ToLowerInvariant(), | ||
| Href)); | ||
| } | ||
| else | ||
| { | ||
| tagBuilder = Generator.GenerateRouteLink(linkText: string.Empty, | ||
| routeName: Route, | ||
| protocol: Protocol, | ||
| hostName: Host, | ||
| fragment: Fragment, | ||
| routeValues: routeValues, | ||
| htmlAttributes: null); | ||
| } | ||
|
|
||
| if (tagBuilder != null) | ||
| { | ||
| output.MergeAttributes(tagBuilder); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // TODO: We will not need this method once https://github.com/aspnet/Razor/issues/89 is completed. | ||
| private static Dictionary<string, object> GetRouteValues( | ||
| TagHelperOutput output, IEnumerable<KeyValuePair<string, string>> routePrefixedAttributes) | ||
| { | ||
| Dictionary<string, object> routeValues = null; | ||
| if (routePrefixedAttributes.Any()) | ||
| { | ||
| // Prefixed values should be treated as bound attributes, remove them from the output. | ||
| output.RemoveRange(routePrefixedAttributes); | ||
|
|
||
| // Generator.GenerateForm does not accept a Dictionary<string, string> for route values. | ||
| routeValues = routePrefixedAttributes.ToDictionary( | ||
| attribute => attribute.Key.Substring(RouteAttributePrefix.Length), | ||
| attribute => (object)attribute.Value); | ||
| } | ||
|
|
||
| return routeValues; | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed general rule is that MVC tag helpers should simply not overwrite what user (or an earlier tag helper in line) specified. so, everywhere else, the
IHtmlGeneratorreturns aTagBuilderwith attributes that might not be copied to theTagHelperOutputbecause they are already there -- and no error results. there's nothing special here and it should follow the general rule. that is, get rid of this entireifblockThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on second thought, given
Process()should do something when all the bound attributes arenull, maybe there is something special here.nevermind