Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit e494f83

Browse files
author
NTaylorMullen
committed
Add AnchorTagHelper.
- Added a TagHelper that targets the <a> tag and allows users to do the equivalent of Html.ActionLink or Html.RouteLink. - Added tests to validate AnchorTagHelper functionality. #1247
1 parent 3e2c256 commit e494f83

File tree

4 files changed

+429
-0
lines changed

4 files changed

+429
-0
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.AspNet.Mvc.Rendering;
8+
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
9+
10+
namespace Microsoft.AspNet.Mvc.TagHelpers
11+
{
12+
/// <summary>
13+
/// <see cref="ITagHelper"/> implementation targeting &lt;a&gt; elements.
14+
/// </summary>
15+
[TagName("a")]
16+
public class AnchorTagHelper : TagHelper
17+
{
18+
private const string RouteAttributePrefix = "route-";
19+
private const string Href = "href";
20+
21+
[Activate]
22+
private IHtmlGenerator Generator { get; set; }
23+
24+
/// <summary>
25+
/// The name of the action method.
26+
/// </summary>
27+
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
28+
public string Action { get; set; }
29+
30+
/// <summary>
31+
/// The name of the controller.
32+
/// </summary>
33+
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
34+
public string Controller { get; set; }
35+
36+
/// <summary>
37+
/// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;.
38+
/// </summary>
39+
public string Protocol { get; set; }
40+
41+
/// <summary>
42+
/// The host name.
43+
/// </summary>
44+
public string Host { get; set; }
45+
46+
/// <summary>
47+
/// The URL fragment name.
48+
/// </summary>
49+
public string Fragment { get; set; }
50+
51+
/// <summary>
52+
/// Name of the route.
53+
/// </summary>
54+
/// <remarks>
55+
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
56+
/// </remarks>
57+
public string Route { get; set; }
58+
59+
/// <inheritdoc />
60+
/// <remarks>Does nothing if user provides an "href" attribute. Throws an
61+
/// <see cref="InvalidOperationException"/> if "href" attribute is provided and <see cref="Action"/>,
62+
/// <see cref="Controller"/>, or <see cref="Route"/> are non-<c>null</c>.</remarks>
63+
public override void Process(TagHelperContext context, TagHelperOutput output)
64+
{
65+
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
66+
67+
// If there's an "href" on the tag it means it's being used as a normal anchor.
68+
if (output.Attributes.ContainsKey(Href))
69+
{
70+
if (Action != null ||
71+
Controller != null ||
72+
Route != null ||
73+
Protocol != null ||
74+
Host != null ||
75+
Fragment != null ||
76+
routePrefixedAttributes.Any())
77+
{
78+
// User specified an href and one of the bound attributes; can't determine the href attribute.
79+
throw new InvalidOperationException(
80+
Resources.FormatAnchorTagHelper_CannotOverrideSpecifiedHref(
81+
"<a>",
82+
nameof(Action).ToLowerInvariant(),
83+
nameof(Controller).ToLowerInvariant(),
84+
nameof(Route).ToLowerInvariant(),
85+
nameof(Protocol).ToLowerInvariant(),
86+
nameof(Host).ToLowerInvariant(),
87+
nameof(Fragment).ToLowerInvariant(),
88+
RouteAttributePrefix,
89+
Href));
90+
}
91+
}
92+
else
93+
{
94+
TagBuilder tagBuilder;
95+
var routeValues = GetRouteValues(output, routePrefixedAttributes);
96+
97+
if (Route == null)
98+
{
99+
tagBuilder = Generator.GenerateActionLink(linkText: string.Empty,
100+
actionName: Action,
101+
controllerName: Controller,
102+
protocol: Protocol,
103+
hostname: Host,
104+
fragment: Fragment,
105+
routeValues: routeValues,
106+
htmlAttributes: null);
107+
}
108+
else if (Action != null || Controller != null)
109+
{
110+
// Route and Action or Controller were specified. Can't determine the href attribute.
111+
throw new InvalidOperationException(
112+
Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(
113+
"<a>",
114+
nameof(Route).ToLowerInvariant(),
115+
nameof(Action).ToLowerInvariant(),
116+
nameof(Controller).ToLowerInvariant(),
117+
Href));
118+
}
119+
else
120+
{
121+
tagBuilder = Generator.GenerateRouteLink(linkText: string.Empty,
122+
routeName: Route,
123+
protocol: Protocol,
124+
hostName: Host,
125+
fragment: Fragment,
126+
routeValues: routeValues,
127+
htmlAttributes: null);
128+
}
129+
130+
if (tagBuilder != null)
131+
{
132+
output.MergeAttributes(tagBuilder);
133+
}
134+
}
135+
}
136+
137+
// TODO: We will not need this method once https://github.com/aspnet/Razor/issues/89 is completed.
138+
private static Dictionary<string, object> GetRouteValues(
139+
TagHelperOutput output, IEnumerable<KeyValuePair<string, string>> routePrefixedAttributes)
140+
{
141+
Dictionary<string, object> routeValues = null;
142+
if (routePrefixedAttributes.Any())
143+
{
144+
// Prefixed values should be treated as bound attributes, remove them from the output.
145+
output.RemoveRange(routePrefixedAttributes);
146+
147+
// Generator.GenerateForm does not accept a Dictionary<string, string> for route values.
148+
routeValues = routePrefixedAttributes.ToDictionary(
149+
attribute => attribute.Key.Substring(RouteAttributePrefix.Length),
150+
attribute => (object)attribute.Value);
151+
}
152+
153+
return routeValues;
154+
}
155+
}
156+
}

src/Microsoft.AspNet.Mvc.TagHelpers/Properties/Resources.Designer.cs

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.Mvc.TagHelpers/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@
117117
<resheader name="writer">
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120+
<data name="AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified" xml:space="preserve">
121+
<value>Cannot determine an {4} for {0}. An {0} with a specified {1} must not have an {2} or {3} attribute.</value>
122+
</data>
123+
<data name="AnchorTagHelper_CannotOverrideSpecifiedHref" xml:space="preserve">
124+
<value>Cannot determine an {8} for {0}. An {0} with a specified {8} must not have attributes starting with {7} or an {1}, {2}, {3}, {4}, {5} or {6} attribute.</value>
125+
</data>
120126
<data name="FormTagHelper_CannotDetermineAction" xml:space="preserve">
121127
<value>Cannot determine an {1} for {0}. A {0} with a URL-based {1} must not have attributes starting with {3} or a {2} attribute.</value>
122128
</data>

0 commit comments

Comments
 (0)