/// <inheritdoc /> /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks> public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (For != null) { var tagBuilder = Generator.GenerateValidationMessage(ViewContext, For.Name, message: null, tag: null, htmlAttributes: null); if (tagBuilder != null) { output.MergeAttributes(tagBuilder); // We check for whitespace to detect scenarios such as: // <span validation-for="Name"> // </span> if (!output.IsContentModified) { var childContent = await context.GetChildContentAsync(); if (childContent.IsWhiteSpace) { // Provide default label text since there was nothing useful in the Razor source. output.Content.SetContent(tagBuilder.InnerHtml); } else { output.Content.SetContent(childContent); } } } } }
/// <inheritdoc /> /// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks> public override void Process(TagHelperContext context, TagHelperOutput output) { var tagBuilder = Generator.GenerateTextArea( ViewContext, For.ModelExplorer, For.Name, rows: 0, columns: 0, htmlAttributes: null); if (tagBuilder != null) { // Overwrite current Content to ensure expression result round-trips correctly. output.Content.SetContent(tagBuilder.InnerHtml); output.MergeAttributes(tagBuilder); } }
public override void Process(TagHelperContext context, TagHelperOutput output) { var tagBuilder = Generator.GenerateSelect( ViewContext, null, optionLabel: AspOptionLabel, expression: AspExpression, selectList: AspItems, currentValues: new string[] { AspCurrent, }, allowMultiple: false, htmlAttributes: null); tagBuilder.AddCssClass( context.AllAttributes.ContainsName("class") == true ? context.AllAttributes["class"].Value.ToString() : string.Empty); if (tagBuilder != null) { output.MergeAttributes(tagBuilder); output.PostContent.Append(tagBuilder.InnerHtml); } }
/// <inheritdoc /> /// <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 "href" is already set, it means the user is attempting to use a normal anchor. if (output.Attributes.ContainsKey(Href)) { if (Action != null || Controller != null || Route != null || Protocol != null || Host != null || Fragment != null || routePrefixedAttributes.Any()) { // User specified an href and one of the bound attributes; can't determine the href attribute. throw new InvalidOperationException( Resources.FormatAnchorTagHelper_CannotOverrideHref( "<a>", ActionAttributeName, ControllerAttributeName, RouteAttributeName, ProtocolAttributeName, HostAttributeName, FragmentAttributeName, RouteAttributePrefix, Href)); } } else { TagBuilder tagBuilder; var routeValues = GetRouteValues(output, routePrefixedAttributes); if (Route == null) { tagBuilder = Generator.GenerateActionLink(linkText: string.Empty, actionName: Action, controllerName: Controller, protocol: Protocol, hostname: Host, fragment: Fragment, routeValues: routeValues, htmlAttributes: null); } else if (Action != null || Controller != null) { // Route and Action or Controller were specified. Can't determine the href attribute. throw new InvalidOperationException( Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified( "<a>", RouteAttributeName, ActionAttributeName, ControllerAttributeName, Href)); } else { tagBuilder = Generator.GenerateRouteLink(linkText: string.Empty, routeName: Route, protocol: Protocol, hostName: Host, fragment: Fragment, routeValues: routeValues, htmlAttributes: null); } if (tagBuilder != null) { output.MergeAttributes(tagBuilder); } } }
/// <inheritdoc /> /// <remarks>Does nothing if <see cref="ValidationSummary"/> is <see cref="ValidationSummary.None"/>.</remarks> public override void Process(TagHelperContext context, TagHelperOutput output) { if (ValidationSummary == ValidationSummary.None) { return; } var tagBuilder = Generator.GenerateValidationSummary( ViewContext, excludePropertyErrors: ValidationSummary == ValidationSummary.ModelOnly, message: null, headerTag: null, htmlAttributes: null); if (tagBuilder != null) { output.MergeAttributes(tagBuilder); output.PostContent.Append(tagBuilder.InnerHtml); } }
/// <inheritdoc /> /// <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 // from a TagBuilder. if (InputTypeName != null) { output.CopyHtmlAttribute("type", context); } if (Value != null) { output.CopyHtmlAttribute(nameof(Value), context); } // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient. // IHtmlGenerator will enforce name requirements. var metadata = For.Metadata; var modelExplorer = For.ModelExplorer; if (metadata == null) { throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( "<input>", ForAttributeName, nameof(IModelMetadataProvider), For.Name)); } string inputType; string inputTypeHint; if (string.IsNullOrEmpty(InputTypeName)) { // Note GetInputType never returns null. inputType = GetInputType(modelExplorer, out inputTypeHint); } else { inputType = InputTypeName.ToLowerInvariant(); inputTypeHint = null; } // inputType may be more specific than default the generator chooses below. if (!output.Attributes.ContainsName("type")) { output.Attributes["type"] = inputType; } TagBuilder tagBuilder; switch (inputType) { case "checkbox": GenerateCheckBox(modelExplorer, output); return; case "hidden": tagBuilder = Generator.GenerateHidden( ViewContext, modelExplorer, For.Name, value: For.Model, useViewData: false, htmlAttributes: null); break; case "password": tagBuilder = Generator.GeneratePassword( ViewContext, modelExplorer, For.Name, value: null, htmlAttributes: null); break; case "radio": tagBuilder = GenerateRadio(modelExplorer); break; default: tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType); break; } if (tagBuilder != null) { // This TagBuilder contains the one <input/> element of interest. Since this is not the "checkbox" // special-case, output is a self-closing element no longer guaranteed. output.MergeAttributes(tagBuilder); output.Content.Append(tagBuilder.InnerHtml); } }
/// <inheritdoc /> /// <remarks> /// Does nothing if user provides an <c>action</c> attribute and <see cref="Antiforgery"/> is <c>null</c> or /// <c>false</c>. /// </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) { var antiforgeryDefault = true; // If "action" is already set, it means the user is attempting to use a normal <form>. if (output.Attributes.ContainsName(HtmlActionAttributeName)) { if (Action != null || Controller != null || Route != null || RouteValues.Count != 0) { // User also specified bound attributes we cannot use. throw new InvalidOperationException( Resources.FormatFormTagHelper_CannotOverrideAction( "<form>", HtmlActionAttributeName, ActionAttributeName, ControllerAttributeName, RouteAttributeName, RouteValuesPrefix)); } // User is using the FormTagHelper like a normal <form> tag. Antiforgery default should be false to // not force the antiforgery token on the user. antiforgeryDefault = false; } else { // Convert from Dictionary<string, string> to Dictionary<string, object>. var routeValues = RouteValues.ToDictionary( kvp => kvp.Key, kvp => (object)kvp.Value, StringComparer.OrdinalIgnoreCase); TagBuilder tagBuilder; if (Route == null) { tagBuilder = Generator.GenerateForm( ViewContext, Action, Controller, routeValues, method: null, htmlAttributes: null); } else if (Action != null || Controller != null) { // Route and Action or Controller were specified. Can't determine the action attribute. throw new InvalidOperationException( Resources.FormatFormTagHelper_CannotDetermineActionWithRouteAndActionOrControllerSpecified( "<form>", RouteAttributeName, ActionAttributeName, ControllerAttributeName, HtmlActionAttributeName)); } else { tagBuilder = Generator.GenerateRouteForm( ViewContext, Route, routeValues, method: null, htmlAttributes: null); } if (tagBuilder != null) { output.MergeAttributes(tagBuilder); output.PostContent.Append(tagBuilder.InnerHtml); } } if (Antiforgery ?? antiforgeryDefault) { var antiforgeryTag = Generator.GenerateAntiforgery(ViewContext); if (antiforgeryTag != null) { output.PostContent.Append(antiforgeryTag.ToString()); } } }
/// <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) { // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient. // IHtmlGenerator will enforce name requirements. var metadata = For.Metadata; if (metadata == null) { throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( "<select>", ForAttributeName, nameof(IModelMetadataProvider), For.Name)); } // Base allowMultiple on the instance or declared type of the expression to avoid a // "SelectExpressionNotEnumerable" InvalidOperationException during generation. // Metadata.IsEnumerableType is similar but does not take runtime type into account. var realModelType = For.ModelExplorer.ModelType; var allowMultiple = typeof(string) != realModelType && typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(realModelType.GetTypeInfo()); // Ensure GenerateSelect() _never_ looks anything up in ViewData. var items = Items ?? Enumerable.Empty<SelectListItem>(); var currentValues = Generator.GetCurrentValues( ViewContext, For.ModelExplorer, expression: For.Name, allowMultiple: allowMultiple); var tagBuilder = Generator.GenerateSelect( ViewContext, For.ModelExplorer, optionLabel: null, expression: For.Name, selectList: items, currentValues: currentValues, allowMultiple: allowMultiple, htmlAttributes: null); if (tagBuilder != null) { output.MergeAttributes(tagBuilder); output.PostContent.Append(tagBuilder.InnerHtml); } // Whether or not (not being highly unlikely) we generate anything, could update contained <option/> // elements. Provide selected values for <option/> tag helpers. They'll run next. ViewContext.FormContext.FormData[SelectedValuesFormDataKey] = currentValues; }
/// <inheritdoc /> /// <remarks> /// Does nothing if user provides an <c>action</c> attribute and <see cref="AntiForgery"/> is <c>null</c> or /// <c>false</c>. /// </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) { var antiForgeryDefault = true; var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); // If "action" is already set, it means the user is attempting to use a normal <form>. if (output.Attributes.ContainsKey(HtmlActionAttributeName)) { if (Action != null || Controller != null || routePrefixedAttributes.Any()) { // User also specified bound attributes we cannot use. throw new InvalidOperationException( Resources.FormatFormTagHelper_CannotOverrideAction( "<form>", 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 on the user. antiForgeryDefault = false; } else { var routeValues = GetRouteValues(output, routePrefixedAttributes); var tagBuilder = Generator.GenerateForm(ViewContext, Action, Controller, routeValues, method: null, htmlAttributes: null); if (tagBuilder != null) { output.MergeAttributes(tagBuilder); output.PostContent.Append(tagBuilder.InnerHtml); } } if (AntiForgery ?? antiForgeryDefault) { var antiForgeryTagBuilder = Generator.GenerateAntiForgery(ViewContext); if (antiForgeryTagBuilder != null) { output.PostContent.Append(antiForgeryTagBuilder.ToString(TagRenderMode.SelfClosing)); } } }
/// <inheritdoc /> /// <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) { // If "href" is already set, it means the user is attempting to use a normal anchor. if (output.Attributes.ContainsName(Href)) { if (Action != null || Controller != null || Route != null || Protocol != null || Host != null || Fragment != null || RouteValues.Count != 0) { // User specified an href and one of the bound attributes; can't determine the href attribute. throw new InvalidOperationException( Resources.FormatAnchorTagHelper_CannotOverrideHref( "<a>", ActionAttributeName, ControllerAttributeName, RouteAttributeName, ProtocolAttributeName, HostAttributeName, FragmentAttributeName, RouteValuesPrefix, Href)); } } else { // Convert from Dictionary<string, string> to Dictionary<string, object>. var routeValues = RouteValues.ToDictionary( kvp => kvp.Key, kvp => (object)kvp.Value, StringComparer.OrdinalIgnoreCase); TagBuilder tagBuilder; if (Route == null) { tagBuilder = Generator.GenerateActionLink(linkText: string.Empty, actionName: Action, controllerName: Controller, protocol: Protocol, hostname: Host, fragment: Fragment, routeValues: routeValues, htmlAttributes: null); } else if (Action != null || Controller != null) { // Route and Action or Controller were specified. Can't determine the href attribute. throw new InvalidOperationException( Resources.FormatAnchorTagHelper_CannotDetermineHrefRouteActionOrControllerSpecified( "<a>", RouteAttributeName, ActionAttributeName, ControllerAttributeName, Href)); } else { tagBuilder = Generator.GenerateRouteLink(linkText: string.Empty, routeName: Route, protocol: Protocol, hostName: Host, fragment: Fragment, routeValues: routeValues, htmlAttributes: null); } if (tagBuilder != null) { output.MergeAttributes(tagBuilder); } } }
public void MergeAttributes_Combines_TagHelperOutputAttributeValues() { // Arrange var tagHelperOutput = new TagHelperOutput( "p", attributes: new Dictionary<string, object>()); var expectedOutputAttribute = new KeyValuePair<string, object>("class", "btn"); tagHelperOutput.Attributes.Add(expectedOutputAttribute); var tagBuilder = new TagBuilder("p", new HtmlEncoder()); var expectedBuilderAttribute = new KeyValuePair<string, object>("for", "hello"); tagBuilder.Attributes.Add("for", "hello"); // Act tagHelperOutput.MergeAttributes(tagBuilder); // Assert Assert.Equal(tagHelperOutput.Attributes.Count, 2); var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class")); Assert.Equal(expectedOutputAttribute.Value, attribute.Value); attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("for")); Assert.Equal(expectedBuilderAttribute.Value, attribute.Value); }
public void MergeAttributes_Maintains_TagHelperOutputAttributeValues() { // Arrange var tagHelperOutput = new TagHelperOutput( "p", attributes: new Dictionary<string, object>()); var expectedAttribute = new KeyValuePair<string, object>("class", "btn"); tagHelperOutput.Attributes.Add(expectedAttribute); var tagBuilder = new TagBuilder("p", new HtmlEncoder()); // Act tagHelperOutput.MergeAttributes(tagBuilder); // Assert var attribute = Assert.Single(tagHelperOutput.Attributes); Assert.Equal(expectedAttribute, attribute); }
public void MergeAttributes_CopiesMultiple_TagHelperOutputAttributeValues() { // Arrange var tagHelperOutput = new TagHelperOutput( "p", attributes: new Dictionary<string, object>()); var tagBuilder = new TagBuilder("p", new HtmlEncoder()); var expectedAttribute1 = new KeyValuePair<string, object>("class", "btn"); var expectedAttribute2 = new KeyValuePair<string, object>("class2", "btn"); tagBuilder.Attributes.Add("class", "btn"); tagBuilder.Attributes.Add("class2", "btn"); // Act tagHelperOutput.MergeAttributes(tagBuilder); // Assert Assert.Equal(2, tagHelperOutput.Attributes.Count); var attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class")); Assert.Equal(expectedAttribute1.Value, attribute.Value); attribute = Assert.Single(tagHelperOutput.Attributes, kvp => kvp.Key.Equals("class2")); Assert.Equal(expectedAttribute2.Value, attribute.Value); }
public void MergeAttributes_AppendsClass_TagHelperOutputAttributeValues_IgnoresCase( string originalName, string updateName) { // Arrange var tagHelperOutput = new TagHelperOutput( "p", attributes: new Dictionary<string, object>()); tagHelperOutput.Attributes.Add(originalName, "Hello"); var tagBuilder = new TagBuilder("p", new HtmlEncoder()); tagBuilder.Attributes.Add(updateName, "btn"); // Act tagHelperOutput.MergeAttributes(tagBuilder); // Assert var attribute = Assert.Single(tagHelperOutput.Attributes); Assert.Equal(new KeyValuePair<string, object>(originalName, "Hello btn"), attribute); }