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:
parent
69f63db7bf
commit
27beca7738
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 "http" or "https".
|
/// The protocol for the URL, such as "http" or "https".
|
||||||
/// </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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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 <input> elements.
|
/// <see cref="ITagHelper"/> implementation targeting <input> 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",
|
||||||
|
|
|
||||||
|
|
@ -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 <label> elements with <c>for</c> attributes.
|
/// <see cref="ITagHelper"/> implementation targeting <label> 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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 <select> elements.
|
/// <see cref="ITagHelper"/> implementation targeting <select> 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 <select> element with
|
/// A collection of <see cref="SelectListItem"/> objects used to populate the <select> element with
|
||||||
/// <optgroup> and <option> elements.
|
/// <optgroup> and <option> 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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 <textarea> elements.
|
/// <see cref="ITagHelper"/> implementation targeting <textarea> 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)
|
||||||
|
|
|
||||||
|
|
@ -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 <span> elements with <c>validation-for</c> attributes.
|
/// <see cref="ITagHelper"/> implementation targeting <span> 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)
|
||||||
|
|
|
||||||
|
|
@ -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 <div> elements with a <c>validation-summary</c>
|
/// <see cref="ITagHelper"/> implementation targeting <div> 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,
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue