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

Commit 32afad0

Browse files
committed
Fix #1618, Add asp- prefix to custom attributes of MVC tag helpers
- update XML docs to reflect new HTML / custom attribute separation - update `Exception` messages to use new attribute names - update MVC tag helper sample to use new custom attribute names - add missing `<input/>` tag helper `throw`s test nits: - reword a few comments and messages for clarity and consistency - use `<exception/>` sections to describe what's thrown - add "Reviewers" comments about current throws - note `<a/>` and `<form/>` are slightly inconsistent with others: `throw` if unable to override specified `href` or `action` attributes; rest `throw` only if custom attributes are inconsistent e.g. `<input/>` with `asp-format` but no `asp-for` - create test tag helpers after all property values are available
1 parent 39bb1c9 commit 32afad0

18 files changed

+340
-267
lines changed

samples/TagHelperSample.Web/Views/Home/Create.cshtml

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,47 @@
99
<h2>Create</h2>
1010

1111
@* anti-forgery is on by default *@
12-
@* form will special-case anything that looks like a URI i.e. contains a '/' or doesn't match an action *@
13-
<form anti-forgery="false" action="Create">
12+
@* <form/> tag helper will special-case only elements with an "asp-action" or "asp-anti-forgery" attribute. *@
13+
<form asp-anti-forgery="false" asp-action="Create">
1414
<div class="form-horizontal">
1515
@* validation summary tag helper will target just <div/> elements and append the list of errors *@
16-
@* - i.e. this helper, like <select/> helper has ContentBehavior.Append *@
17-
@* helper does nothing if model is valid and (client-side validation is disabled or validation-summary="ModelOnly") *@
16+
@* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
17+
@* helper does nothing if model is valid and (client-side validation is disabled or asp-validation-summary="ModelOnly") *@
1818
@* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@
1919
@* initially at least, will not remove the <div/> if list isn't generated *@
2020
@* - should helper remove the <div/> if list isn't generated? *@
2121
@* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@
2222
@* Acceptable values are: "None", "ModelOnly" and "All" *@
23-
<div validation-summary="All" style="color:blue" id="validation_day" class="form-group">
23+
<div asp-validation-summary="All" style="color:blue" id="validation_day" class="form-group">
2424
<span style="color:red">This is my message</span>
2525
</div>
2626

2727
@* element will have correct name and id attributes for Id property. unusual part is the constant value. *@
2828
@* - the helper will _not_ override the user-specified "value" attribute *@
29-
<input type="hidden" for="Id" value="0" />
29+
<input type="hidden" asp-for="Id" value="0" />
3030

3131
<div class="form-group">
32-
@* no special-case for the "for" attribute; may eventually need to opt out on per-element basis here and in <form/> *@
33-
<label for="Name" class="control-label col-md-2" style="color:blue" />
32+
@* no special-case for the "asp-for" attribute; now distinct from HTML "for" attribute. *@
33+
<label asp-for="Name" class="control-label col-md-2" style="color:blue" />
3434
<div class="col-md-10">
35-
<input type="text" for="Name" style="color:blue" />
36-
<span validation-for="Name" style="color:blue" />
35+
<input type="text" asp-for="Name" style="color:blue" />
36+
<span asp-validation-for="Name" style="color:blue" />
3737
</div>
3838
</div>
3939
<div class="form-group">
40-
<label for="DateOfBirth" class="control-label col-md-2" />
40+
<label asp-for="DateOfBirth" class="control-label col-md-2" />
4141
<div class="col-md-10">
42-
@* will automatically infer type="date" (reused HTML attribute) and format="{0:yyyy-MM-dd}" (optional bound attribute) *@
43-
<input for="DateOfBirth" />
44-
<span validation-for="DateOfBirth">When were you born?</span>
42+
@* will automatically infer type="date" (reused HTML attribute) and asp-format="{0:yyyy-MM-dd}" (optional bound attribute) *@
43+
<input asp-for="DateOfBirth" />
44+
<span asp-validation-for="DateOfBirth">When were you born?</span>
4545
</div>
4646
</div>
4747
<div class="form-group">
48-
<label for="YearsEmployeed" class="control-label col-md-2" />
48+
<label asp-for="YearsEmployeed" class="control-label col-md-2" />
4949
<div class="col-md-10">
5050
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@
51-
<select for="YearsEmployeed" items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
52-
@* schedule-wise option tag helper (which adds "selected" attribute to static <option/>s) comes after helpers *@
53-
@* - static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
51+
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
52+
@* Static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
5453
@* - @NTaylorMullen thinks <option/> tag helper could tell <select/> helper not to select anything from "items" *@
5554
@* - wouldn't help if user selected one static <option/> and expression indicated another, especially one earlier in the <select/> *@
5655
@* - may need a "default" bound parameter on the <select/> to avoid these cases and maintain "don't override" *@
@@ -68,26 +67,26 @@
6867

6968
@* targets only <span/> in Beta; does not support equivalent of Html.ValidationMessageFor()'s tag parameter *@
7069
@* - may eventually either support additional tags e.g. <p/> and <div/> or all tags /> *@
71-
<span validation-for="YearsEmployeed" />
70+
<span asp-validation-for="YearsEmployeed" />
7271
</div>
7372
</div>
7473
<div class="form-group">
75-
<label for="Blurb" class="control-label col-md-2" />
74+
<label asp-for="Blurb" class="control-label col-md-2" />
7675
<div class="col-md-10">
77-
<textarea rows="4" for="Blurb"></textarea>
78-
<span validation-for="Blurb" />
76+
<textarea rows="4" asp-for="Blurb"></textarea>
77+
<span asp-validation-for="Blurb" />
7978
</div>
8079
</div>
8180

8281
<div class="form-group">
8382
<div class="col-md-offset-2 col-md-10">
84-
@* this <input/> lacks a "for" attribute and will not be changed by the <input/> tag helper *@
83+
@* this <input/> lacks a "asp-for" attribute and will not be changed by the <input/> tag helper *@
8584
<input type="submit" value="Create" class="btn btn-default" />
8685
</div>
8786
</div>
8887
</div>
8988
</form>
9089

9190
<div>
92-
<a action="Index">Back to list</a>
91+
<a asp-action="Index">Back to list</a>
9392
</div>

samples/TagHelperSample.Web/Views/Home/Edit.cshtml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@
66

77
<form>
88
<div class="form-horizontal">
9-
<div validation-summary="All"/>
10-
<input type="hidden" for="Id" />
9+
<div asp-validation-summary="All" />
10+
<input type="hidden" asp-for="Id" />
1111

1212
<div class="form-group">
13-
<label for="Name" class="control-label col-md-2" />
13+
<label asp-for="Name" class="control-label col-md-2" />
1414
<div class="col-md-10">
15-
<input type="text" for="Name" />
15+
<input type="text" asp-for="Name" />
1616
<span validation-for="Name" />
1717
</div>
1818
</div>
1919
<div class="form-group">
20-
<label for="DateOfBirth" class="control-label col-md-2" />
20+
<label asp-for="DateOfBirth" class="control-label col-md-2" />
2121
<div class="col-md-10">
22-
<input type="date" for="DateOfBirth" format="{0:yyyy-MM-dd}" />
23-
<span validation-for="DateOfBirth" />
22+
<input type="date" asp-for="DateOfBirth" asp-format="{0:yyyy-MM-dd}" />
23+
<span asp-validation-for="DateOfBirth" />
2424
</div>
2525
</div>
2626
<div class="form-group">
27-
<label for="YearsEmployeed" class="control-label col-md-2" />
27+
<label asp-for="YearsEmployeed" class="control-label col-md-2" />
2828
<div class="col-md-10">
29-
<select for="YearsEmployeed" items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
29+
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
3030
<optgroup label="Newby">
3131
<option value="0">Less than 1</option>
3232
<option value="1">1</option>
@@ -37,14 +37,14 @@
3737
<option value="5">5</option>
3838
<option value="6">6</option>
3939
</select>
40-
<span validation-for="YearsEmployeed" />
40+
<span asp-validation-for="YearsEmployeed" />
4141
</div>
4242
</div>
4343
<div class="form-group">
44-
<label for="Blurb" class="control-label col-md-2" />
44+
<label asp-for="Blurb" class="control-label col-md-2" />
4545
<div class="col-md-10">
46-
<textarea rows="4" for="Blurb"></textarea>
47-
<span validation-for="Blurb" />
46+
<textarea rows="4" asp-for="Blurb"></textarea>
47+
<span asp-validation-for="Blurb" />
4848
</div>
4949
</div>
5050

@@ -57,5 +57,5 @@
5757
</form>
5858

5959
<div>
60-
<a action="Index">Back to list</a>
60+
<a asp-action="Index">Back to list</a>
6161
</div>

samples/TagHelperSample.Web/Views/Home/Index.cshtml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,36 @@
99
@for (var index = 0; index < Model.Count(); ++index)
1010
{
1111
<div class="form-group">
12-
@Html.LabelFor(m => m[index].Name) @* [index].Name [0].Name *@
12+
@Html.LabelFor(m => m[index].Name)
1313
@Html.TextBoxFor(m => m[index].Name, htmlAttributes: new { disabled = "disabled", @readonly= "readonly" })
14-
@*<label for="[index].Name" />
15-
<input type="text" for="[index].Name" disabled="disabled" readonly="readonly" />*@
14+
@*<label asp-for="[index].Name" />
15+
<input type="text" asp-for="[index].Name" disabled="disabled" readonly="readonly" />*@
1616
</div>
1717
<div class="form-group">
1818
@Html.LabelFor(m => m[index].DateOfBirth)
1919
@Html.TextBoxFor(m => m[index].DateOfBirth, format: "{0:yyyy-MM-dd}", htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="date" })
20-
@*<label for="[index].DateOfBirth" />
21-
<input type="date" for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
20+
@*<label asp-for="[index].DateOfBirth" />
21+
<input type="date" asp-for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
2222
</div>
2323
<div class="form-group">
2424
@Html.LabelFor(m => m[index].YearsEmployeed)
2525
@Html.TextBoxFor(m => m[index].YearsEmployeed, htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="number" })
26-
@*<label for="[index].YearsEmployeed" />
27-
<input type="number" for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
26+
@*<label asp-for="[index].YearsEmployeed" />
27+
<input type="number" asp-for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
2828
</div>
2929
<div class="form-group">
3030
@Html.LabelFor(m => m[index].Blurb)
3131
@Html.TextAreaFor(m => m[index].Blurb, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
32-
@*<label for="[index].Blurb" />
33-
<textarea rows="4" for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
32+
@*<label asp-for="[index].Blurb" />
33+
<textarea rows="4" asp-for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
3434
</div>
3535

3636
<p>
37-
<a action="Edit" route-id="@index">Edit</a>
37+
<a asp-action="Edit" asp-route-id="@index">Edit</a>
3838
</p>
3939
}
4040
</div>
4141
}
4242
<p>
43-
<a action="Create">Create New</a>
43+
<a asp-action="Create">Create New</a>
4444
</p>

src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
1515
[TagName("a")]
1616
public class AnchorTagHelper : TagHelper
1717
{
18-
private const string RouteAttributePrefix = "route-";
18+
private const string ActionAttributeName = "asp-action";
19+
private const string ControllerAttributeName = "asp-controller";
20+
private const string FragmentAttributeName = "asp-fragment";
21+
private const string HostAttributeName = "asp-host";
22+
private const string ProtocolAttributeName = "asp-protocol";
23+
private const string RouteAttributeName = "asp-route";
24+
private const string RouteAttributePrefix = "asp-route-";
1925
private const string Href = "href";
2026

2127
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
@@ -26,27 +32,32 @@ public class AnchorTagHelper : TagHelper
2632
/// The name of the action method.
2733
/// </summary>
2834
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
35+
[HtmlAttributeName(ActionAttributeName)]
2936
public string Action { get; set; }
3037

3138
/// <summary>
3239
/// The name of the controller.
3340
/// </summary>
3441
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
42+
[HtmlAttributeName(ControllerAttributeName)]
3543
public string Controller { get; set; }
3644

3745
/// <summary>
3846
/// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;.
3947
/// </summary>
48+
[HtmlAttributeName(ProtocolAttributeName)]
4049
public string Protocol { get; set; }
4150

4251
/// <summary>
4352
/// The host name.
4453
/// </summary>
54+
[HtmlAttributeName(HostAttributeName)]
4555
public string Host { get; set; }
4656

4757
/// <summary>
4858
/// The URL fragment name.
4959
/// </summary>
60+
[HtmlAttributeName(FragmentAttributeName)]
5061
public string Fragment { get; set; }
5162

5263
/// <summary>
@@ -55,17 +66,22 @@ public class AnchorTagHelper : TagHelper
5566
/// <remarks>
5667
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
5768
/// </remarks>
69+
[HtmlAttributeName(RouteAttributeName)]
5870
public string Route { get; set; }
5971

6072
/// <inheritdoc />
61-
/// <remarks>Does nothing if user provides an "href" attribute. Throws an
62-
/// <see cref="InvalidOperationException"/> if "href" attribute is provided and <see cref="Action"/>,
63-
/// <see cref="Controller"/>, or <see cref="Route"/> are non-<c>null</c>.</remarks>
73+
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
74+
/// <exception cref="InvalidOperationException">
75+
/// Thrown if <c>href</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>,
76+
/// <see cref="Fragment"/>, <see cref="Host"/>, <see cref="Protocol"/>, or <see cref="Route"/> are
77+
/// non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes. Also thrown if <see cref="Route"/>
78+
/// and one or both of <see cref="Action"/> and <see cref="Controller"/> are non-<c>null</c>.
79+
/// </exception>
6480
public override void Process(TagHelperContext context, TagHelperOutput output)
6581
{
6682
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
6783

68-
// If there's an "href" on the tag it means it's being used as a normal anchor.
84+
// If "href" is already set, it means the user is attempting to use a normal anchor.
6985
if (output.Attributes.ContainsKey(Href))
7086
{
7187
if (Action != null ||
@@ -77,15 +93,16 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
7793
routePrefixedAttributes.Any())
7894
{
7995
// User specified an href and one of the bound attributes; can't determine the href attribute.
96+
// Reviewers: Should this instead ignore the helper-specific attributes?
8097
throw new InvalidOperationException(
81-
Resources.FormatAnchorTagHelper_CannotOverrideSpecifiedHref(
98+
Resources.FormatAnchorTagHelper_CannotOverrideHref(
8299
"<a>",
83-
nameof(Action).ToLowerInvariant(),
84-
nameof(Controller).ToLowerInvariant(),
85-
nameof(Route).ToLowerInvariant(),
86-
nameof(Protocol).ToLowerInvariant(),
87-
nameof(Host).ToLowerInvariant(),
88-
nameof(Fragment).ToLowerInvariant(),
100+
ActionAttributeName,
101+
ControllerAttributeName,
102+
RouteAttributeName,
103+
ProtocolAttributeName,
104+
HostAttributeName,
105+
FragmentAttributeName,
89106
RouteAttributePrefix,
90107
Href));
91108
}
@@ -112,9 +129,9 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
112129
throw new InvalidOperationException(
113130
Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(
114131
"<a>",
115-
nameof(Route).ToLowerInvariant(),
116-
nameof(Action).ToLowerInvariant(),
117-
nameof(Controller).ToLowerInvariant(),
132+
RouteAttributeName,
133+
ActionAttributeName,
134+
ControllerAttributeName,
118135
Href));
119136
}
120137
else

0 commit comments

Comments
 (0)