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>
|
||||
|
||||
@* 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 anti-forgery="false" action="Create">
|
||||
@* <form/> tag helper will special-case only elements with an "asp-action" or "asp-anti-forgery" attribute. *@
|
||||
<form asp-anti-forgery="false" asp-action="Create">
|
||||
<div class="form-horizontal">
|
||||
@* 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 *@
|
||||
@* helper does nothing if model is valid and (client-side validation is disabled or validation-summary="ModelOnly") *@
|
||||
@* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
|
||||
@* 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 *@
|
||||
@* initially at least, will not 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) *@
|
||||
@* 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>
|
||||
</div>
|
||||
|
||||
@* 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 *@
|
||||
<input type="hidden" for="Id" value="0" />
|
||||
<input type="hidden" asp-for="Id" value="0" />
|
||||
|
||||
<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/> *@
|
||||
<label for="Name" class="control-label col-md-2" style="color:blue" />
|
||||
@* no special-case for the "asp-for" attribute; now distinct from HTML "for" attribute. *@
|
||||
<label asp-for="Name" class="control-label col-md-2" style="color:blue" />
|
||||
<div class="col-md-10">
|
||||
<input type="text" for="Name" style="color:blue" />
|
||||
<span validation-for="Name" style="color:blue" />
|
||||
<input type="text" asp-for="Name" style="color:blue" />
|
||||
<span asp-validation-for="Name" style="color:blue" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
@* will automatically infer type="date" (reused HTML attribute) and format="{0:yyyy-MM-dd}" (optional bound attribute) *@
|
||||
<input for="DateOfBirth" />
|
||||
<span validation-for="DateOfBirth">When were you born?</span>
|
||||
@* will automatically infer type="date" (reused HTML attribute) and asp-format="{0:yyyy-MM-dd}" (optional bound attribute) *@
|
||||
<input asp-for="DateOfBirth" />
|
||||
<span asp-validation-for="DateOfBirth">When were you born?</span>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@
|
||||
<select for="YearsEmployeed" 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/> *@
|
||||
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
|
||||
@* 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" *@
|
||||
@* - 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" *@
|
||||
|
|
@ -68,20 +67,20 @@
|
|||
|
||||
@* 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 /> *@
|
||||
<span validation-for="YearsEmployeed" />
|
||||
<span asp-validation-for="YearsEmployeed" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<textarea rows="4" for="Blurb"></textarea>
|
||||
<span validation-for="Blurb" />
|
||||
<textarea rows="4" asp-for="Blurb"></textarea>
|
||||
<span asp-validation-for="Blurb" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -89,5 +88,5 @@
|
|||
</form>
|
||||
|
||||
<div>
|
||||
<a action="Index">Back to list</a>
|
||||
<a asp-action="Index">Back to list</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,27 +6,27 @@
|
|||
|
||||
<form>
|
||||
<div class="form-horizontal">
|
||||
<div validation-summary="All"/>
|
||||
<input type="hidden" for="Id" />
|
||||
<div asp-validation-summary="All" />
|
||||
<input type="hidden" asp-for="Id" />
|
||||
|
||||
<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">
|
||||
<input type="text" for="Name" />
|
||||
<input type="text" asp-for="Name" />
|
||||
<span validation-for="Name" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<input type="date" for="DateOfBirth" format="{0:yyyy-MM-dd}" />
|
||||
<span validation-for="DateOfBirth" />
|
||||
<input type="date" asp-for="DateOfBirth" asp-format="{0:yyyy-MM-dd}" />
|
||||
<span asp-validation-for="DateOfBirth" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
<option value="0">Less than 1</option>
|
||||
<option value="1">1</option>
|
||||
|
|
@ -37,14 +37,14 @@
|
|||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
</select>
|
||||
<span validation-for="YearsEmployeed" />
|
||||
<span asp-validation-for="YearsEmployeed" />
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<textarea rows="4" for="Blurb"></textarea>
|
||||
<span validation-for="Blurb" />
|
||||
<textarea rows="4" asp-for="Blurb"></textarea>
|
||||
<span asp-validation-for="Blurb" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -57,5 +57,5 @@
|
|||
</form>
|
||||
|
||||
<div>
|
||||
<a action="Index">Back to list</a>
|
||||
<a asp-action="Index">Back to list</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,36 +9,36 @@
|
|||
@for (var index = 0; index < Model.Count(); ++index)
|
||||
{
|
||||
<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" })
|
||||
@*<label for="[index].Name" />
|
||||
<input type="text" for="[index].Name" disabled="disabled" readonly="readonly" />*@
|
||||
@*<label asp-for="[index].Name" />
|
||||
<input type="text" asp-for="[index].Name" disabled="disabled" readonly="readonly" />*@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@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" })
|
||||
@*<label for="[index].DateOfBirth" />
|
||||
<input type="date" for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
|
||||
@*<label asp-for="[index].DateOfBirth" />
|
||||
<input type="date" asp-for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@Html.LabelFor(m => m[index].YearsEmployeed)
|
||||
@Html.TextBoxFor(m => m[index].YearsEmployeed, htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="number" })
|
||||
@*<label for="[index].YearsEmployeed" />
|
||||
<input type="number" for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
|
||||
@*<label asp-for="[index].YearsEmployeed" />
|
||||
<input type="number" asp-for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@Html.LabelFor(m => m[index].Blurb)
|
||||
@Html.TextAreaFor(m => m[index].Blurb, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
|
||||
@*<label for="[index].Blurb" />
|
||||
<textarea rows="4" for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
|
||||
@*<label asp-for="[index].Blurb" />
|
||||
<textarea rows="4" asp-for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<a action="Edit" route-id="@index">Edit</a>
|
||||
<a asp-action="Edit" asp-route-id="@index">Edit</a>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<p>
|
||||
<a action="Create">Create New</a>
|
||||
<a asp-action="Create">Create New</a>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[TagName("a")]
|
||||
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";
|
||||
|
||||
// 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.
|
||||
/// </summary>
|
||||
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
|
||||
[HtmlAttributeName(ActionAttributeName)]
|
||||
public string Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the controller.
|
||||
/// </summary>
|
||||
/// <remarks>Must be <c>null</c> if <see cref="Route"/> is non-<c>null</c>.</remarks>
|
||||
[HtmlAttributeName(ControllerAttributeName)]
|
||||
public string Controller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The protocol for the URL, such as "http" or "https".
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ProtocolAttributeName)]
|
||||
public string Protocol { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The host name.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(HostAttributeName)]
|
||||
public string Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL fragment name.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(FragmentAttributeName)]
|
||||
public string Fragment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,17 +66,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <remarks>
|
||||
/// Must be <c>null</c> if <see cref="Action"/> or <see cref="Controller"/> is non-<c>null</c>.
|
||||
/// </remarks>
|
||||
[HtmlAttributeName(RouteAttributeName)]
|
||||
public string Route { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if user provides an "href" attribute. Throws an
|
||||
/// <see cref="InvalidOperationException"/> if "href" attribute is provided and <see cref="Action"/>,
|
||||
/// <see cref="Controller"/>, or <see cref="Route"/> are non-<c>null</c>.</remarks>
|
||||
/// <remarks>Does nothing if user provides an <c>href</c> attribute.</remarks>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// 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)
|
||||
{
|
||||
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 (Action != null ||
|
||||
|
|
@ -77,15 +93,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
routePrefixedAttributes.Any())
|
||||
{
|
||||
// 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(
|
||||
Resources.FormatAnchorTagHelper_CannotOverrideSpecifiedHref(
|
||||
Resources.FormatAnchorTagHelper_CannotOverrideHref(
|
||||
"<a>",
|
||||
nameof(Action).ToLowerInvariant(),
|
||||
nameof(Controller).ToLowerInvariant(),
|
||||
nameof(Route).ToLowerInvariant(),
|
||||
nameof(Protocol).ToLowerInvariant(),
|
||||
nameof(Host).ToLowerInvariant(),
|
||||
nameof(Fragment).ToLowerInvariant(),
|
||||
ActionAttributeName,
|
||||
ControllerAttributeName,
|
||||
RouteAttributeName,
|
||||
ProtocolAttributeName,
|
||||
HostAttributeName,
|
||||
FragmentAttributeName,
|
||||
RouteAttributePrefix,
|
||||
Href));
|
||||
}
|
||||
|
|
@ -112,9 +129,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
throw new InvalidOperationException(
|
||||
Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified(
|
||||
"<a>",
|
||||
nameof(Route).ToLowerInvariant(),
|
||||
nameof(Action).ToLowerInvariant(),
|
||||
nameof(Controller).ToLowerInvariant(),
|
||||
RouteAttributeName,
|
||||
ActionAttributeName,
|
||||
ControllerAttributeName,
|
||||
Href));
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -16,7 +16,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[ContentBehavior(ContentBehavior.Append)]
|
||||
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.
|
||||
[Activate]
|
||||
|
|
@ -29,14 +33,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// The name of the action method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If value contains a '/' this <see cref="ITagHelper"/> will do nothing.
|
||||
/// </remarks>
|
||||
[HtmlAttributeName(ActionAttributeName)]
|
||||
public string Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the controller.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ControllerAttributeName)]
|
||||
public string Controller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -45,41 +48,45 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public string Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the anti-forgery token should be generated. Defaults to <c>true</c> if <see cref="Action"/> is not
|
||||
/// a URL, <c>false</c> otherwise.
|
||||
/// Whether the anti-forgery token should be generated.
|
||||
/// </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; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
bool antiForgeryDefault = true;
|
||||
|
||||
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 != null && Action.Contains('/'))
|
||||
// If "action" is already set, it means the user is attempting to use a normal <form>.
|
||||
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(
|
||||
Resources.FormatFormTagHelper_CannotDetermineAction(
|
||||
Resources.FormatFormTagHelper_CannotOverrideAction(
|
||||
"<form>",
|
||||
nameof(Action).ToLowerInvariant(),
|
||||
nameof(Controller).ToLowerInvariant(),
|
||||
HtmlActionAttributeName,
|
||||
ActionAttributeName,
|
||||
ControllerAttributeName,
|
||||
RouteAttributePrefix));
|
||||
}
|
||||
|
||||
// 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.
|
||||
// User is using the FormTagHelper like a normal <form> tag. Anti-forgery default should be false to
|
||||
// not force the anti-forgery token on the user.
|
||||
antiForgeryDefault = false;
|
||||
|
||||
// Restore Action, Method and Route HTML attributes if they were provided, user wants non-TagHelper <form>.
|
||||
output.CopyHtmlAttribute(nameof(Action), context);
|
||||
|
||||
// Restore method attribute.
|
||||
if (Method != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(nameof(Method), context);
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <input> elements.
|
||||
/// <see cref="ITagHelper"/> implementation targeting <input> elements with an <c>asp-for</c> attribute.
|
||||
/// </summary>
|
||||
[ContentBehavior(ContentBehavior.Replace)]
|
||||
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.
|
||||
private static readonly Dictionary<string, string> _defaultInputTypes =
|
||||
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
|
|
@ -64,6 +67,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// An expression to be evaluated against the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <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="IHtmlGenerator.GenerateTextBox"/>.
|
||||
/// </remarks>
|
||||
[HtmlAttributeName(FormatAttributeName)]
|
||||
public string Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -99,7 +104,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public string Value { get; set; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
// Regular HTML <input/> element. Just make sure Format wasn't specified.
|
||||
// Reviewers: Should this instead ignore the unused helper-specific attribute?
|
||||
if (Format != null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatInputTagHelper_UnableToFormat(
|
||||
"<input>",
|
||||
nameof(For).ToLowerInvariant(),
|
||||
nameof(Format).ToLowerInvariant()));
|
||||
ForAttributeName,
|
||||
FormatAttributeName));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -134,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
|
||||
"<input>",
|
||||
nameof(For).ToLowerInvariant(),
|
||||
ForAttributeName,
|
||||
nameof(IModelMetadataProvider),
|
||||
For.Name));
|
||||
}
|
||||
|
|
@ -210,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult(
|
||||
"<input>",
|
||||
nameof(For).ToLowerInvariant(),
|
||||
ForAttributeName,
|
||||
metadata.RealModelType.FullName,
|
||||
typeof(bool).FullName,
|
||||
"type",
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <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>
|
||||
[ContentBehavior(ContentBehavior.Modify)]
|
||||
public class LabelTagHelper : TagHelper
|
||||
{
|
||||
private const string ForAttributeName = "asp-for";
|
||||
|
||||
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
|
||||
[Activate]
|
||||
protected internal ViewContext ViewContext { get; set; }
|
||||
|
|
@ -24,9 +26,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// An expression to be evaluated against the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (For != null)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
= new ResourceManager("Microsoft.AspNet.Mvc.TagHelpers.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <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>
|
||||
internal static string AnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified
|
||||
{
|
||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
/// 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>
|
||||
internal static string AnchorTagHelper_CannotOverrideSpecifiedHref
|
||||
internal static string AnchorTagHelper_CannotOverrideHref
|
||||
{
|
||||
get { return GetString("AnchorTagHelper_CannotOverrideSpecifiedHref"); }
|
||||
get { return GetString("AnchorTagHelper_CannotOverrideHref"); }
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
|
|
@ -90,22 +106,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
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>
|
||||
/// Cannot determine body for {0}. '{2}' must be null if '{1}' is null.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -118,10 +118,13 @@
|
|||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<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 name="AnchorTagHelper_CannotOverrideSpecifiedHref" 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>
|
||||
<data name="AnchorTagHelper_CannotOverrideHref" xml:space="preserve">
|
||||
<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 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>
|
||||
|
|
@ -132,9 +135,6 @@
|
|||
<data name="InputTagHelper_ValueRequired" xml:space="preserve">
|
||||
<value>'{1}' must not be null for {0} if '{2}' is '{3}'.</value>
|
||||
</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">
|
||||
<value>Cannot determine body for {0}. '{2}' must be null if '{1}' is null.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <select> elements.
|
||||
/// <see cref="ITagHelper"/> implementation targeting <select> elements with an <c>asp-for</c> attribute.
|
||||
/// </summary>
|
||||
[ContentBehavior(ContentBehavior.Append)]
|
||||
public class SelectTagHelper : TagHelper
|
||||
{
|
||||
private const string ForAttributeName = "asp-for";
|
||||
private const string ItemsAttributeName = "asp-items";
|
||||
|
||||
/// <summary>
|
||||
/// Key used for selected values in <see cref="FormContext.FormData"/>.
|
||||
/// </summary>
|
||||
|
|
@ -39,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// An expression to be evaluated against the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,21 +59,26 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// A collection of <see cref="SelectListItem"/> objects used to populate the <select> element with
|
||||
/// <optgroup> and <option> elements.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ItemsAttributeName)]
|
||||
public IEnumerable<SelectListItem> Items { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <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)
|
||||
{
|
||||
if (For == null)
|
||||
{
|
||||
// Regular HTML <select/> element. Just make sure Items wasn't specified.
|
||||
// Reviewers: Should this instead ignore the unused helper-specific attribute?
|
||||
if (Items != null)
|
||||
{
|
||||
var message = Resources.FormatSelectTagHelper_CannotDetermineContentWhenOnlyItemsSpecified(
|
||||
"<select>",
|
||||
nameof(For).ToLowerInvariant(),
|
||||
nameof(Items).ToLowerInvariant());
|
||||
ForAttributeName,
|
||||
ItemsAttributeName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata(
|
||||
"<select>",
|
||||
nameof(For).ToLowerInvariant(),
|
||||
ForAttributeName,
|
||||
nameof(IModelMetadataProvider),
|
||||
For.Name));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ITagHelper"/> implementation targeting <textarea> elements.
|
||||
/// <see cref="ITagHelper"/> implementation targeting <textarea> elements with an <c>asp-for</c> attribute.
|
||||
/// </summary>
|
||||
[ContentBehavior(ContentBehavior.Replace)]
|
||||
public class TextAreaTagHelper : TagHelper
|
||||
{
|
||||
private const string ForAttributeName = "asp-for";
|
||||
|
||||
// Protected to ensure subclasses are correctly activated. Internal for ease of use when testing.
|
||||
[Activate]
|
||||
protected internal IHtmlGenerator Generator { get; set; }
|
||||
|
|
@ -24,10 +26,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// An expression to be evaluated against the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (For != null)
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <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>
|
||||
[TagName("span")]
|
||||
[ContentBehavior(ContentBehavior.Modify)]
|
||||
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.
|
||||
[Activate]
|
||||
protected internal ViewContext ViewContext { get; set; }
|
||||
|
|
@ -25,10 +28,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
/// <summary>
|
||||
/// Name to be validated on the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName("validation-for")]
|
||||
[HtmlAttributeName(ValidationForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
if (For != null)
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ using Microsoft.AspNet.Razor.TagHelpers;
|
|||
namespace Microsoft.AspNet.Mvc.TagHelpers
|
||||
{
|
||||
/// <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.
|
||||
/// </summary>
|
||||
[TagName("div")]
|
||||
[ContentBehavior(ContentBehavior.Append)]
|
||||
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.
|
||||
[Activate]
|
||||
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
|
||||
/// <see cref="ValidationSummary"/> enum.
|
||||
/// </summary>
|
||||
[HtmlAttributeName("validation-summary")]
|
||||
[HtmlAttributeName(ValidationSummaryAttributeName)]
|
||||
public string ValidationSummaryValue { get; set; }
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ValidationSummaryValue))
|
||||
|
|
@ -44,7 +49,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
throw new InvalidOperationException(
|
||||
Resources.FormatTagHelpers_InvalidValue_ThreeAcceptableValues(
|
||||
"<div>",
|
||||
"validation-summary",
|
||||
ValidationSummaryAttributeName,
|
||||
ValidationSummaryValue,
|
||||
ValidationSummary.All,
|
||||
ValidationSummary.ModelOnly,
|
||||
|
|
|
|||
|
|
@ -20,32 +20,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// Arrange
|
||||
var expectedTagName = "not-a";
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Action = "index",
|
||||
Controller = "home",
|
||||
Fragment = "hello=world",
|
||||
Host = "contoso.com",
|
||||
Protocol = "http"
|
||||
};
|
||||
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>
|
||||
{
|
||||
{ "id", "myanchor" },
|
||||
{ "route-foo", "bar" },
|
||||
{ "action", "index" },
|
||||
{ "controller", "home" },
|
||||
{ "fragment", "hello=world" },
|
||||
{ "host", "contoso.com" },
|
||||
{ "protocol", "http" }
|
||||
{ "asp-route-foo", "bar" },
|
||||
{ "asp-action", "index" },
|
||||
{ "asp-controller", "home" },
|
||||
{ "asp-fragment", "hello=world" },
|
||||
{ "asp-host", "contoso.com" },
|
||||
{ "asp-protocol", "http" }
|
||||
});
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "id", "myanchor" },
|
||||
{ "route-foo", "bar" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
},
|
||||
content: "Something");
|
||||
|
||||
|
|
@ -64,7 +56,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var viewContext = TestableHtmlGenerator.GetViewContext(model: null,
|
||||
htmlGenerator: htmlGenerator,
|
||||
metadataProvider: metadataProvider);
|
||||
anchorTagHelper.Generator = htmlGenerator;
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Action = "index",
|
||||
Controller = "home",
|
||||
Fragment = "hello=world",
|
||||
Generator = htmlGenerator,
|
||||
Host = "contoso.com",
|
||||
Protocol = "http",
|
||||
};
|
||||
|
||||
// Act
|
||||
await anchorTagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
|
@ -83,13 +83,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public async Task ProcessAsync_CallsIntoRouteLinkWithExpectedParameters()
|
||||
{
|
||||
// Arrange
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Route = "Default",
|
||||
Protocol = "http",
|
||||
Host = "contoso.com",
|
||||
Fragment = "hello=world"
|
||||
};
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>());
|
||||
var output = new TagHelperOutput(
|
||||
|
|
@ -103,7 +96,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
string.Empty, "Default", "http", "contoso.com", "hello=world", null, null))
|
||||
.Returns(new TagBuilder("a"))
|
||||
.Verifiable();
|
||||
anchorTagHelper.Generator = generator.Object;
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Fragment = "hello=world",
|
||||
Generator = generator.Object,
|
||||
Host = "contoso.com",
|
||||
Protocol = "http",
|
||||
Route = "Default",
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await anchorTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -117,14 +117,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
public async Task ProcessAsync_CallsIntoActionLinkWithExpectedParameters()
|
||||
{
|
||||
// Arrange
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
Controller = "Home",
|
||||
Protocol = "http",
|
||||
Host = "contoso.com",
|
||||
Fragment = "hello=world"
|
||||
};
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>());
|
||||
var output = new TagHelperOutput(
|
||||
|
|
@ -138,7 +130,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
string.Empty, "Index", "Home", "http", "contoso.com", "hello=world", null, null))
|
||||
.Returns(new TagBuilder("a"))
|
||||
.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
|
||||
await anchorTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[InlineData("Protocol")]
|
||||
[InlineData("Host")]
|
||||
[InlineData("Fragment")]
|
||||
[InlineData("route-")]
|
||||
[InlineData("asp-route-")]
|
||||
public async Task ProcessAsync_ThrowsIfHrefConflictsWithBoundAttributes(string propertyName)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -167,19 +167,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{ "href", "http://www.contoso.com" }
|
||||
},
|
||||
content: string.Empty);
|
||||
|
||||
if (propertyName == "route-")
|
||||
if (propertyName == "asp-route-")
|
||||
{
|
||||
output.Attributes.Add("route-foo", "bar");
|
||||
output.Attributes.Add("asp-route-foo", "bar");
|
||||
}
|
||||
else
|
||||
{
|
||||
typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home");
|
||||
}
|
||||
|
||||
var expectedErrorMessage = "Cannot determine an 'href' for <a>. An <a> with a specified 'href' must not " +
|
||||
"have attributes starting with 'route-' or an 'action', 'controller', " +
|
||||
"'route', 'protocol', 'host', or 'fragment' attribute.";
|
||||
var expectedErrorMessage = "Cannot override the 'href' attribute for <a>. An <a> with a specified " +
|
||||
"'href' must not have attributes starting with 'asp-route-' or an " +
|
||||
"'asp-action', 'asp-controller', 'asp-route', 'asp-protocol', 'asp-host', or " +
|
||||
"'asp-fragment' attribute.";
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
|
|
@ -196,15 +196,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// Arrange
|
||||
var anchorTagHelper = new AnchorTagHelper
|
||||
{
|
||||
Route = "Default"
|
||||
Route = "Default",
|
||||
};
|
||||
typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home");
|
||||
var output = new TagHelperOutput(
|
||||
"a",
|
||||
attributes: new Dictionary<string, string>(),
|
||||
content: string.Empty);
|
||||
var expectedErrorMessage = "Cannot determine an 'href' for <a>. An <a> with a " +
|
||||
"specified 'route' must not have an 'action' or 'controller' attribute.";
|
||||
var expectedErrorMessage = "Cannot determine an 'href' attribute for <a>. An <a> with a specified " +
|
||||
"'asp-route' must not have an 'asp-action' or 'asp-controller' attribute.";
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
|
|
|
|||
|
|
@ -23,29 +23,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// Arrange
|
||||
var expectedTagName = "not-form";
|
||||
var metadataProvider = new DataAnnotationsModelMetadataProvider();
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "index",
|
||||
Controller = "home",
|
||||
Method = "post",
|
||||
AntiForgery = true
|
||||
};
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>
|
||||
{
|
||||
{ "id", "myform" },
|
||||
{ "route-foo", "bar" },
|
||||
{ "action", "index" },
|
||||
{ "controller", "home" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
{ "asp-action", "index" },
|
||||
{ "asp-controller", "home" },
|
||||
{ "method", "post" },
|
||||
{ "anti-forgery", true }
|
||||
{ "asp-anti-forgery", true }
|
||||
});
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "id", "myform" },
|
||||
{ "route-foo", "bar" },
|
||||
{ "asp-route-foo", "bar" },
|
||||
},
|
||||
content: "Something");
|
||||
var urlHelper = new Mock<IUrlHelper>();
|
||||
|
|
@ -64,8 +57,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
metadataProvider: metadataProvider);
|
||||
var expectedContent = "Something" + htmlGenerator.GenerateAntiForgery(viewContext)
|
||||
.ToString(TagRenderMode.SelfClosing);
|
||||
formTagHelper.ViewContext = viewContext;
|
||||
formTagHelper.Generator = htmlGenerator;
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "index",
|
||||
AntiForgery = true,
|
||||
Controller = "home",
|
||||
Generator = htmlGenerator,
|
||||
Method = "post",
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
// Act
|
||||
await formTagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
|
@ -90,11 +90,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
AntiForgery = antiForgery
|
||||
};
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>());
|
||||
var output = new TagHelperOutput(
|
||||
|
|
@ -114,8 +109,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
|
||||
generator.Setup(mock => mock.GenerateAntiForgery(viewContext))
|
||||
.Returns(new TagBuilder("input"));
|
||||
formTagHelper.ViewContext = viewContext;
|
||||
formTagHelper.Generator = generator.Object;
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
AntiForgery = antiForgery,
|
||||
Generator = generator.Object,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
// Act
|
||||
await formTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -131,20 +131,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
// Arrange
|
||||
var testViewContext = CreateViewContext();
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
AntiForgery = false
|
||||
};
|
||||
var context = new TagHelperContext(
|
||||
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(
|
||||
"form",
|
||||
attributes: new Dictionary<string, string>()
|
||||
{
|
||||
{ "route-val", "hello" },
|
||||
{ "roUte--Foo", "bar" }
|
||||
{ "asp-route-val", "hello" },
|
||||
{ "asp-roUte--Foo", "bar" }
|
||||
},
|
||||
content: string.Empty);
|
||||
output.Attributes.Add(expectedAttribute);
|
||||
|
|
@ -174,8 +169,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
})
|
||||
.Returns(new TagBuilder("form"))
|
||||
.Verifiable();
|
||||
formTagHelper.ViewContext = testViewContext;
|
||||
formTagHelper.Generator = generator.Object;
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
AntiForgery = false,
|
||||
Generator = generator.Object,
|
||||
ViewContext = testViewContext,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await formTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -192,13 +192,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
Controller = "Home",
|
||||
Method = "POST",
|
||||
AntiForgery = false
|
||||
};
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>());
|
||||
var output = new TagHelperOutput(
|
||||
|
|
@ -210,8 +203,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, "POST", null))
|
||||
.Returns(new TagBuilder("form"))
|
||||
.Verifiable();
|
||||
formTagHelper.ViewContext = viewContext;
|
||||
formTagHelper.Generator = generator.Object;
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "Index",
|
||||
AntiForgery = false,
|
||||
Controller = "Home",
|
||||
Generator = generator.Object,
|
||||
Method = "POST",
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
await formTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -222,22 +222,26 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Assert.Empty(output.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_RestoresBoundAttributesIfActionIsURL()
|
||||
[Theory]
|
||||
[InlineData("my-action")]
|
||||
[InlineData("http://www.contoso.com")]
|
||||
[InlineData("my/action")]
|
||||
public async Task ProcessAsync_RestoresBoundAttributesIfActionIsSpecified(string htmlAction)
|
||||
{
|
||||
// Arrange
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "http://www.contoso.com",
|
||||
Method = "POST"
|
||||
};
|
||||
var output = new TagHelperOutput("form",
|
||||
attributes: new Dictionary<string, string>(),
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "aCTiON", htmlAction },
|
||||
},
|
||||
content: string.Empty);
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>()
|
||||
{
|
||||
{ "aCTiON", "http://www.contoso.com" },
|
||||
{ "METhod", "POST" }
|
||||
});
|
||||
|
||||
|
|
@ -248,7 +252,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
Assert.Equal("form", output.TagName);
|
||||
Assert.Equal(2, output.Attributes.Count);
|
||||
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"));
|
||||
Assert.Equal("POST", attribute.Value);
|
||||
Assert.Empty(output.Content);
|
||||
|
|
@ -258,7 +262,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
[InlineData(true, "<input />")]
|
||||
[InlineData(false, "")]
|
||||
[InlineData(null, "")]
|
||||
public async Task ProcessAsync_SupportsAntiForgeryIfActionIsURL(bool? antiForgery, string expectedContent)
|
||||
public async Task ProcessAsync_SupportsAntiForgeryIfActionIsSpecified(
|
||||
bool? antiForgery,
|
||||
string expectedContent)
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
|
|
@ -267,20 +273,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
.Returns(new TagBuilder("input"));
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "http://www.contoso.com",
|
||||
AntiForgery = antiForgery,
|
||||
Generator = generator.Object,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
formTagHelper.ViewContext = viewContext;
|
||||
formTagHelper.Generator = generator.Object;
|
||||
|
||||
var output = new TagHelperOutput("form",
|
||||
attributes: new Dictionary<string, string>(),
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "aCTiON", "my-action" },
|
||||
},
|
||||
content: string.Empty);
|
||||
var context = new TagHelperContext(
|
||||
allAttributes: new Dictionary<string, object>()
|
||||
{
|
||||
{ "aCTiON", "http://www.contoso.com" }
|
||||
});
|
||||
var context = new TagHelperContext(allAttributes: new Dictionary<string, object>());
|
||||
|
||||
// Act
|
||||
await formTagHelper.ProcessAsync(context, output);
|
||||
|
|
@ -288,52 +292,40 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
// Assert
|
||||
Assert.Equal("form", output.TagName);
|
||||
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);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_ThrowsIfActionIsUrlWithSpecifiedController()
|
||||
[Theory]
|
||||
[InlineData("Action")]
|
||||
[InlineData("Controller")]
|
||||
[InlineData("asp-route-")]
|
||||
public async Task ProcessAsync_ThrowsIfActionConflictsWithBoundAttributes(string propertyName)
|
||||
{
|
||||
// Arrange
|
||||
var formTagHelper = new FormTagHelper
|
||||
{
|
||||
Action = "http://www.contoso.com",
|
||||
Controller = "Home",
|
||||
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(
|
||||
"form",
|
||||
attributes: new Dictionary<string, string>
|
||||
{
|
||||
{ "route-foo", "bar" }
|
||||
{ "action", "my-action" },
|
||||
},
|
||||
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
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
|
|
|
|||
|
|
@ -552,6 +552,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
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(
|
||||
IHtmlGenerator htmlGenerator,
|
||||
object container,
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
var originalAttributes = new Dictionary<string, string>();
|
||||
var content = "original content";
|
||||
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 output = new TagHelperOutput(expectedTagName, originalAttributes, content);
|
||||
|
|
|
|||
|
|
@ -253,8 +253,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
|
|||
attributes: new Dictionary<string, string>(),
|
||||
content: "Content of validation message");
|
||||
var expectedViewContext = CreateViewContext();
|
||||
var expectedMessage = "Cannot parse 'validation-summary' value 'Hello World' for <div>. Acceptable values " +
|
||||
"are 'All', 'ModelOnly' and 'None'.";
|
||||
var expectedMessage = "Cannot parse 'asp-validation-summary' value 'Hello World' for <div>. Acceptable " +
|
||||
"values are 'All', 'ModelOnly' and 'None'.";
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
|
|
|
|||
Loading…
Reference in New Issue