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
This commit is contained in:
Doug Bunting 2014-11-30 16:52:44 -08:00
parent 69f63db7bf
commit 27beca7738
18 changed files with 340 additions and 267 deletions

View File

@ -9,48 +9,47 @@
<h2>Create</h2> <h2>Create</h2>
@* anti-forgery is on by default *@ @* anti-forgery is on by default *@
@* form will special-case anything that looks like a URI i.e. contains a '/' or doesn't match an action *@ @* <form/> tag helper will special-case only elements with an "asp-action" or "asp-anti-forgery" attribute. *@
<form anti-forgery="false" action="Create"> <form asp-anti-forgery="false" asp-action="Create">
<div class="form-horizontal"> <div class="form-horizontal">
@* validation summary tag helper will target just <div/> elements and append the list of errors *@ @* validation summary tag helper will target just <div/> elements and append the list of errors *@
@* - i.e. this helper, like <select/> helper has ContentBehavior.Append *@ @* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
@* helper does nothing if model is valid and (client-side validation is disabled or validation-summary="ModelOnly") *@ @* helper does nothing if model is valid and (client-side validation is disabled or asp-validation-summary="ModelOnly") *@
@* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@ @* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@
@* initially at least, will not remove the <div/> if list isn't generated *@ @* initially at least, will not remove the <div/> if list isn't generated *@
@* - should helper remove the <div/> if list isn't generated? *@ @* - should helper remove the <div/> if list isn't generated? *@
@* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@ @* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@
@* Acceptable values are: "None", "ModelOnly" and "All" *@ @* Acceptable values are: "None", "ModelOnly" and "All" *@
<div validation-summary="All" style="color:blue" id="validation_day" class="form-group"> <div asp-validation-summary="All" style="color:blue" id="validation_day" class="form-group">
<span style="color:red">This is my message</span> <span style="color:red">This is my message</span>
</div> </div>
@* element will have correct name and id attributes for Id property. unusual part is the constant value. *@ @* element will have correct name and id attributes for Id property. unusual part is the constant value. *@
@* - the helper will _not_ override the user-specified "value" attribute *@ @* - the helper will _not_ override the user-specified "value" attribute *@
<input type="hidden" for="Id" value="0" /> <input type="hidden" asp-for="Id" value="0" />
<div class="form-group"> <div class="form-group">
@* no special-case for the "for" attribute; may eventually need to opt out on per-element basis here and in <form/> *@ @* no special-case for the "asp-for" attribute; now distinct from HTML "for" attribute. *@
<label for="Name" class="control-label col-md-2" style="color:blue" /> <label asp-for="Name" class="control-label col-md-2" style="color:blue" />
<div class="col-md-10"> <div class="col-md-10">
<input type="text" for="Name" style="color:blue" /> <input type="text" asp-for="Name" style="color:blue" />
<span validation-for="Name" style="color:blue" /> <span asp-validation-for="Name" style="color:blue" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="DateOfBirth" class="control-label col-md-2" /> <label asp-for="DateOfBirth" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
@* will automatically infer type="date" (reused HTML attribute) and format="{0:yyyy-MM-dd}" (optional bound attribute) *@ @* will automatically infer type="date" (reused HTML attribute) and asp-format="{0:yyyy-MM-dd}" (optional bound attribute) *@
<input for="DateOfBirth" /> <input asp-for="DateOfBirth" />
<span validation-for="DateOfBirth">When were you born?</span> <span asp-validation-for="DateOfBirth">When were you born?</span>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="YearsEmployeed" class="control-label col-md-2" /> <label asp-for="YearsEmployeed" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@ @* <select/> tag helper has ContentBehavior.Append -- items render after static options *@
<select for="YearsEmployeed" items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control"> <select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
@* schedule-wise option tag helper (which adds "selected" attribute to static <option/>s) comes after helpers *@ @* Static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
@* - static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
@* - @NTaylorMullen thinks <option/> tag helper could tell <select/> helper not to select anything from "items" *@ @* - @NTaylorMullen thinks <option/> tag helper could tell <select/> helper not to select anything from "items" *@
@* - wouldn't help if user selected one static <option/> and expression indicated another, especially one earlier in the <select/> *@ @* - wouldn't help if user selected one static <option/> and expression indicated another, especially one earlier in the <select/> *@
@* - may need a "default" bound parameter on the <select/> to avoid these cases and maintain "don't override" *@ @* - may need a "default" bound parameter on the <select/> to avoid these cases and maintain "don't override" *@
@ -68,20 +67,20 @@
@* targets only <span/> in Beta; does not support equivalent of Html.ValidationMessageFor()'s tag parameter *@ @* targets only <span/> in Beta; does not support equivalent of Html.ValidationMessageFor()'s tag parameter *@
@* - may eventually either support additional tags e.g. <p/> and <div/> or all tags /> *@ @* - may eventually either support additional tags e.g. <p/> and <div/> or all tags /> *@
<span validation-for="YearsEmployeed" /> <span asp-validation-for="YearsEmployeed" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Blurb" class="control-label col-md-2" /> <label asp-for="Blurb" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
<textarea rows="4" for="Blurb"></textarea> <textarea rows="4" asp-for="Blurb"></textarea>
<span validation-for="Blurb" /> <span asp-validation-for="Blurb" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-md-offset-2 col-md-10"> <div class="col-md-offset-2 col-md-10">
@* this <input/> lacks a "for" attribute and will not be changed by the <input/> tag helper *@ @* this <input/> lacks a "asp-for" attribute and will not be changed by the <input/> tag helper *@
<input type="submit" value="Create" class="btn btn-default" /> <input type="submit" value="Create" class="btn btn-default" />
</div> </div>
</div> </div>
@ -89,5 +88,5 @@
</form> </form>
<div> <div>
<a action="Index">Back to list</a> <a asp-action="Index">Back to list</a>
</div> </div>

View File

@ -6,27 +6,27 @@
<form> <form>
<div class="form-horizontal"> <div class="form-horizontal">
<div validation-summary="All"/> <div asp-validation-summary="All" />
<input type="hidden" for="Id" /> <input type="hidden" asp-for="Id" />
<div class="form-group"> <div class="form-group">
<label for="Name" class="control-label col-md-2" /> <label asp-for="Name" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
<input type="text" for="Name" /> <input type="text" asp-for="Name" />
<span validation-for="Name" /> <span validation-for="Name" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="DateOfBirth" class="control-label col-md-2" /> <label asp-for="DateOfBirth" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
<input type="date" for="DateOfBirth" format="{0:yyyy-MM-dd}" /> <input type="date" asp-for="DateOfBirth" asp-format="{0:yyyy-MM-dd}" />
<span validation-for="DateOfBirth" /> <span asp-validation-for="DateOfBirth" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="YearsEmployeed" class="control-label col-md-2" /> <label asp-for="YearsEmployeed" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
<select for="YearsEmployeed" items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control"> <select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
<optgroup label="Newby"> <optgroup label="Newby">
<option value="0">Less than 1</option> <option value="0">Less than 1</option>
<option value="1">1</option> <option value="1">1</option>
@ -37,14 +37,14 @@
<option value="5">5</option> <option value="5">5</option>
<option value="6">6</option> <option value="6">6</option>
</select> </select>
<span validation-for="YearsEmployeed" /> <span asp-validation-for="YearsEmployeed" />
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Blurb" class="control-label col-md-2" /> <label asp-for="Blurb" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
<textarea rows="4" for="Blurb"></textarea> <textarea rows="4" asp-for="Blurb"></textarea>
<span validation-for="Blurb" /> <span asp-validation-for="Blurb" />
</div> </div>
</div> </div>
@ -57,5 +57,5 @@
</form> </form>
<div> <div>
<a action="Index">Back to list</a> <a asp-action="Index">Back to list</a>
</div> </div>

View File

@ -9,36 +9,36 @@
@for (var index = 0; index < Model.Count(); ++index) @for (var index = 0; index < Model.Count(); ++index)
{ {
<div class="form-group"> <div class="form-group">
@Html.LabelFor(m => m[index].Name) @* [index].Name [0].Name *@ @Html.LabelFor(m => m[index].Name)
@Html.TextBoxFor(m => m[index].Name, htmlAttributes: new { disabled = "disabled", @readonly= "readonly" }) @Html.TextBoxFor(m => m[index].Name, htmlAttributes: new { disabled = "disabled", @readonly= "readonly" })
@*<label for="[index].Name" /> @*<label asp-for="[index].Name" />
<input type="text" for="[index].Name" disabled="disabled" readonly="readonly" />*@ <input type="text" asp-for="[index].Name" disabled="disabled" readonly="readonly" />*@
</div> </div>
<div class="form-group"> <div class="form-group">
@Html.LabelFor(m => m[index].DateOfBirth) @Html.LabelFor(m => m[index].DateOfBirth)
@Html.TextBoxFor(m => m[index].DateOfBirth, format: "{0:yyyy-MM-dd}", htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="date" }) @Html.TextBoxFor(m => m[index].DateOfBirth, format: "{0:yyyy-MM-dd}", htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="date" })
@*<label for="[index].DateOfBirth" /> @*<label asp-for="[index].DateOfBirth" />
<input type="date" for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@ <input type="date" asp-for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
</div> </div>
<div class="form-group"> <div class="form-group">
@Html.LabelFor(m => m[index].YearsEmployeed) @Html.LabelFor(m => m[index].YearsEmployeed)
@Html.TextBoxFor(m => m[index].YearsEmployeed, htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="number" }) @Html.TextBoxFor(m => m[index].YearsEmployeed, htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="number" })
@*<label for="[index].YearsEmployeed" /> @*<label asp-for="[index].YearsEmployeed" />
<input type="number" for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@ <input type="number" asp-for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
</div> </div>
<div class="form-group"> <div class="form-group">
@Html.LabelFor(m => m[index].Blurb) @Html.LabelFor(m => m[index].Blurb)
@Html.TextAreaFor(m => m[index].Blurb, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" }) @Html.TextAreaFor(m => m[index].Blurb, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
@*<label for="[index].Blurb" /> @*<label asp-for="[index].Blurb" />
<textarea rows="4" for="[index].Blurb" disabled="disabled" readonly="readonly" />*@ <textarea rows="4" asp-for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
</div> </div>
<p> <p>
<a action="Edit" route-id="@index">Edit</a> <a asp-action="Edit" asp-route-id="@index">Edit</a>
</p> </p>
} }
</div> </div>
} }
<p> <p>
<a action="Create">Create New</a> <a asp-action="Create">Create New</a>
</p> </p>

View File

@ -15,7 +15,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[TagName("a")] [TagName("a")]
public class AnchorTagHelper : TagHelper public class AnchorTagHelper : TagHelper
{ {
private const string RouteAttributePrefix = "route-"; private const string ActionAttributeName = "asp-action";
private const string ControllerAttributeName = "asp-controller";
private const string FragmentAttributeName = "asp-fragment";
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 Href = "href"; private const string Href = "href";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
@ -26,27 +32,32 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// The name of the action method. /// The name of the action method.
/// </summary> /// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks> /// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
[HtmlAttributeName(ActionAttributeName)]
public string Action { get; set; } public string Action { get; set; }
/// <summary> /// <summary>
/// The name of the controller. /// The name of the controller.
/// </summary> /// </summary>
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks> /// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
[HtmlAttributeName(ControllerAttributeName)]
public string Controller { get; set; } public string Controller { get; set; }
/// <summary> /// <summary>
/// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;. /// The protocol for the URL, such as &quot;http&quot; or &quot;https&quot;.
/// </summary> /// </summary>
[HtmlAttributeName(ProtocolAttributeName)]
public string Protocol { get; set; } public string Protocol { get; set; }
/// <summary> /// <summary>
/// The host name. /// The host name.
/// </summary> /// </summary>
[HtmlAttributeName(HostAttributeName)]
public string Host { get; set; } public string Host { get; set; }
/// <summary> /// <summary>
/// The URL fragment name. /// The URL fragment name.
/// </summary> /// </summary>
[HtmlAttributeName(FragmentAttributeName)]
public string Fragment { get; set; } public string Fragment { get; set; }
/// <summary> /// <summary>
@ -55,17 +66,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <remarks> /// <remarks>
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>. /// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
/// </remarks> /// </remarks>
[HtmlAttributeName(RouteAttributeName)]
public string Route { get; set; } public string Route { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if user provides an "href" attribute. Throws an /// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
/// <see cref="InvalidOperationException"/> if "href" attribute is provided and <see cref="Action"/>, /// <exception cref="InvalidOperationException">
/// <see cref="Controller"/>, or <see cref="Route"/> are non-<c>null</c>.</remarks> /// Thrown if <c>href</c> attribute is provided and <see cref="Action"/>, <see cref="Controller"/>,
/// <see cref="Fragment"/>, <see cref="Host"/>, <see cref="Protocol"/>, or <see cref="Route"/> are
/// non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes. Also thrown if <see cref="Route"/>
/// and one or both of <see cref="Action"/> and <see cref="Controller"/> are non-<c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
// If there's an "href" on the tag it means it's being used as a normal anchor. // If "href" is already set, it means the user is attempting to use a normal anchor.
if (output.Attributes.ContainsKey(Href)) if (output.Attributes.ContainsKey(Href))
{ {
if (Action != null || if (Action != null ||
@ -77,15 +93,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
routePrefixedAttributes.Any()) routePrefixedAttributes.Any())
{ {
// User specified an href and one of the bound attributes; can't determine the href attribute. // User specified an href and one of the bound attributes; can't determine the href attribute.
// Reviewers: Should this instead ignore the helper-specific attributes?
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatAnchorTagHelper_CannotOverrideSpecifiedHref( Resources.FormatAnchorTagHelper_CannotOverrideHref(
"<a>", "<a>",
nameof(Action).ToLowerInvariant(), ActionAttributeName,
nameof(Controller).ToLowerInvariant(), ControllerAttributeName,
nameof(Route).ToLowerInvariant(), RouteAttributeName,
nameof(Protocol).ToLowerInvariant(), ProtocolAttributeName,
nameof(Host).ToLowerInvariant(), HostAttributeName,
nameof(Fragment).ToLowerInvariant(), FragmentAttributeName,
RouteAttributePrefix, RouteAttributePrefix,
Href)); Href));
} }
@ -112,9 +129,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified( Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(
"<a>", "<a>",
nameof(Route).ToLowerInvariant(), RouteAttributeName,
nameof(Action).ToLowerInvariant(), ActionAttributeName,
nameof(Controller).ToLowerInvariant(), ControllerAttributeName,
Href)); Href));
} }
else else

View File

@ -16,7 +16,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[ContentBehavior(ContentBehavior.Append)] [ContentBehavior(ContentBehavior.Append)]
public class FormTagHelper : TagHelper public class FormTagHelper : TagHelper
{ {
private const string RouteAttributePrefix = "route-"; private const string ActionAttributeName = "asp-action";
private const string AntiForgeryAttributeName = "asp-anti-forgery";
private const string ControllerAttributeName = "asp-controller";
private const string RouteAttributePrefix = "asp-route-";
private const string HtmlActionAttributeName = "action";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate] [Activate]
@ -29,14 +33,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// The name of the action method. /// The name of the action method.
/// </summary> /// </summary>
/// <remarks> [HtmlAttributeName(ActionAttributeName)]
/// If value contains a '/' this <see cref="ITagHelper"/> will do nothing.
/// </remarks>
public string Action { get; set; } public string Action { get; set; }
/// <summary> /// <summary>
/// The name of the controller. /// The name of the controller.
/// </summary> /// </summary>
[HtmlAttributeName(ControllerAttributeName)]
public string Controller { get; set; } public string Controller { get; set; }
/// <summary> /// <summary>
@ -45,41 +48,45 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public string Method { get; set; } public string Method { get; set; }
/// <summary> /// <summary>
/// Whether the anti-forgery token should be generated. Defaults to <c>true</c> if <see cref="Action"/> is not /// Whether the anti-forgery token should be generated.
/// a URL, <c>false</c> otherwise.
/// </summary> /// </summary>
[HtmlAttributeName("anti-forgery")] /// <value>Defaults to <c>false</c> if user provides an <c>action</c> attribute; <c>true</c> otherwise.</value>
[HtmlAttributeName(AntiForgeryAttributeName)]
public bool? AntiForgery { get; set; } public bool? AntiForgery { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if <see cref="Action"/> contains a '/'.</remarks> /// <remarks>At most adds an anti-forgery token if user provides an <c>action</c> attribute.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <c>action</c> attribute is provided and <see cref="Action"/> or <see cref="Controller"/> are
/// non-<c>null</c> or if the user provided <c>asp-route-*</c> attributes.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
bool antiForgeryDefault = true; bool antiForgeryDefault = true;
var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix);
// If Action contains a '/' it means the user is attempting to use the FormTagHelper as a normal form. // If "action" is already set, it means the user is attempting to use a normal <form>.
if (Action != null && Action.Contains('/')) if (output.Attributes.ContainsKey(HtmlActionAttributeName))
{ {
if (Controller != null || routePrefixedAttributes.Any()) if (Action != null || Controller != null || routePrefixedAttributes.Any())
{ {
// We don't know how to generate a form action since a Controller attribute was also provided. // User also specified bound attributes we cannot use.
// Reviewers: Should this instead ignore the helper-specific attributes -- only change
// antiForgeryDefault?
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatFormTagHelper_CannotDetermineAction( Resources.FormatFormTagHelper_CannotOverrideAction(
"<form>", "<form>",
nameof(Action).ToLowerInvariant(), HtmlActionAttributeName,
nameof(Controller).ToLowerInvariant(), ActionAttributeName,
ControllerAttributeName,
RouteAttributePrefix)); RouteAttributePrefix));
} }
// User is using the FormTagHelper like a normal <form> tag, anti-forgery default should be false to // User is using the FormTagHelper like a normal <form> tag. Anti-forgery default should be false to
// not force the anti-forgery token onto the user. // not force the anti-forgery token on the user.
antiForgeryDefault = false; antiForgeryDefault = false;
// Restore Action, Method and Route HTML attributes if they were provided, user wants non-TagHelper <form>. // Restore method attribute.
output.CopyHtmlAttribute(nameof(Action), context);
if (Method != null) if (Method != null)
{ {
output.CopyHtmlAttribute(nameof(Method), context); output.CopyHtmlAttribute(nameof(Method), context);

View File

@ -13,11 +13,14 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;input&gt; elements. /// <see cref="ITagHelper"/> implementation targeting &lt;input&gt; elements with an <c>asp-for</c> attribute.
/// </summary> /// </summary>
[ContentBehavior(ContentBehavior.Replace)] [ContentBehavior(ContentBehavior.Replace)]
public class InputTagHelper : TagHelper public class InputTagHelper : TagHelper
{ {
private const string ForAttributeName = "asp-for";
private const string FormatAttributeName = "asp-format";
// Mapping from datatype names and data annotation hints to values for the <input/> element's "type" attribute. // Mapping from datatype names and data annotation hints to values for the <input/> element's "type" attribute.
private static readonly Dictionary<string, string> _defaultInputTypes = private static readonly Dictionary<string, string> _defaultInputTypes =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
@ -64,6 +67,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// An expression to be evaluated against the current model. /// An expression to be evaluated against the current model.
/// </summary> /// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; } public ModelExpression For { get; set; }
/// <summary> /// <summary>
@ -76,6 +80,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <see cref="InputTypeName"/> is "String". That is, <see cref="Format"/> is used when calling /// <see cref="InputTypeName"/> is "String". That is, <see cref="Format"/> is used when calling
/// <see cref="IHtmlGenerator.GenerateTextBox"/>. /// <see cref="IHtmlGenerator.GenerateTextBox"/>.
/// </remarks> /// </remarks>
[HtmlAttributeName(FormatAttributeName)]
public string Format { get; set; } public string Format { get; set; }
/// <summary> /// <summary>
@ -99,7 +104,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public string Value { get; set; } public string Value { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c></remarks> /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Format"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
// Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying // Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying
@ -117,12 +125,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
if (For == null) if (For == null)
{ {
// Regular HTML <input/> element. Just make sure Format wasn't specified. // Regular HTML <input/> element. Just make sure Format wasn't specified.
// Reviewers: Should this instead ignore the unused helper-specific attribute?
if (Format != null) if (Format != null)
{ {
throw new InvalidOperationException(Resources.FormatInputTagHelper_UnableToFormat( throw new InvalidOperationException(Resources.FormatInputTagHelper_UnableToFormat(
"<input>", "<input>",
nameof(For).ToLowerInvariant(), ForAttributeName,
nameof(Format).ToLowerInvariant())); FormatAttributeName));
} }
} }
else else
@ -134,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<input>", "<input>",
nameof(For).ToLowerInvariant(), ForAttributeName,
nameof(IModelMetadataProvider), nameof(IModelMetadataProvider),
For.Name)); For.Name));
} }
@ -210,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult( throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult(
"<input>", "<input>",
nameof(For).ToLowerInvariant(), ForAttributeName,
metadata.RealModelType.FullName, metadata.RealModelType.FullName,
typeof(bool).FullName, typeof(bool).FullName,
"type", "type",

View File

@ -8,11 +8,13 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with <c>for</c> attributes. /// <see cref="ITagHelper"/> implementation targeting &lt;label&gt; elements with an <c>asp-for</c> attribute.
/// </summary> /// </summary>
[ContentBehavior(ContentBehavior.Modify)] [ContentBehavior(ContentBehavior.Modify)]
public class LabelTagHelper : TagHelper public class LabelTagHelper : TagHelper
{ {
private const string ForAttributeName = "asp-for";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate] [Activate]
protected internal ViewContext ViewContext { get; set; } protected internal ViewContext ViewContext { get; set; }
@ -24,9 +26,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// An expression to be evaluated against the current model. /// An expression to be evaluated against the current model.
/// </summary> /// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; } public ModelExpression For { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (For != null) if (For != null)

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
= new ResourceManager("Microsoft.AspNet.Mvc.TagHelpers.Resources", typeof(Resources).GetTypeInfo().Assembly); = new ResourceManager("Microsoft.AspNet.Mvc.TagHelpers.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary> /// <summary>
/// Cannot determine an '{4}' for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. /// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
/// </summary> /// </summary>
internal static string AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified internal static string AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified
{ {
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
} }
/// <summary> /// <summary>
/// Cannot determine an '{4}' for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute. /// Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.
/// </summary> /// </summary>
internal static string FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4) internal static string FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(object p0, object p1, object p2, object p3, object p4)
{ {
@ -27,19 +27,35 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
} }
/// <summary> /// <summary>
/// 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. /// Cannot override the '{8}' attribute 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.
/// </summary> /// </summary>
internal static string AnchorTagHelper_CannotOverrideSpecifiedHref internal static string AnchorTagHelper_CannotOverrideHref
{ {
get { return GetString("AnchorTagHelper_CannotOverrideSpecifiedHref"); } get { return GetString("AnchorTagHelper_CannotOverrideHref"); }
} }
/// <summary> /// <summary>
/// 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. /// Cannot override the '{8}' attribute 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.
/// </summary> /// </summary>
internal static string FormatAnchorTagHelper_CannotOverrideSpecifiedHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8) internal static string FormatAnchorTagHelper_CannotOverrideHref(object p0, object p1, object p2, object p3, object p4, object p5, object p6, object p7, object p8)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideSpecifiedHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8); return string.Format(CultureInfo.CurrentCulture, GetString("AnchorTagHelper_CannotOverrideHref"), p0, p1, p2, p3, p4, p5, p6, p7, p8);
}
/// <summary>
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{4}' or an '{2}' or '{3}' attribute.
/// </summary>
internal static string FormTagHelper_CannotOverrideAction
{
get { return GetString("FormTagHelper_CannotOverrideAction"); }
}
/// <summary>
/// Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{4}' or an '{2}' or '{3}' attribute.
/// </summary>
internal static string FormatFormTagHelper_CannotOverrideAction(object p0, object p1, object p2, object p3, object p4)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotOverrideAction"), p0, p1, p2, p3, p4);
} }
/// <summary> /// <summary>
@ -90,22 +106,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_ValueRequired"), p0, p1, p2, p3); return string.Format(CultureInfo.CurrentCulture, GetString("InputTagHelper_ValueRequired"), p0, p1, p2, p3);
} }
/// <summary>
/// Cannot determine an '{1}' for {0}. A {0} with a URL-based '{1}' must not have attributes starting with '{3}' or a '{2}' attribute.
/// </summary>
internal static string FormTagHelper_CannotDetermineAction
{
get { return GetString("FormTagHelper_CannotDetermineAction"); }
}
/// <summary>
/// Cannot determine an '{1}' for {0}. A {0} with a URL-based '{1}' must not have attributes starting with '{3}' or a '{2}' attribute.
/// </summary>
internal static string FormatFormTagHelper_CannotDetermineAction(object p0, object p1, object p2, object p3)
{
return string.Format(CultureInfo.CurrentCulture, GetString("FormTagHelper_CannotDetermineAction"), p0, p1, p2, p3);
}
/// <summary> /// <summary>
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null. /// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
/// </summary> /// </summary>

View File

@ -118,10 +118,13 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified" xml:space="preserve"> <data name="AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified" xml:space="preserve">
<value>Cannot determine an '{4}' for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value> <value>Cannot determine an '{4}' attribute for {0}. An {0} with a specified '{1}' must not have an '{2}' or '{3}' attribute.</value>
</data> </data>
<data name="AnchorTagHelper_CannotOverrideSpecifiedHref" xml:space="preserve"> <data name="AnchorTagHelper_CannotOverrideHref" xml:space="preserve">
<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> <value>Cannot override the '{8}' attribute 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>
</data>
<data name="FormTagHelper_CannotOverrideAction" xml:space="preserve">
<value>Cannot override the '{1}' attribute for {0}. A {0} with a specified '{1}' must not have attributes starting with '{4}' or an '{2}' or '{3}' attribute.</value>
</data> </data>
<data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve"> <data name="InputTagHelper_InvalidExpressionResult" xml:space="preserve">
<value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value> <value>Unexpected '{1}' expression result type '{2}' for {0}. '{1}' must be of type '{3}' if '{4}' is '{5}'.</value>
@ -132,9 +135,6 @@
<data name="InputTagHelper_ValueRequired" xml:space="preserve"> <data name="InputTagHelper_ValueRequired" xml:space="preserve">
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value> <value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
</data> </data>
<data name="FormTagHelper_CannotDetermineAction" xml:space="preserve">
<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>
</data>
<data name="SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified" xml:space="preserve"> <data name="SelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified" xml:space="preserve">
<value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value> <value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value>
</data> </data>

View File

@ -13,11 +13,14 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements. /// <see cref="ITagHelper"/> implementation targeting &lt;select&gt; elements with an <c>asp-for</c> attribute.
/// </summary> /// </summary>
[ContentBehavior(ContentBehavior.Append)] [ContentBehavior(ContentBehavior.Append)]
public class SelectTagHelper : TagHelper public class SelectTagHelper : TagHelper
{ {
private const string ForAttributeName = "asp-for";
private const string ItemsAttributeName = "asp-items";
/// <summary> /// <summary>
/// Key used for selected values in <see cref="FormContext.FormData"/>. /// Key used for selected values in <see cref="FormContext.FormData"/>.
/// </summary> /// </summary>
@ -39,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// An expression to be evaluated against the current model. /// An expression to be evaluated against the current model.
/// </summary> /// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; } public ModelExpression For { get; set; }
/// <summary> /// <summary>
@ -55,21 +59,26 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// A collection of <see cref="SelectListItem"/> objects used to populate the &lt;select&gt; element with /// A collection of <see cref="SelectListItem"/> objects used to populate the &lt;select&gt; element with
/// &lt;optgroup&gt; and &lt;option&gt; elements. /// &lt;optgroup&gt; and &lt;option&gt; elements.
/// </summary> /// </summary>
[HtmlAttributeName(ItemsAttributeName)]
public IEnumerable<SelectListItem> Items { get; set; } public IEnumerable<SelectListItem> Items { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks> /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="Items"/> is non-<c>null</c> but <see cref="For"/> is <c>null</c>.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (For == null) if (For == null)
{ {
// Regular HTML <select/> element. Just make sure Items wasn't specified. // Regular HTML <select/> element. Just make sure Items wasn't specified.
// Reviewers: Should this instead ignore the unused helper-specific attribute?
if (Items != null) if (Items != null)
{ {
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified( var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
"<select>", "<select>",
nameof(For).ToLowerInvariant(), ForAttributeName,
nameof(Items).ToLowerInvariant()); ItemsAttributeName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
@ -88,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
"<select>", "<select>",
nameof(For).ToLowerInvariant(), ForAttributeName,
nameof(IModelMetadataProvider), nameof(IModelMetadataProvider),
For.Name)); For.Name));
} }

View File

@ -8,11 +8,13 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;textarea&gt; elements. /// <see cref="ITagHelper"/> implementation targeting &lt;textarea&gt; elements with an <c>asp-for</c> attribute.
/// </summary> /// </summary>
[ContentBehavior(ContentBehavior.Replace)] [ContentBehavior(ContentBehavior.Replace)]
public class TextAreaTagHelper : TagHelper public class TextAreaTagHelper : TagHelper
{ {
private const string ForAttributeName = "asp-for";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate] [Activate]
protected internal IHtmlGenerator Generator { get; set; } protected internal IHtmlGenerator Generator { get; set; }
@ -24,10 +26,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// An expression to be evaluated against the current model. /// An expression to be evaluated against the current model.
/// </summary> /// </summary>
[HtmlAttributeName(ForAttributeName)]
public ModelExpression For { get; set; } public ModelExpression For { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing unless user binds "for" attribute in Razor source.</remarks> /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (For != null) if (For != null)

View File

@ -8,12 +8,15 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;span&gt; elements with <c>validation-for</c> attributes. /// <see cref="ITagHelper"/> implementation targeting &lt;span&gt; elements with an <c>asp-validation-for</c>
/// attribute.
/// </summary> /// </summary>
[TagName("span")] [TagName("span")]
[ContentBehavior(ContentBehavior.Modify)] [ContentBehavior(ContentBehavior.Modify)]
public class ValidationMessageTagHelper : TagHelper public class ValidationMessageTagHelper : TagHelper
{ {
private const string ValidationForAttributeName = "asp-validation-for";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate] [Activate]
protected internal ViewContext ViewContext { get; set; } protected internal ViewContext ViewContext { get; set; }
@ -25,10 +28,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// <summary> /// <summary>
/// Name to be validated on the current model. /// Name to be validated on the current model.
/// </summary> /// </summary>
[HtmlAttributeName("validation-for")] [HtmlAttributeName(ValidationForAttributeName)]
public ModelExpression For { get; set; } public ModelExpression For { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (For != null) if (For != null)

View File

@ -9,13 +9,15 @@ using Microsoft.AspNet.Razor.TagHelpers;
namespace Microsoft.AspNet.Mvc.TagHelpers namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
/// <summary> /// <summary>
/// <see cref="ITagHelper"/> implementation targeting &lt;div&gt; elements with a <c>validation-summary</c> /// <see cref="ITagHelper"/> implementation targeting &lt;div&gt; elements with an <c>asp-validation-summary</c>
/// attribute. /// attribute.
/// </summary> /// </summary>
[TagName("div")] [TagName("div")]
[ContentBehavior(ContentBehavior.Append)] [ContentBehavior(ContentBehavior.Append)]
public class ValidationSummaryTagHelper : TagHelper public class ValidationSummaryTagHelper : TagHelper
{ {
private const string ValidationSummaryAttributeName = "asp-validation-summary";
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing. // Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
[Activate] [Activate]
protected internal ViewContext ViewContext { get; set; } protected internal ViewContext ViewContext { get; set; }
@ -29,11 +31,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
/// If <c>All</c> or <c>ModelOnly</c>, appends a validation summary. Acceptable values are defined by the /// If <c>All</c> or <c>ModelOnly</c>, appends a validation summary. Acceptable values are defined by the
/// <see cref="ValidationSummary"/> enum. /// <see cref="ValidationSummary"/> enum.
/// </summary> /// </summary>
[HtmlAttributeName("validation-summary")] [HtmlAttributeName(ValidationSummaryAttributeName)]
public string ValidationSummaryValue { get; set; } public string ValidationSummaryValue { get; set; }
/// <inheritdoc /> /// <inheritdoc />
/// Does nothing if <see cref="ValidationSummaryValue"/> is <c>null</c>, empty or "None". /// <remarks>Does nothing if <see cref="ValidationSummaryValue"/> is <c>null</c>, empty or "None".</remarks>
/// <exception cref="InvalidOperationException">
/// Thrown if <see cref="ValidationSummaryValue"/> is not a valid <see cref="ValidationSummary"/> value.
/// </exception>
public override void Process(TagHelperContext context, TagHelperOutput output) public override void Process(TagHelperContext context, TagHelperOutput output)
{ {
if (!string.IsNullOrEmpty(ValidationSummaryValue)) if (!string.IsNullOrEmpty(ValidationSummaryValue))
@ -44,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
throw new InvalidOperationException( throw new InvalidOperationException(
Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues( Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues(
"<div>", "<div>",
"validation-summary", ValidationSummaryAttributeName,
ValidationSummaryValue, ValidationSummaryValue,
ValidationSummary.All, ValidationSummary.All,
ValidationSummary.ModelOnly, ValidationSummary.ModelOnly,

View File

@ -20,32 +20,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange // Arrange
var expectedTagName = "not-a"; var expectedTagName = "not-a";
var metadataProvider = new DataAnnotationsModelMetadataProvider(); var metadataProvider = new DataAnnotationsModelMetadataProvider();
var anchorTagHelper = new AnchorTagHelper
{
Action = "index",
Controller = "home",
Fragment = "hello=world",
Host = "contoso.com",
Protocol = "http"
};
var tagHelperContext = new TagHelperContext( var tagHelperContext = new TagHelperContext(
allAttributes: new Dictionary<string, object> allAttributes: new Dictionary<string, object>
{ {
{ "id", "myanchor" }, { "id", "myanchor" },
{ "route-foo", "bar" }, { "asp-route-foo", "bar" },
{ "action", "index" }, { "asp-action", "index" },
{ "controller", "home" }, { "asp-controller", "home" },
{ "fragment", "hello=world" }, { "asp-fragment", "hello=world" },
{ "host", "contoso.com" }, { "asp-host", "contoso.com" },
{ "protocol", "http" } { "asp-protocol", "http" }
}); });
var output = new TagHelperOutput( var output = new TagHelperOutput(
expectedTagName, expectedTagName,
attributes: new Dictionary<string, string> attributes: new Dictionary<string, string>
{ {
{ "id", "myanchor" }, { "id", "myanchor" },
{ "route-foo", "bar" }, { "asp-route-foo", "bar" },
}, },
content: "Something"); content: "Something");
@ -64,7 +56,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var viewContext = TestableHtmlGenerator.GetViewContext(model: null, var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
htmlGenerator: htmlGenerator, htmlGenerator: htmlGenerator,
metadataProvider: metadataProvider); metadataProvider: metadataProvider);
anchorTagHelper.Generator = htmlGenerator; var anchorTagHelper = new AnchorTagHelper
{
Action = "index",
Controller = "home",
Fragment = "hello=world",
Generator = htmlGenerator,
Host = "contoso.com",
Protocol = "http",
};
// Act // Act
await anchorTagHelper.ProcessAsync(tagHelperContext, output); await anchorTagHelper.ProcessAsync(tagHelperContext, output);
@ -83,13 +83,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters() public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters()
{ {
// Arrange // Arrange
var anchorTagHelper = new AnchorTagHelper
{
Route = "Default",
Protocol = "http",
Host = "contoso.com",
Fragment = "hello=world"
};
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>()); allAttributes: new Dictionary<string, object>());
var output = new TagHelperOutput( var output = new TagHelperOutput(
@ -103,7 +96,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
string.Empty, "Default", "http", "contoso.com", "hello=world", null, null)) string.Empty, "Default", "http", "contoso.com", "hello=world", null, null))
.Returns(new TagBuilder("a")) .Returns(new TagBuilder("a"))
.Verifiable(); .Verifiable();
anchorTagHelper.Generator = generator.Object; var anchorTagHelper = new AnchorTagHelper
{
Fragment = "hello=world",
Generator = generator.Object,
Host = "contoso.com",
Protocol = "http",
Route = "Default",
};
// Act & Assert // Act & Assert
await anchorTagHelper.ProcessAsync(context, output); await anchorTagHelper.ProcessAsync(context, output);
@ -117,14 +117,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
public async Task ProcessAsync_CallsIntoActionLinkWithExpectedParameters() public async Task ProcessAsync_CallsIntoActionLinkWithExpectedParameters()
{ {
// Arrange // Arrange
var anchorTagHelper = new AnchorTagHelper
{
Action = "Index",
Controller = "Home",
Protocol = "http",
Host = "contoso.com",
Fragment = "hello=world"
};
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>()); allAttributes: new Dictionary<string, object>());
var output = new TagHelperOutput( var output = new TagHelperOutput(
@ -138,7 +130,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
string.Empty, "Index", "Home", "http", "contoso.com", "hello=world", null, null)) string.Empty, "Index", "Home", "http", "contoso.com", "hello=world", null, null))
.Returns(new TagBuilder("a")) .Returns(new TagBuilder("a"))
.Verifiable(); .Verifiable();
anchorTagHelper.Generator = generator.Object; var anchorTagHelper = new AnchorTagHelper
{
Action = "Index",
Controller = "Home",
Fragment = "hello=world",
Generator = generator.Object,
Host = "contoso.com",
Protocol = "http",
};
// Act & Assert // Act & Assert
await anchorTagHelper.ProcessAsync(context, output); await anchorTagHelper.ProcessAsync(context, output);
@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[InlineData("Protocol")] [InlineData("Protocol")]
[InlineData("Host")] [InlineData("Host")]
[InlineData("Fragment")] [InlineData("Fragment")]
[InlineData("route-")] [InlineData("asp-route-")]
public async Task ProcessAsync_ThrowsIfHrefConflictsWithBoundAttributes(string propertyName) public async Task ProcessAsync_ThrowsIfHrefConflictsWithBoundAttributes(string propertyName)
{ {
// Arrange // Arrange
@ -167,19 +167,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ "href", "http://www.contoso.com" } { "href", "http://www.contoso.com" }
}, },
content: string.Empty); content: string.Empty);
if (propertyName == "asp-route-")
if (propertyName == "route-")
{ {
output.Attributes.Add("route-foo", "bar"); output.Attributes.Add("asp-route-foo", "bar");
} }
else else
{ {
typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home"); typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home");
} }
var expectedErrorMessage = "Cannot determine an 'href' for <a>. An <a> with a specified 'href' must not " + var expectedErrorMessage = "Cannot override the 'href' attribute for <a>. An <a> with a specified " +
"have attributes starting with 'route-' or an 'action', 'controller', " + "'href' must not have attributes starting with 'asp-route-' or an " +
"'route', 'protocol', 'host', or 'fragment' attribute."; "'asp-action', 'asp-controller', 'asp-route', 'asp-protocol', 'asp-host', or " +
"'asp-fragment' attribute.";
// Act & Assert // Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>( var ex = await Assert.ThrowsAsync<InvalidOperationException>(
@ -196,15 +196,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange // Arrange
var anchorTagHelper = new AnchorTagHelper var anchorTagHelper = new AnchorTagHelper
{ {
Route = "Default" Route = "Default",
}; };
typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home"); typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home");
var output = new TagHelperOutput( var output = new TagHelperOutput(
"a", "a",
attributes: new Dictionary<string, string>(), attributes: new Dictionary<string, string>(),
content: string.Empty); content: string.Empty);
var expectedErrorMessage = "Cannot determine an 'href' for <a>. An <a> with a " + var expectedErrorMessage = "Cannot determine an 'href' attribute for <a>. An <a> with a specified " +
"specified 'route' must not have an 'action' or 'controller' attribute."; "'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
// Act & Assert // Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>( var ex = await Assert.ThrowsAsync<InvalidOperationException>(

View File

@ -23,29 +23,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Arrange // Arrange
var expectedTagName = "not-form"; var expectedTagName = "not-form";
var metadataProvider = new DataAnnotationsModelMetadataProvider(); var metadataProvider = new DataAnnotationsModelMetadataProvider();
var formTagHelper = new FormTagHelper
{
Action = "index",
Controller = "home",
Method = "post",
AntiForgery = true
};
var tagHelperContext = new TagHelperContext( var tagHelperContext = new TagHelperContext(
allAttributes: new Dictionary<string, object> allAttributes: new Dictionary<string, object>
{ {
{ "id", "myform" }, { "id", "myform" },
{ "route-foo", "bar" }, { "asp-route-foo", "bar" },
{ "action", "index" }, { "asp-action", "index" },
{ "controller", "home" }, { "asp-controller", "home" },
{ "method", "post" }, { "method", "post" },
{ "anti-forgery", true } { "asp-anti-forgery", true }
}); });
var output = new TagHelperOutput( var output = new TagHelperOutput(
expectedTagName, expectedTagName,
attributes: new Dictionary<string, string> attributes: new Dictionary<string, string>
{ {
{ "id", "myform" }, { "id", "myform" },
{ "route-foo", "bar" }, { "asp-route-foo", "bar" },
}, },
content: "Something"); content: "Something");
var urlHelper = new Mock<IUrlHelper>(); var urlHelper = new Mock<IUrlHelper>();
@ -64,8 +57,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
metadataProvider: metadataProvider); metadataProvider: metadataProvider);
var expectedContent = "Something" + htmlGenerator.GenerateAntiForgery(viewContext) var expectedContent = "Something" + htmlGenerator.GenerateAntiForgery(viewContext)
.ToString(TagRenderMode.SelfClosing); .ToString(TagRenderMode.SelfClosing);
formTagHelper.ViewContext = viewContext; var formTagHelper = new FormTagHelper
formTagHelper.Generator = htmlGenerator; {
Action = "index",
AntiForgery = true,
Controller = "home",
Generator = htmlGenerator,
Method = "post",
ViewContext = viewContext,
};
// Act // Act
await formTagHelper.ProcessAsync(tagHelperContext, output); await formTagHelper.ProcessAsync(tagHelperContext, output);
@ -90,11 +90,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
// Arrange // Arrange
var viewContext = CreateViewContext(); var viewContext = CreateViewContext();
var formTagHelper = new FormTagHelper
{
Action = "Index",
AntiForgery = antiForgery
};
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>()); allAttributes: new Dictionary<string, object>());
var output = new TagHelperOutput( var output = new TagHelperOutput(
@ -114,8 +109,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
generator.Setup(mock => mock.GenerateAntiForgery(viewContext)) generator.Setup(mock => mock.GenerateAntiForgery(viewContext))
.Returns(new TagBuilder("input")); .Returns(new TagBuilder("input"));
formTagHelper.ViewContext = viewContext; var formTagHelper = new FormTagHelper
formTagHelper.Generator = generator.Object; {
Action = "Index",
AntiForgery = antiForgery,
Generator = generator.Object,
ViewContext = viewContext,
};
// Act // Act
await formTagHelper.ProcessAsync(context, output); await formTagHelper.ProcessAsync(context, output);
@ -131,20 +131,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
// Arrange // Arrange
var testViewContext = CreateViewContext(); var testViewContext = CreateViewContext();
var formTagHelper = new FormTagHelper
{
Action = "Index",
AntiForgery = false
};
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>()); allAttributes: new Dictionary<string, object>());
var expectedAttribute = new KeyValuePair<string, string>("ROUTEE-NotRoute", "something"); var expectedAttribute = new KeyValuePair<string, string>("asp-ROUTEE-NotRoute", "something");
var output = new TagHelperOutput( var output = new TagHelperOutput(
"form", "form",
attributes: new Dictionary<string, string>() attributes: new Dictionary<string, string>()
{ {
{ "route-val", "hello" }, { "asp-route-val", "hello" },
{ "roUte--Foo", "bar" } { "asp-roUte--Foo", "bar" }
}, },
content: string.Empty); content: string.Empty);
output.Attributes.Add(expectedAttribute); output.Attributes.Add(expectedAttribute);
@ -174,8 +169,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
}) })
.Returns(new TagBuilder("form")) .Returns(new TagBuilder("form"))
.Verifiable(); .Verifiable();
formTagHelper.ViewContext = testViewContext; var formTagHelper = new FormTagHelper
formTagHelper.Generator = generator.Object; {
Action = "Index",
AntiForgery = false,
Generator = generator.Object,
ViewContext = testViewContext,
};
// Act & Assert // Act & Assert
await formTagHelper.ProcessAsync(context, output); await formTagHelper.ProcessAsync(context, output);
@ -192,13 +192,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{ {
// Arrange // Arrange
var viewContext = CreateViewContext(); var viewContext = CreateViewContext();
var formTagHelper = new FormTagHelper
{
Action = "Index",
Controller = "Home",
Method = "POST",
AntiForgery = false
};
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>()); allAttributes: new Dictionary<string, object>());
var output = new TagHelperOutput( var output = new TagHelperOutput(
@ -210,8 +203,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, "POST", null)) .Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, "POST", null))
.Returns(new TagBuilder("form")) .Returns(new TagBuilder("form"))
.Verifiable(); .Verifiable();
formTagHelper.ViewContext = viewContext; var formTagHelper = new FormTagHelper
formTagHelper.Generator = generator.Object; {
Action = "Index",
AntiForgery = false,
Controller = "Home",
Generator = generator.Object,
Method = "POST",
ViewContext = viewContext,
};
// Act & Assert // Act & Assert
await formTagHelper.ProcessAsync(context, output); await formTagHelper.ProcessAsync(context, output);
@ -222,22 +222,26 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Empty(output.Content); Assert.Empty(output.Content);
} }
[Fact] [Theory]
public async Task ProcessAsync_RestoresBoundAttributesIfActionIsURL() [InlineData("my-action")]
[InlineData("http://www.contoso.com")]
[InlineData("my/action")]
public async Task ProcessAsync_RestoresBoundAttributesIfActionIsSpecified(string htmlAction)
{ {
// Arrange // Arrange
var formTagHelper = new FormTagHelper var formTagHelper = new FormTagHelper
{ {
Action = "http://www.contoso.com",
Method = "POST" Method = "POST"
}; };
var output = new TagHelperOutput("form", var output = new TagHelperOutput("form",
attributes: new Dictionary<string, string>(), attributes: new Dictionary<string, string>
{
{ "aCTiON", htmlAction },
},
content: string.Empty); content: string.Empty);
var context = new TagHelperContext( var context = new TagHelperContext(
allAttributes: new Dictionary<string, object>() allAttributes: new Dictionary<string, object>()
{ {
{ "aCTiON", "http://www.contoso.com" },
{ "METhod", "POST" } { "METhod", "POST" }
}); });
@ -248,7 +252,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Assert.Equal("form", output.TagName); Assert.Equal("form", output.TagName);
Assert.Equal(2, output.Attributes.Count); Assert.Equal(2, output.Attributes.Count);
var attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("aCTiON")); var attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("aCTiON"));
Assert.Equal("http://www.contoso.com", attribute.Value); Assert.Equal(htmlAction, attribute.Value);
attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("METhod")); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("METhod"));
Assert.Equal("POST", attribute.Value); Assert.Equal("POST", attribute.Value);
Assert.Empty(output.Content); Assert.Empty(output.Content);
@ -258,7 +262,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
[InlineData(true, "<input />")] [InlineData(true, "<input />")]
[InlineData(false, "")] [InlineData(false, "")]
[InlineData(null, "")] [InlineData(null, "")]
public async Task ProcessAsync_SupportsAntiForgeryIfActionIsURL(bool? antiForgery, string expectedContent) public async Task ProcessAsync_SupportsAntiForgeryIfActionIsSpecified(
bool? antiForgery,
string expectedContent)
{ {
// Arrange // Arrange
var viewContext = CreateViewContext(); var viewContext = CreateViewContext();
@ -267,20 +273,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
.Returns(new TagBuilder("input")); .Returns(new TagBuilder("input"));
var formTagHelper = new FormTagHelper var formTagHelper = new FormTagHelper
{ {
Action = "http://www.contoso.com",
AntiForgery = antiForgery, AntiForgery = antiForgery,
Generator = generator.Object,
ViewContext = viewContext,
}; };
formTagHelper.ViewContext = viewContext;
formTagHelper.Generator = generator.Object;
var output = new TagHelperOutput("form", var output = new TagHelperOutput("form",
attributes: new Dictionary<string, string>(), attributes: new Dictionary<string, string>
{
{ "aCTiON", "my-action" },
},
content: string.Empty); content: string.Empty);
var context = new TagHelperContext( var context = new TagHelperContext(allAttributes: new Dictionary<string, object>());
allAttributes: new Dictionary<string, object>()
{
{ "aCTiON", "http://www.contoso.com" }
});
// Act // Act
await formTagHelper.ProcessAsync(context, output); await formTagHelper.ProcessAsync(context, output);
@ -288,52 +292,40 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
// Assert // Assert
Assert.Equal("form", output.TagName); Assert.Equal("form", output.TagName);
var attribute = Assert.Single(output.Attributes); var attribute = Assert.Single(output.Attributes);
Assert.Equal(new KeyValuePair<string, string>("aCTiON", "http://www.contoso.com"), attribute); Assert.Equal(new KeyValuePair<string, string>("aCTiON", "my-action"), attribute);
Assert.Equal(expectedContent, output.Content); Assert.Equal(expectedContent, output.Content);
} }
[Fact] [Theory]
public async Task ProcessAsync_ThrowsIfActionIsUrlWithSpecifiedController() [InlineData("Action")]
[InlineData("Controller")]
[InlineData("asp-route-")]
public async Task ProcessAsync_ThrowsIfActionConflictsWithBoundAttributes(string propertyName)
{ {
// Arrange // Arrange
var formTagHelper = new FormTagHelper var formTagHelper = new FormTagHelper
{ {
Action = "http://www.contoso.com",
Controller = "Home",
Method = "POST" Method = "POST"
}; };
var expectedErrorMessage = "Cannot determine an 'action' for <form>. A <form> with a URL-based 'action' " +
"must not have attributes starting with 'route-' or a 'controller' attribute.";
var tagHelperOutput = new TagHelperOutput(
"form",
attributes: new Dictionary<string, string>(),
content: string.Empty);
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => formTagHelper.ProcessAsync(context: null, output: tagHelperOutput));
Assert.Equal(expectedErrorMessage, ex.Message);
}
[Fact]
public async Task ProcessAsync_ThrowsIfActionIsUrlWithSpecifiedRoutes()
{
// Arrange
var formTagHelper = new FormTagHelper
{
Action = "http://www.contoso.com",
Method = "POST"
};
var expectedErrorMessage = "Cannot determine an 'action' for <form>. A <form> with a URL-based 'action' " +
"must not have attributes starting with 'route-' or a 'controller' attribute.";
var tagHelperOutput = new TagHelperOutput( var tagHelperOutput = new TagHelperOutput(
"form", "form",
attributes: new Dictionary<string, string> attributes: new Dictionary<string, string>
{ {
{ "route-foo", "bar" } { "action", "my-action" },
}, },
content: string.Empty); content: string.Empty);
if (propertyName == "asp-route-")
{
tagHelperOutput.Attributes.Add("asp-route-foo", "bar");
}
else
{
typeof(FormTagHelper).GetProperty(propertyName).SetValue(formTagHelper, "Home");
}
var expectedErrorMessage = "Cannot override the 'action' attribute for <form>. A <form> with a specified " +
"'action' must not have attributes starting with 'asp-route-' or an " +
"'asp-action' or 'asp-controller' attribute.";
// Act & Assert // Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>( var ex = await Assert.ThrowsAsync<InvalidOperationException>(

View File

@ -552,6 +552,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
expressionName: propertyName); expressionName: propertyName);
} }
[Fact]
public async Task ProcessAsync_Throws_IfForNotBoundButFormatIs()
{
// Arrange
var contextAttributes = new Dictionary<string, object>();
var originalAttributes = new Dictionary<string, string>();
var content = "original content";
var expectedTagName = "select";
var expectedMessage = "Unable to format without a 'asp-for' expression for <input>. 'asp-format' must " +
"be null if 'asp-for' is null.";
var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(expectedTagName, originalAttributes, content);
var tagHelper = new InputTagHelper
{
Format = "{0}",
};
// Act & Assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(
() => tagHelper.ProcessAsync(tagHelperContext, output));
Assert.Equal(expectedMessage, exception.Message);
}
private static InputTagHelper GetTagHelper( private static InputTagHelper GetTagHelper(
IHtmlGenerator htmlGenerator, IHtmlGenerator htmlGenerator,
object container, object container,

View File

@ -469,7 +469,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var originalAttributes = new Dictionary<string, string>(); var originalAttributes = new Dictionary<string, string>();
var content = "original content"; var content = "original content";
var expectedTagName = "select"; var expectedTagName = "select";
var expectedMessage = "Cannot determine body for <select>. 'items' must be null if 'for' is null."; var expectedMessage = "Cannot determine body for <select>. 'asp-items' must be null if 'asp-for' is null.";
var tagHelperContext = new TagHelperContext(contextAttributes); var tagHelperContext = new TagHelperContext(contextAttributes);
var output = new TagHelperOutput(expectedTagName, originalAttributes, content); var output = new TagHelperOutput(expectedTagName, originalAttributes, content);

View File

@ -253,8 +253,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
attributes: new Dictionary<string, string>(), attributes: new Dictionary<string, string>(),
content: "Content of validation message"); content: "Content of validation message");
var expectedViewContext = CreateViewContext(); var expectedViewContext = CreateViewContext();
var expectedMessage = "Cannot parse 'validation-summary' value 'Hello World' for <div>. Acceptable values " + var expectedMessage = "Cannot parse 'asp-validation-summary' value 'Hello World' for <div>. Acceptable " +
"are 'All', 'ModelOnly' and 'None'."; "values are 'All', 'ModelOnly' and 'None'.";
// Act // Act
var ex = await Assert.ThrowsAsync<InvalidOperationException>( var ex = await Assert.ThrowsAsync<InvalidOperationException>(