Allow `null` or empty `fullName` in one special case
- #6662 - users can now provide a `name` or `data-valmsg-for` attribute to avoid `ArgumentException`s - affects `<input>`, `<select>`, `<textarea>` elements and validation message `<div>`s - remove `fullName` check in `DefaultHtmlGenerator.GetCurrentValues(...)` entirely The new workaround is _not_ identical to changing `ViewData.TemplateInfo.HtmlFieldPrefix` - does not change where expression values are found in `ModelState` or `ViewData` - likely needs to be combined with additional workarounds i.e. for advanced use only nits: - clean up some excessive argument naming; add a few missing argument names - take VS suggestions in changed classes e.g. inline a few variable declarations - clean up some test data
This commit is contained in:
parent
75e3ed952b
commit
ecedbd5372
|
|
@ -113,6 +113,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[HtmlAttributeName("type")]
|
||||
public string InputTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <input> element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed through to the generated HTML in all cases. Also used to determine whether <see cref="For"/> is
|
||||
/// valid with an empty <see cref="ModelExpression.Name"/>.
|
||||
/// </remarks>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the <input> element.
|
||||
/// </summary>
|
||||
|
|
@ -146,6 +155,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
output.CopyHtmlAttribute("type", context);
|
||||
}
|
||||
|
||||
if (Name != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(nameof(Name), context);
|
||||
}
|
||||
|
||||
if (Value != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(nameof(Value), context);
|
||||
|
|
@ -183,15 +197,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
output.Attributes.SetAttribute("type", inputType);
|
||||
}
|
||||
|
||||
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
|
||||
IDictionary<string, object> htmlAttributes = null;
|
||||
if (string.IsNullOrEmpty(For.Name) &&
|
||||
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
|
||||
!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "name", Name },
|
||||
};
|
||||
}
|
||||
|
||||
TagBuilder tagBuilder;
|
||||
switch (inputType)
|
||||
{
|
||||
case "hidden":
|
||||
tagBuilder = GenerateHidden(modelExplorer);
|
||||
tagBuilder = GenerateHidden(modelExplorer, htmlAttributes);
|
||||
break;
|
||||
|
||||
case "checkbox":
|
||||
tagBuilder = GenerateCheckBox(modelExplorer, output);
|
||||
tagBuilder = GenerateCheckBox(modelExplorer, output, htmlAttributes);
|
||||
break;
|
||||
|
||||
case "password":
|
||||
|
|
@ -200,15 +226,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
modelExplorer,
|
||||
For.Name,
|
||||
value: null,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
break;
|
||||
|
||||
case "radio":
|
||||
tagBuilder = GenerateRadio(modelExplorer);
|
||||
tagBuilder = GenerateRadio(modelExplorer, htmlAttributes);
|
||||
break;
|
||||
|
||||
default:
|
||||
tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType);
|
||||
tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType, htmlAttributes);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +274,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
return inputTypeHint;
|
||||
}
|
||||
|
||||
private TagBuilder GenerateCheckBox(ModelExplorer modelExplorer, TagHelperOutput output)
|
||||
private TagBuilder GenerateCheckBox(
|
||||
ModelExplorer modelExplorer,
|
||||
TagHelperOutput output,
|
||||
IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
if (modelExplorer.ModelType == typeof(string))
|
||||
{
|
||||
|
|
@ -282,6 +311,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
var renderingMode =
|
||||
output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag;
|
||||
hiddenForCheckboxTag.TagRenderMode = renderingMode;
|
||||
if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") &&
|
||||
!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
// The checkbox and hidden elements should have the same name attribute value. Attributes will
|
||||
// match if both are present because both have a generated value. Reach here in the special case
|
||||
// where user provided a non-empty fallback name.
|
||||
hiddenForCheckboxTag.MergeAttribute("name", Name);
|
||||
}
|
||||
|
||||
if (ViewContext.FormContext.CanRenderAtEndOfForm)
|
||||
{
|
||||
|
|
@ -298,10 +335,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
modelExplorer,
|
||||
For.Name,
|
||||
isChecked: null,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
private TagBuilder GenerateRadio(ModelExplorer modelExplorer)
|
||||
private TagBuilder GenerateRadio(ModelExplorer modelExplorer, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
// Note empty string is allowed.
|
||||
if (Value == null)
|
||||
|
|
@ -319,10 +356,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
For.Name,
|
||||
Value,
|
||||
isChecked: null,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
private TagBuilder GenerateTextBox(ModelExplorer modelExplorer, string inputTypeHint, string inputType)
|
||||
private TagBuilder GenerateTextBox(
|
||||
ModelExplorer modelExplorer,
|
||||
string inputTypeHint,
|
||||
string inputType,
|
||||
IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
var format = Format;
|
||||
if (string.IsNullOrEmpty(format))
|
||||
|
|
@ -338,12 +379,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
format = GetFormat(modelExplorer, inputTypeHint, inputType);
|
||||
}
|
||||
}
|
||||
var htmlAttributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "type", inputType }
|
||||
};
|
||||
|
||||
if (string.Equals(inputType, "file") && string.Equals(inputTypeHint, TemplateRenderer.IEnumerableOfIFormFileName))
|
||||
if (htmlAttributes == null)
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
htmlAttributes["type"] = inputType;
|
||||
if (string.Equals(inputType, "file") &&
|
||||
string.Equals(
|
||||
inputTypeHint,
|
||||
TemplateRenderer.IEnumerableOfIFormFileName,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
htmlAttributes["multiple"] = "multiple";
|
||||
}
|
||||
|
|
@ -352,14 +399,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
ViewContext,
|
||||
modelExplorer,
|
||||
For.Name,
|
||||
value: modelExplorer.Model,
|
||||
format: format,
|
||||
htmlAttributes: htmlAttributes);
|
||||
modelExplorer.Model,
|
||||
format,
|
||||
htmlAttributes);
|
||||
}
|
||||
|
||||
// Imitate Generator.GenerateHidden() using Generator.GenerateTextBox(). This adds support for asp-format that
|
||||
// is not available in Generator.GenerateHidden().
|
||||
private TagBuilder GenerateHidden(ModelExplorer modelExplorer)
|
||||
private TagBuilder GenerateHidden(ModelExplorer modelExplorer, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
var value = For.Model;
|
||||
if (value is byte[] byteArrayValue)
|
||||
|
|
@ -367,21 +414,17 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
value = Convert.ToBase64String(byteArrayValue);
|
||||
}
|
||||
|
||||
if (htmlAttributes == null)
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// In DefaultHtmlGenerator(), GenerateTextBox() calls GenerateInput() _almost_ identically to how
|
||||
// GenerateHidden() does and the main switch inside GenerateInput() handles InputType.Text and
|
||||
// InputType.Hidden identically. No behavior differences at all when a type HTML attribute already exists.
|
||||
var htmlAttributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "type", "hidden" }
|
||||
};
|
||||
htmlAttributes["type"] = "hidden";
|
||||
|
||||
return Generator.GenerateTextBox(
|
||||
ViewContext,
|
||||
modelExplorer,
|
||||
For.Name,
|
||||
value: value,
|
||||
format: Format,
|
||||
htmlAttributes: htmlAttributes);
|
||||
return Generator.GenerateTextBox(ViewContext, modelExplorer, For.Name, value, Format, htmlAttributes);
|
||||
}
|
||||
|
||||
// Get a fall-back format based on the metadata.
|
||||
|
|
@ -462,4 +505,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[HtmlAttributeName(ItemsAttributeName)]
|
||||
public IEnumerable<SelectListItem> Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <input> element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed through to the generated HTML in all cases. Also used to determine whether <see cref="For"/> is
|
||||
/// valid with an empty <see cref="ModelExpression.Name"/>.
|
||||
/// </remarks>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init(TagHelperContext context)
|
||||
{
|
||||
|
|
@ -89,11 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
var realModelType = For.ModelExplorer.ModelType;
|
||||
_allowMultiple = typeof(string) != realModelType &&
|
||||
typeof(IEnumerable).IsAssignableFrom(realModelType);
|
||||
_currentValues = Generator.GetCurrentValues(
|
||||
ViewContext,
|
||||
For.ModelExplorer,
|
||||
expression: For.Name,
|
||||
allowMultiple: _allowMultiple);
|
||||
_currentValues = Generator.GetCurrentValues(ViewContext, For.ModelExplorer, For.Name, _allowMultiple);
|
||||
|
||||
// Whether or not (not being highly unlikely) we generate anything, could update contained <option/>
|
||||
// elements. Provide selected values for <option/> tag helpers.
|
||||
|
|
@ -115,6 +120,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
|
||||
// Pass through attribute that is also a well-known HTML attribute. Must be done prior to any copying
|
||||
// from a TagBuilder.
|
||||
if (Name != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(nameof(Name), context);
|
||||
}
|
||||
|
||||
// Ensure GenerateSelect() _never_ looks anything up in ViewData.
|
||||
var items = Items ?? Enumerable.Empty<SelectListItem>();
|
||||
|
||||
|
|
@ -125,6 +137,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
return;
|
||||
}
|
||||
|
||||
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
|
||||
IDictionary<string, object> htmlAttributes = null;
|
||||
if (string.IsNullOrEmpty(For.Name) &&
|
||||
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
|
||||
!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "name", Name },
|
||||
};
|
||||
}
|
||||
|
||||
var tagBuilder = Generator.GenerateSelect(
|
||||
ViewContext,
|
||||
For.ModelExplorer,
|
||||
|
|
@ -133,7 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
selectList: items,
|
||||
currentValues: _currentValues,
|
||||
allowMultiple: _allowMultiple,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
if (tagBuilder != null)
|
||||
{
|
||||
|
|
@ -145,4 +169,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
|
@ -40,6 +41,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[HtmlAttributeName(ForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <input> element.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Passed through to the generated HTML in all cases. Also used to determine whether <see cref="For"/> is
|
||||
/// valid with an empty <see cref="ModelExpression.Name"/>.
|
||||
/// </remarks>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>Does nothing if <see cref="For"/> is <c>null</c>.</remarks>
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
|
|
@ -54,13 +64,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
throw new ArgumentNullException(nameof(output));
|
||||
}
|
||||
|
||||
// Pass through attribute that is also a well-known HTML attribute. Must be done prior to any copying
|
||||
// from a TagBuilder.
|
||||
if (Name != null)
|
||||
{
|
||||
output.CopyHtmlAttribute(nameof(Name), context);
|
||||
}
|
||||
|
||||
// Ensure Generator does not throw due to empty "fullName" if user provided a name attribute.
|
||||
IDictionary<string, object> htmlAttributes = null;
|
||||
if (string.IsNullOrEmpty(For.Name) &&
|
||||
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
|
||||
!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "name", Name },
|
||||
};
|
||||
}
|
||||
|
||||
var tagBuilder = Generator.GenerateTextArea(
|
||||
ViewContext,
|
||||
For.ModelExplorer,
|
||||
For.Name,
|
||||
rows: 0,
|
||||
columns: 0,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
if (tagBuilder != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
|
|
@ -16,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
[HtmlTargetElement("span", Attributes = ValidationForAttributeName)]
|
||||
public class ValidationMessageTagHelper : TagHelper
|
||||
{
|
||||
private const string DataValidationForAttributeName = "data-valmsg-for";
|
||||
private const string ValidationForAttributeName = "asp-validation-for";
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -36,9 +38,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
|
||||
protected IHtmlGenerator Generator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name to be validated on the current model.
|
||||
/// </summary>
|
||||
[HtmlAttributeName(ValidationForAttributeName)]
|
||||
public ModelExpression For { get; set; }
|
||||
|
||||
|
|
@ -58,13 +57,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
|
||||
if (For != null)
|
||||
{
|
||||
// Ensure Generator does not throw due to empty "fullName" if user provided data-valmsg-for attribute.
|
||||
// Assume data-valmsg-for value is non-empty if attribute is present at all. Should align with name of
|
||||
// another tag helper e.g. an <input/> and those tag helpers bind Name.
|
||||
IDictionary<string, object> htmlAttributes = null;
|
||||
if (string.IsNullOrEmpty(For.Name) &&
|
||||
string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) &&
|
||||
output.Attributes.ContainsName(DataValidationForAttributeName))
|
||||
{
|
||||
htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ DataValidationForAttributeName, "-non-empty-value-" },
|
||||
};
|
||||
}
|
||||
|
||||
var tagBuilder = Generator.GenerateValidationMessage(
|
||||
ViewContext,
|
||||
For.ModelExplorer,
|
||||
For.Name,
|
||||
message: null,
|
||||
tag: null,
|
||||
htmlAttributes: null);
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
if (tagBuilder != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -214,8 +214,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
if (modelExplorer.Model != null)
|
||||
{
|
||||
bool modelChecked;
|
||||
if (bool.TryParse(modelExplorer.Model.ToString(), out modelChecked))
|
||||
if (bool.TryParse(modelExplorer.Model.ToString(), out var modelChecked))
|
||||
{
|
||||
isChecked = modelChecked;
|
||||
}
|
||||
|
|
@ -261,7 +260,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
|
||||
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
tagBuilder.MergeAttribute("name", fullName);
|
||||
if (!string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
tagBuilder.MergeAttribute("name", fullName);
|
||||
}
|
||||
|
||||
return tagBuilder;
|
||||
}
|
||||
|
|
@ -363,8 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
// Special-case opaque values and arbitrary binary data.
|
||||
var byteArrayValue = value as byte[];
|
||||
if (byteArrayValue != null)
|
||||
if (value is byte[] byteArrayValue)
|
||||
{
|
||||
value = Convert.ToBase64String(byteArrayValue);
|
||||
}
|
||||
|
|
@ -596,7 +597,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
var htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes);
|
||||
if (!IsFullNameValid(fullName, htmlAttributeDictionary))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatHtmlGenerator_FieldNameCannotBeNullOrEmpty(
|
||||
|
|
@ -622,17 +624,20 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
var tagBuilder = new TagBuilder("select");
|
||||
tagBuilder.InnerHtml.SetHtmlContent(listItemBuilder);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
||||
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
|
||||
tagBuilder.MergeAttributes(htmlAttributeDictionary);
|
||||
NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement);
|
||||
if (!string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
}
|
||||
|
||||
if (allowMultiple)
|
||||
{
|
||||
tagBuilder.MergeAttribute("multiple", "multiple");
|
||||
}
|
||||
|
||||
// If there are any errors for a named field, we add the css attribute.
|
||||
ModelStateEntry entry;
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(fullName, out entry))
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(fullName, out var entry))
|
||||
{
|
||||
if (entry.Errors.Count > 0)
|
||||
{
|
||||
|
|
@ -672,7 +677,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
var htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes);
|
||||
if (!IsFullNameValid(fullName, htmlAttributeDictionary))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatHtmlGenerator_FieldNameCannotBeNullOrEmpty(
|
||||
|
|
@ -684,8 +690,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
nameof(expression));
|
||||
}
|
||||
|
||||
ModelStateEntry entry;
|
||||
viewContext.ViewData.ModelState.TryGetValue(fullName, out entry);
|
||||
viewContext.ViewData.ModelState.TryGetValue(fullName, out var entry);
|
||||
|
||||
var value = string.Empty;
|
||||
if (entry != null && entry.AttemptedValue != null)
|
||||
|
|
@ -699,18 +704,24 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
var tagBuilder = new TagBuilder("textarea");
|
||||
NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), true);
|
||||
tagBuilder.MergeAttributes(htmlAttributeDictionary, replaceExisting: true);
|
||||
if (rows > 0)
|
||||
{
|
||||
tagBuilder.MergeAttribute("rows", rows.ToString(CultureInfo.InvariantCulture), true);
|
||||
tagBuilder.MergeAttribute("rows", rows.ToString(CultureInfo.InvariantCulture), replaceExisting: true);
|
||||
}
|
||||
|
||||
if (columns > 0)
|
||||
{
|
||||
tagBuilder.MergeAttribute("cols", columns.ToString(CultureInfo.InvariantCulture), true);
|
||||
tagBuilder.MergeAttribute(
|
||||
"cols",
|
||||
columns.ToString(CultureInfo.InvariantCulture),
|
||||
replaceExisting: true);
|
||||
}
|
||||
|
||||
tagBuilder.MergeAttribute("name", fullName, true);
|
||||
if (!string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
}
|
||||
|
||||
AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression);
|
||||
AddValidationAttributes(viewContext, tagBuilder, modelExplorer, expression);
|
||||
|
|
@ -773,7 +784,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
var htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes);
|
||||
if (!IsFullNameValid(fullName, htmlAttributeDictionary, fallbackAttributeName: "data-valmsg-for"))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatHtmlGenerator_FieldNameCannotBeNullOrEmpty(
|
||||
|
|
@ -791,8 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return null;
|
||||
}
|
||||
|
||||
ModelStateEntry entry;
|
||||
var tryGetModelStateResult = viewContext.ViewData.ModelState.TryGetValue(fullName, out entry);
|
||||
var tryGetModelStateResult = viewContext.ViewData.ModelState.TryGetValue(fullName, out var entry);
|
||||
var modelErrors = tryGetModelStateResult ? entry.Errors : null;
|
||||
|
||||
ModelError modelError = null;
|
||||
|
|
@ -812,8 +823,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
{
|
||||
tag = viewContext.ValidationMessageElement;
|
||||
}
|
||||
|
||||
var tagBuilder = new TagBuilder(tag);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
||||
tagBuilder.MergeAttributes(htmlAttributeDictionary);
|
||||
|
||||
// Only the style of the span is changed according to the errors if message is null or empty.
|
||||
// Otherwise the content and style is handled by the client-side validation.
|
||||
|
|
@ -838,7 +850,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
if (formContext != null)
|
||||
{
|
||||
tagBuilder.MergeAttribute("data-valmsg-for", fullName);
|
||||
if (!string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
tagBuilder.MergeAttribute("data-valmsg-for", fullName);
|
||||
}
|
||||
|
||||
var replaceValidationMessageContents = string.IsNullOrEmpty(message);
|
||||
tagBuilder.MergeAttribute("data-valmsg-replace",
|
||||
|
|
@ -868,9 +883,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return null;
|
||||
}
|
||||
|
||||
ModelStateEntry entryForModel;
|
||||
if (excludePropertyErrors &&
|
||||
(!viewData.ModelState.TryGetValue(viewData.TemplateInfo.HtmlFieldPrefix, out entryForModel) ||
|
||||
(!viewData.ModelState.TryGetValue(viewData.TemplateInfo.HtmlFieldPrefix, out var entryForModel) ||
|
||||
entryForModel.Errors.Count == 0))
|
||||
{
|
||||
// Client-side validation (if enabled) will not affect the generated element and element will be empty.
|
||||
|
|
@ -964,18 +978,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatHtmlGenerator_FieldNameCannotBeNullOrEmpty(
|
||||
typeof(IHtmlHelper).FullName,
|
||||
nameof(IHtmlHelper.Editor),
|
||||
typeof(IHtmlHelper<>).FullName,
|
||||
nameof(IHtmlHelper<object>.EditorFor),
|
||||
"htmlFieldName"),
|
||||
nameof(expression));
|
||||
}
|
||||
|
||||
var type = allowMultiple ? typeof(string[]) : typeof(string);
|
||||
var rawValue = GetModelStateValue(viewContext, fullName, type);
|
||||
|
||||
|
|
@ -1133,8 +1135,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
internal static object GetModelStateValue(ViewContext viewContext, string key, Type destinationType)
|
||||
{
|
||||
ModelStateEntry entry;
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(key, out entry) && entry.RawValue != null)
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(key, out var entry) && entry.RawValue != null)
|
||||
{
|
||||
return ModelBindingHelper.ConvertTo(entry.RawValue, destinationType, culture: null);
|
||||
}
|
||||
|
|
@ -1207,7 +1208,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
// elements. But we support the *ForModel() methods in any lower-level template, once HtmlFieldPrefix is
|
||||
// non-empty.
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
if (!IsFullNameValid(fullName, htmlAttributes))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
Resources.FormatHtmlGenerator_FieldNameCannotBeNullOrEmpty(
|
||||
|
|
@ -1220,11 +1221,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var inputTypeString = GetInputTypeString(inputType);
|
||||
var tagBuilder = new TagBuilder("input");
|
||||
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
|
||||
var tagBuilder = new TagBuilder("input")
|
||||
{
|
||||
TagRenderMode = TagRenderMode.SelfClosing,
|
||||
};
|
||||
|
||||
tagBuilder.MergeAttributes(htmlAttributes);
|
||||
tagBuilder.MergeAttribute("type", inputTypeString);
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
if (!string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
}
|
||||
|
||||
var suppliedTypeString = tagBuilder.Attributes["type"];
|
||||
if (_placeholderInputTypes.Contains(suppliedTypeString))
|
||||
|
|
@ -1249,8 +1256,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
case InputType.Radio:
|
||||
if (!usedModelState)
|
||||
{
|
||||
var modelStateValue = GetModelStateValue(viewContext, fullName, typeof(string)) as string;
|
||||
if (modelStateValue != null)
|
||||
if (GetModelStateValue(viewContext, fullName, typeof(string)) is string modelStateValue)
|
||||
{
|
||||
isChecked = string.Equals(modelStateValue, valueParameter, StringComparison.Ordinal);
|
||||
usedModelState = true;
|
||||
|
|
@ -1313,8 +1319,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
// If there are any errors for a named field, we add the CSS attribute.
|
||||
ModelStateEntry entry;
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(fullName, out entry) && entry.Errors.Count > 0)
|
||||
if (viewContext.ViewData.ModelState.TryGetValue(fullName, out var entry) && entry.Errors.Count > 0)
|
||||
{
|
||||
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
|
||||
}
|
||||
|
|
@ -1410,8 +1415,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
private static object ConvertEnumFromString<TEnum>(string value) where TEnum : struct
|
||||
{
|
||||
TEnum enumValue;
|
||||
if (Enum.TryParse(value, out enumValue))
|
||||
if (Enum.TryParse(value, out TEnum enumValue))
|
||||
{
|
||||
return enumValue;
|
||||
}
|
||||
|
|
@ -1500,10 +1504,41 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return selectList;
|
||||
}
|
||||
|
||||
private static bool IsFullNameValid(string fullName, IDictionary<string, object> htmlAttributeDictionary)
|
||||
{
|
||||
return IsFullNameValid(fullName, htmlAttributeDictionary, fallbackAttributeName: "name");
|
||||
}
|
||||
|
||||
private static bool IsFullNameValid(
|
||||
string fullName,
|
||||
IDictionary<string, object> htmlAttributeDictionary,
|
||||
string fallbackAttributeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
// fullName==null is normally an error because name="" is not valid in HTML 5.
|
||||
if (htmlAttributeDictionary == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user has provided an explicit name attribute.
|
||||
// Generalized a bit because other attributes e.g. data-valmsg-for refer to element names.
|
||||
htmlAttributeDictionary.TryGetValue(fallbackAttributeName, out var attributeObject);
|
||||
var attributeString = Convert.ToString(attributeObject, CultureInfo.InvariantCulture);
|
||||
if (string.IsNullOrEmpty(attributeString))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IHtmlContent GenerateGroupsAndOptions(string optionLabel, IEnumerable<SelectListItem> selectList)
|
||||
{
|
||||
return GenerateGroupsAndOptions(optionLabel: optionLabel, selectList: selectList, currentValues: null);
|
||||
return GenerateGroupsAndOptions(optionLabel, selectList, currentValues: null);
|
||||
}
|
||||
|
||||
private IHtmlContent GenerateGroupsAndOptions(
|
||||
|
|
|
|||
|
|
@ -160,8 +160,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// </remarks>
|
||||
public static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
|
||||
{
|
||||
var dictionary = htmlAttributes as IDictionary<string, object>;
|
||||
if (dictionary != null)
|
||||
if (htmlAttributes is IDictionary<string, object> dictionary)
|
||||
{
|
||||
return new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
|
@ -586,7 +585,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <inheritdoc />
|
||||
public IHtmlContent Raw(object value)
|
||||
{
|
||||
return new HtmlString(value == null ? null : value.ToString());
|
||||
return new HtmlString(value?.ToString());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -685,7 +684,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <inheritdoc />
|
||||
public IHtmlContent TextBox(string expression, object value, string format, object htmlAttributes)
|
||||
{
|
||||
return GenerateTextBox(modelExplorer: null, expression: expression, value: value, format: format,
|
||||
return GenerateTextBox(
|
||||
modelExplorer: null,
|
||||
expression: expression,
|
||||
value: value,
|
||||
format: format,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
|
|
@ -724,6 +727,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return HtmlString.Empty;
|
||||
}
|
||||
|
||||
if (!hiddenForCheckbox.Attributes.ContainsKey("name") &&
|
||||
checkbox.Attributes.TryGetValue("name", out var name))
|
||||
{
|
||||
// The checkbox and hidden elements should have the same name attribute value. Attributes will match
|
||||
// if both are present because both have a generated value. Reach here in the special case where user
|
||||
// provided a non-empty fallback name.
|
||||
hiddenForCheckbox.MergeAttribute("name", name);
|
||||
}
|
||||
|
||||
if (ViewContext.FormContext.CanRenderAtEndOfForm)
|
||||
{
|
||||
ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckbox);
|
||||
|
|
@ -861,7 +873,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
||||
}
|
||||
|
||||
var shouldGenerateAntiforgery = antiforgery.HasValue ? antiforgery.Value : method != FormMethod.Get;
|
||||
var shouldGenerateAntiforgery = antiforgery ?? method != FormMethod.Get;
|
||||
if (shouldGenerateAntiforgery)
|
||||
{
|
||||
ViewContext.FormContext.EndOfFormContent.Add(_htmlGenerator.GenerateAntiforgery(ViewContext));
|
||||
|
|
@ -917,7 +929,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
tagBuilder.WriteTo(ViewContext.Writer, _htmlEncoder);
|
||||
}
|
||||
|
||||
var shouldGenerateAntiforgery = antiforgery.HasValue ? antiforgery.Value : method != FormMethod.Get;
|
||||
var shouldGenerateAntiforgery = antiforgery ?? method != FormMethod.Get;
|
||||
if (shouldGenerateAntiforgery)
|
||||
{
|
||||
ViewContext.FormContext.EndOfFormContent.Add(_htmlGenerator.GenerateAntiforgery(ViewContext));
|
||||
|
|
@ -969,9 +981,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
var tagBuilder = _htmlGenerator.GenerateLabel(
|
||||
ViewContext,
|
||||
modelExplorer,
|
||||
expression: expression,
|
||||
labelText: labelText,
|
||||
htmlAttributes: htmlAttributes);
|
||||
expression,
|
||||
labelText,
|
||||
htmlAttributes);
|
||||
if (tagBuilder == null)
|
||||
{
|
||||
return HtmlString.Empty;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
{
|
||||
public class InputTagHelperTest
|
||||
{
|
||||
public static TheoryData MultiAttributeCheckBoxData
|
||||
public static TheoryData<TagHelperAttributeList, string> MultiAttributeCheckBoxData
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -252,6 +252,317 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
Assert.Equal(expectedPostElement, output.PostElement.GetContent());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("checkbox")]
|
||||
[InlineData("hidden")]
|
||||
[InlineData("number")]
|
||||
[InlineData("password")]
|
||||
[InlineData("text")]
|
||||
public void Process_WithEmptyForName_Throws(string inputTypeName)
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = false;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "type", inputTypeName },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
"input",
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => tagHelper.Process(context, output),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_Radio_WithEmptyForName_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var inputTypeName = "radio";
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = 23;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(int), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
Value = "24",
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "type", inputTypeName },
|
||||
{ "value", "24" },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
"input",
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => tagHelper.Process(context, output),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hidden")]
|
||||
[InlineData("number")]
|
||||
[InlineData("text")]
|
||||
public void Process_WithEmptyForName_DoesNotThrow_WithName(string inputTypeName)
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "input";
|
||||
var expectedAttributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
{ "value", "False" },
|
||||
};
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = false;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
viewContext.ClientValidationEnabled = false;
|
||||
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedAttributes, output.Attributes);
|
||||
Assert.False(output.IsContentModified);
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_Checkbox_WithEmptyForName_DoesNotThrow_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedPostElementContent = $"<input name=\"HtmlEncode[[{expectedAttributeValue}]]\" " +
|
||||
"type=\"HtmlEncode[[hidden]]\" value=\"HtmlEncode[[false]]\" />";
|
||||
var expectedTagName = "input";
|
||||
var inputTypeName = "checkbox";
|
||||
var expectedAttributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
{ "value", "true" },
|
||||
};
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = false;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(bool), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
viewContext.ClientValidationEnabled = false;
|
||||
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedAttributes, output.Attributes);
|
||||
Assert.False(output.IsContentModified);
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
|
||||
Assert.False(viewContext.FormContext.HasEndOfFormContent);
|
||||
Assert.Equal(expectedPostElementContent, HtmlContentUtilities.HtmlContentToString(output.PostElement));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_Password_WithEmptyForName_DoesNotThrow_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "input";
|
||||
var inputTypeName = "password";
|
||||
var expectedAttributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
};
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = "password";
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
viewContext.ClientValidationEnabled = false;
|
||||
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
// Expect attributes to just pass through. Tag helper binds all input attributes and doesn't add any.
|
||||
var context = new TagHelperContext(expectedAttributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedAttributes, output.Attributes);
|
||||
Assert.False(output.IsContentModified);
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_Radio_WithEmptyForName_DoesNotThrow_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "input";
|
||||
var inputTypeName = "radio";
|
||||
var expectedAttributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
{ "value", "24" },
|
||||
};
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = 23;
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(int), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
viewContext.ClientValidationEnabled = false;
|
||||
|
||||
var tagHelper = new InputTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
InputTypeName = inputTypeName,
|
||||
Name = expectedAttributeValue,
|
||||
Value = "24",
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
{ "type", inputTypeName },
|
||||
{ "value", "24" },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
getChildContentAsync: (useCachedResult, encoder) => Task.FromResult<TagHelperContent>(result: null))
|
||||
{
|
||||
TagMode = TagMode.SelfClosing,
|
||||
};
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedAttributes, output.Attributes);
|
||||
Assert.False(output.IsContentModified);
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
// Top-level container (List<Model> or Model instance), immediate container type (Model or NestModel),
|
||||
// model accessor, expression path / id, expected value.
|
||||
public static TheoryData<object, Type, object, NameAndId, string> TestDataSet
|
||||
|
|
@ -1410,7 +1721,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
};
|
||||
|
||||
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
|
||||
|
||||
|
||||
var model = new DateTime(
|
||||
year: 2000,
|
||||
month: 1,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.TagHelpers.Internal;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -704,6 +706,132 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
Assert.Same(currentValues, actualCurrentValues.Values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_WithEmptyForName_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
var expectedTagName = "select";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = "model-value";
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new SelectTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => tagHelper.Process(context, output),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_WithEmptyForName_DoesNotThrow_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "select";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = "model-value";
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new SelectTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
Assert.False(output.IsContentModified);
|
||||
|
||||
var attribute = Assert.Single(output.Attributes);
|
||||
Assert.Equal("name", attribute.Name);
|
||||
Assert.Equal(expectedAttributeValue, attribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_PassesNameThrough_EvenIfNullFor()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "span";
|
||||
|
||||
var selectList = Array.Empty<SelectListItem>();
|
||||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
generator
|
||||
.Setup(gen => gen.GenerateGroupsAndOptions(/* optionLabel: */ null, selectList))
|
||||
.Returns(HtmlString.Empty)
|
||||
.Verifiable();
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(
|
||||
model: null,
|
||||
htmlGenerator: generator.Object,
|
||||
metadataProvider: metadataProvider);
|
||||
|
||||
var tagHelper = new SelectTagHelper(generator.Object)
|
||||
{
|
||||
Items = selectList,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
};
|
||||
|
||||
var tagHelperContext = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act
|
||||
tagHelper.Process(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
generator.VerifyAll();
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
var attribute = Assert.Single(output.Attributes);
|
||||
Assert.Equal("name", attribute.Name);
|
||||
Assert.Equal(expectedAttributeValue, attribute.Value);
|
||||
}
|
||||
|
||||
public class NameAndId
|
||||
{
|
||||
public NameAndId(string name, string id)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -160,6 +160,84 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_WithEmptyForName_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
var expectedTagName = "textarea";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = "model-value";
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new TextAreaTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(new TagHelperAttributeList(), new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => tagHelper.Process(context, output),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Process_WithEmptyForName_DoesNotThrow_WithName()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedContent = Environment.NewLine + "HtmlEncode[[model-value]]";
|
||||
var expectedTagName = "textarea";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var model = "model-value";
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model);
|
||||
var modelExpression = new ModelExpression(name: string.Empty, modelExplorer: modelExplorer);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider);
|
||||
var tagHelper = new TextAreaTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
Name = expectedAttributeValue,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var attributes = new TagHelperAttributeList
|
||||
{
|
||||
{ "name", expectedAttributeValue },
|
||||
};
|
||||
|
||||
var context = new TagHelperContext(attributes, new Dictionary<object, object>(), "test");
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act
|
||||
tagHelper.Process(context, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
Assert.Equal(expectedContent, HtmlContentUtilities.HtmlContentToString(output.Content));
|
||||
|
||||
var attribute = Assert.Single(output.Attributes);
|
||||
Assert.Equal("name", attribute.Name);
|
||||
Assert.Equal(expectedAttributeValue, attribute.Value);
|
||||
}
|
||||
|
||||
public class NameAndId
|
||||
{
|
||||
public NameAndId(string name, string id)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines;
|
|||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -89,6 +90,151 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers
|
|||
Assert.Equal(expectedTagName, output.TagName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_WithEmptyNameFor_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var expectedTagName = "span";
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var modelExpression = CreateModelExpression(string.Empty);
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(
|
||||
model: null,
|
||||
htmlGenerator: htmlGenerator,
|
||||
metadataProvider: metadataProvider);
|
||||
|
||||
var validationMessageTagHelper = new ValidationMessageTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList
|
||||
{
|
||||
{ "for", modelExpression },
|
||||
},
|
||||
new Dictionary<object, object>(),
|
||||
"test");
|
||||
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act & Assert
|
||||
await ExceptionAssert.ThrowsArgumentAsync(
|
||||
() => validationMessageTagHelper.ProcessAsync(tagHelperContext, output),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_GeneratesExpectedOutput_WithEmptyNameFor_WithValidationFor()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "span";
|
||||
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var modelExpression = CreateModelExpression(string.Empty);
|
||||
var htmlGenerator = new TestableHtmlGenerator(metadataProvider);
|
||||
var viewContext = TestableHtmlGenerator.GetViewContext(
|
||||
model: null,
|
||||
htmlGenerator: htmlGenerator,
|
||||
metadataProvider: metadataProvider);
|
||||
|
||||
var validationMessageTagHelper = new ValidationMessageTagHelper(htmlGenerator)
|
||||
{
|
||||
For = modelExpression,
|
||||
ViewContext = viewContext,
|
||||
};
|
||||
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList
|
||||
{
|
||||
{ "for", modelExpression },
|
||||
},
|
||||
new Dictionary<object, object>(),
|
||||
"test");
|
||||
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList
|
||||
{
|
||||
{ "data-valmsg-for", expectedAttributeValue },
|
||||
},
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
validationMessageTagHelper.ViewContext = viewContext;
|
||||
|
||||
// Act
|
||||
await validationMessageTagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
Assert.Collection(output.Attributes,
|
||||
attribute =>
|
||||
{
|
||||
Assert.Equal("data-valmsg-for", attribute.Name);
|
||||
Assert.Equal(expectedAttributeValue, attribute.Value);
|
||||
},
|
||||
attribute =>
|
||||
{
|
||||
Assert.Equal("class", attribute.Name);
|
||||
Assert.Equal("field-validation-valid", attribute.Value);
|
||||
},
|
||||
attribute =>
|
||||
{
|
||||
Assert.Equal("data-valmsg-replace", attribute.Name);
|
||||
Assert.Equal("true", attribute.Value);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_PassesValidationForThrough_EvenIfNullFor()
|
||||
{
|
||||
// Arrange
|
||||
var expectedAttributeValue = "-expression-";
|
||||
var expectedTagName = "span";
|
||||
|
||||
// Generator is not used in this scenario.
|
||||
var generator = new Mock<IHtmlGenerator>(MockBehavior.Strict);
|
||||
var validationMessageTagHelper = new ValidationMessageTagHelper(generator.Object)
|
||||
{
|
||||
ViewContext = CreateViewContext(),
|
||||
};
|
||||
|
||||
var tagHelperContext = new TagHelperContext(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList(),
|
||||
new Dictionary<object, object>(),
|
||||
"test");
|
||||
|
||||
var output = new TagHelperOutput(
|
||||
expectedTagName,
|
||||
new TagHelperAttributeList
|
||||
{
|
||||
{ "data-valmsg-for", expectedAttributeValue },
|
||||
},
|
||||
(_, __) => Task.FromResult<TagHelperContent>(new DefaultTagHelperContent()));
|
||||
|
||||
// Act
|
||||
await validationMessageTagHelper.ProcessAsync(tagHelperContext, output);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTagName, output.TagName);
|
||||
var attribute = Assert.Single(output.Attributes);
|
||||
Assert.Equal("data-valmsg-for", attribute.Name);
|
||||
Assert.Equal(expectedAttributeValue, attribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessAsync_CallsIntoGenerateValidationMessageWithExpectedParameters()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData());
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: false);
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
|
|
@ -104,6 +104,29 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBoxWithNullExpression_DoesNotThrow_WithNameAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input class=""HtmlEncode[[some-class]]"" name=""HtmlEncode[[-expression-]]"" " +
|
||||
@"type=""HtmlEncode[[checkbox]]"" value=""HtmlEncode[[true]]"" /><input " +
|
||||
@"name=""HtmlEncode[[-expression-]]"" type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[false]]"" />";
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: false);
|
||||
helper.ViewContext.ClientValidationEnabled = false;
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "class", "some-class"},
|
||||
{ "name", "-expression-" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var html = helper.CheckBox(null, isChecked: false, htmlAttributes: attributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(html));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CheckBoxCheckedWithOnlyName_GeneratesExpectedValue()
|
||||
{
|
||||
|
|
@ -413,12 +436,17 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
var requiredMessage = ValidationAttributeUtil.GetRequiredErrorMessage("Boolean");
|
||||
var expected =
|
||||
$@"<input data-val=""HtmlEncode[[true]]"" data-val-required=""HtmlEncode[[{requiredMessage}]]"" " +
|
||||
@"id=""HtmlEncode[[MyPrefix]]"" name=""HtmlEncode[[MyPrefix]]"" Property3=""HtmlEncode[[Property3Value]]"" " +
|
||||
@"type=""HtmlEncode[[checkbox]]"" value=""HtmlEncode[[true]]"" /><input name=""HtmlEncode[[MyPrefix]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[false]]"" />";
|
||||
@"id=""HtmlEncode[[MyPrefix]]"" name=""HtmlEncode[[MyPrefix]]"" " +
|
||||
@"Property3=""HtmlEncode[[Property3Value]]"" type=""HtmlEncode[[checkbox]]"" " +
|
||||
@"value=""HtmlEncode[[true]]"" /><input name=""HtmlEncode[[MyPrefix]]"" " +
|
||||
@"type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[false]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model: false);
|
||||
var attributes = new Dictionary<string, object> { { "Property3", "Property3Value" } };
|
||||
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "Property3", "Property3Value" },
|
||||
{ "name", "-expression-" }, // overridden
|
||||
};
|
||||
|
||||
// Act
|
||||
var html = helper.CheckBox(string.Empty, isChecked: false, htmlAttributes: attributes);
|
||||
|
|
@ -589,10 +617,15 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
var expected =
|
||||
$@"<input data-val=""HtmlEncode[[true]]"" data-val-required=""HtmlEncode[[{requiredMessage}]]"" " +
|
||||
@"id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" " +
|
||||
@"Property3=""HtmlEncode[[Property3Value]]"" type=""HtmlEncode[[checkbox]]"" value=""HtmlEncode[[true]]"" /><input " +
|
||||
@"name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[false]]"" />";
|
||||
@"Property3=""HtmlEncode[[Property3Value]]"" type=""HtmlEncode[[checkbox]]"" " +
|
||||
@"value=""HtmlEncode[[true]]"" /><input name=""HtmlEncode[[Property1]]"" " +
|
||||
@"type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[false]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData());
|
||||
var attributes = new Dictionary<string, object> { { "Property3", "Property3Value" } };
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "Property3", "Property3Value" },
|
||||
{ "name", "-expression-" }, // overridden
|
||||
};
|
||||
|
||||
// Act
|
||||
var html = helper.CheckBoxFor(m => m.Property1, attributes);
|
||||
|
|
|
|||
|
|
@ -15,19 +15,36 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
public class HtmlHelperHiddenTest
|
||||
{
|
||||
public static IEnumerable<object[]> HiddenWithAttributesData
|
||||
public static TheoryData<object, string> HiddenWithAttributesData
|
||||
{
|
||||
get
|
||||
{
|
||||
var expected1 = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
var expected1 = @"<input baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" " +
|
||||
@"name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
yield return new object[] { new Dictionary<string, object> { { "baz", "BazValue" } }, expected1 };
|
||||
yield return new object[] { new { baz = "BazValue" }, expected1 };
|
||||
var expected2 = @"<input foo-baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" " +
|
||||
@"name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
var htmlAttributes1 = new Dictionary<string, object>
|
||||
{
|
||||
{ "baz", "BazValue" },
|
||||
{ "name", "-expression-" }, // overridden
|
||||
};
|
||||
var htmlAttributes2 = new
|
||||
{
|
||||
baz = "BazValue",
|
||||
name = "-expression-", // overridden
|
||||
};
|
||||
|
||||
var expected2 = @"<input foo-baz=""HtmlEncode[[BazValue]]"" id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" type=""HtmlEncode[[hidden]]"" " +
|
||||
@"value=""HtmlEncode[[ModelStateValue]]"" />";
|
||||
yield return new object[] { new Dictionary<string, object> { { "foo-baz", "BazValue" } }, expected2 };
|
||||
yield return new object[] { new { foo_baz = "BazValue" }, expected2 };
|
||||
var data = new TheoryData<object, string>
|
||||
{
|
||||
{ htmlAttributes1, expected1 },
|
||||
{ htmlAttributes2, expected1 },
|
||||
{ new Dictionary<string, object> { { "foo-baz", "BazValue" } }, expected2 },
|
||||
{ new { foo_baz = "BazValue" }, expected2 }
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +425,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
public void HiddenWithEmptyNameAndPrefixThrows()
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("model-value");
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "class", "some-class"}
|
||||
|
|
@ -419,11 +436,31 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => helper.Hidden(string.Empty, string.Empty, attributes),
|
||||
() => helper.Hidden(expression: string.Empty, value: null, htmlAttributes: attributes),
|
||||
"expression",
|
||||
expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenWithEmptyNameAndPrefix_DoesNotThrow_WithNameAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input class=""HtmlEncode[[some-class]]"" name=""HtmlEncode[[-expression-]]"" " +
|
||||
@"type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[model-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("model-value");
|
||||
var attributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "class", "some-class"},
|
||||
{ "name", "-expression-" },
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = helper.Hidden(expression: string.Empty, value: null, htmlAttributes: attributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HiddenWithViewDataErrors_GeneratesExpectedValue()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,39 +15,58 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
{
|
||||
public class HtmlHelperPasswordTest
|
||||
{
|
||||
public static IEnumerable<object[]> PasswordWithViewDataAndAttributesData
|
||||
public static TheoryData<object> HtmlAttributeData
|
||||
{
|
||||
get
|
||||
{
|
||||
var attributes1 = new Dictionary<string, object>
|
||||
return new TheoryData<object>
|
||||
{
|
||||
{ "test-key", "test-value" },
|
||||
{ "value", "attribute-value" }
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "name", "-expression-" }, // overridden
|
||||
{ "test-key", "test-value" },
|
||||
{ "value", "attribute-value" },
|
||||
},
|
||||
new
|
||||
{
|
||||
name = "-expression-", // overridden
|
||||
test_key = "test-value",
|
||||
value = "attribute-value",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var attributes2 = new { test_key = "test-value", value = "attribute-value" };
|
||||
public static TheoryData<ViewDataDictionary<PasswordModel>, object> PasswordWithViewDataAndAttributesData
|
||||
{
|
||||
get
|
||||
{
|
||||
var nullModelViewData = GetViewDataWithNullModelAndNonEmptyViewData();
|
||||
var viewData = GetViewDataWithModelStateAndModelAndViewDataValues();
|
||||
viewData.Model.Property1 = "does-not-get-used";
|
||||
|
||||
var vdd = GetViewDataWithModelStateAndModelAndViewDataValues();
|
||||
vdd.Model.Property1 = "does-not-get-used";
|
||||
yield return new object[] { vdd, attributes1 };
|
||||
yield return new object[] { vdd, attributes2 };
|
||||
var data = new TheoryData<ViewDataDictionary<PasswordModel>, object>();
|
||||
foreach (var items in HtmlAttributeData)
|
||||
{
|
||||
data.Add(viewData, items[0]);
|
||||
data.Add(nullModelViewData, items[0]);
|
||||
}
|
||||
|
||||
var nullModelVdd = GetViewDataWithNullModelAndNonEmptyViewData();
|
||||
yield return new object[] { nullModelVdd, attributes1 };
|
||||
yield return new object[] { nullModelVdd, attributes2 };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PasswordWithViewDataAndAttributesData))]
|
||||
public void Password_UsesAttributeValueWhenValueArgumentIsNull(
|
||||
ViewDataDictionary<PasswordModel> vdd,
|
||||
ViewDataDictionary<PasswordModel> viewData,
|
||||
object attributes)
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[attribute-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(vdd);
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" " +
|
||||
@"test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[attribute-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData);
|
||||
|
||||
// Act
|
||||
var result = helper.Password("Property1", value: null, htmlAttributes: attributes);
|
||||
|
|
@ -59,13 +78,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
[Theory]
|
||||
[MemberData(nameof(PasswordWithViewDataAndAttributesData))]
|
||||
public void Password_UsesExplicitValue_IfSpecified(
|
||||
ViewDataDictionary<PasswordModel> vdd,
|
||||
ViewDataDictionary<PasswordModel> viewData,
|
||||
object attributes)
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[explicit-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(vdd);
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" " +
|
||||
@"test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[explicit-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData);
|
||||
|
||||
// Act
|
||||
var result = helper.Password("Property1", "explicit-value", attributes);
|
||||
|
|
@ -128,20 +148,35 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
public void PasswordWithEmptyNameAndPrefixThrows()
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
||||
var name = string.Empty;
|
||||
var value = string.Empty;
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("model-value");
|
||||
var expression = string.Empty;
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => helper.Password(name, value, htmlAttributes: null),
|
||||
() => helper.Password(expression, value: null, htmlAttributes: null),
|
||||
"expression",
|
||||
expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PasswordWithEmptyNameAndPrefix_DoesNotThrow_WithNameAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input name=""HtmlEncode[[-expression-]]"" type=""HtmlEncode[[password]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("model-value");
|
||||
var expression = string.Empty;
|
||||
var htmlAttributes = new { name = "-expression-" };
|
||||
|
||||
// Act
|
||||
var result = helper.Password(expression, value: null, htmlAttributes: htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, HtmlContentUtilities.HtmlContentToString(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_UsesModelStateErrors_ButDoesNotUseModelOrViewDataOrModelStateForValueAttribute()
|
||||
{
|
||||
|
|
@ -218,14 +253,13 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(PasswordWithViewDataAndAttributesData))]
|
||||
public void PasswordForWithAttributes_GeneratesExpectedValue(
|
||||
ViewDataDictionary<PasswordModel> vdd,
|
||||
object htmlAttributes)
|
||||
[MemberData(nameof(HtmlAttributeData))]
|
||||
public void PasswordForWithAttributes_GeneratesExpectedValue(object htmlAttributes)
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[attribute-value]]"" />";
|
||||
var expected = @"<input id=""HtmlEncode[[Property1]]"" name=""HtmlEncode[[Property1]]"" " +
|
||||
@"test-key=""HtmlEncode[[test-value]]"" type=""HtmlEncode[[password]]"" " +
|
||||
@"value=""HtmlEncode[[attribute-value]]"" />";
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
|
||||
helper.ViewData.Model.Property1 = "test";
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core
|
||||
|
|
@ -122,13 +123,19 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-", // overridden
|
||||
};
|
||||
|
||||
// Act
|
||||
var radioButtonResult = helper.RadioButton("Property1", value: "myvalue", htmlAttributes: new { attr = "value" });
|
||||
var radioButtonResult = helper.RadioButton("Property1", "myvalue", htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[radio]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" " +
|
||||
"name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[radio]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(radioButtonResult));
|
||||
}
|
||||
|
||||
|
|
@ -137,13 +144,61 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-", // overridden
|
||||
};
|
||||
|
||||
// Act
|
||||
var radioButtonForResult = helper.RadioButtonFor(m => m.Property1, value: "myvalue", htmlAttributes: new { attr = "value" });
|
||||
var radioButtonForResult = helper.RadioButtonFor(m => m.Property1, "myvalue", htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[radio]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" " +
|
||||
"name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[radio]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(radioButtonForResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RadioButtonFor_Throws_IfFullNameEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("anotherValue");
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => helper.RadioButtonFor(m => m, "myvalue", htmlAttributes),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RadioButtonFor_DoesNotThrow_IfFullNameEmpty_WithNameAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("anotherValue");
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-",
|
||||
};
|
||||
|
||||
// Act
|
||||
var radioButtonForResult = helper.RadioButtonFor(m => m, "myvalue", htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" " +
|
||||
"name=\"HtmlEncode[[-expression-]]\" type=\"HtmlEncode[[radio]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(radioButtonForResult));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.TestCommon;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Core
|
||||
|
|
@ -108,17 +109,27 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
public void TextBox_UsesSpecifiedHtmlAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(new ViewDataDictionary<TestModel>(metadataProvider));
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-", // overridden
|
||||
};
|
||||
|
||||
var model = new TestModel
|
||||
{
|
||||
Property1 = "propValue"
|
||||
};
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
helper.ViewContext.ClientValidationEnabled = false;
|
||||
helper.ViewData.Model = new TestModel { Property1 = "propValue" };
|
||||
|
||||
// Act
|
||||
var textBoxResult = helper.TextBox("Property1", value: "myvalue", htmlAttributes: new { attr = "value" });
|
||||
var textBoxResult = helper.TextBox("Property1", "myvalue", htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[text]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" " +
|
||||
"name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[text]]\" value=\"HtmlEncode[[myvalue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(textBoxResult));
|
||||
}
|
||||
|
||||
|
|
@ -126,17 +137,73 @@ namespace Microsoft.AspNetCore.Mvc.Core
|
|||
public void TextBoxFor_UsesSpecifiedHtmlAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new EmptyModelMetadataProvider();
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(new ViewDataDictionary<TestModel>(metadataProvider));
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-", // overridden
|
||||
};
|
||||
|
||||
var model = new TestModel
|
||||
{
|
||||
Property1 = "propValue"
|
||||
};
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper(model);
|
||||
helper.ViewContext.ClientValidationEnabled = false;
|
||||
helper.ViewData.Model = new TestModel { Property1 = "propValue" };
|
||||
|
||||
// Act
|
||||
var textBoxForResult = helper.TextBoxFor(m => m.Property1, htmlAttributes: new { attr = "value" });
|
||||
var textBoxForResult = helper.TextBoxFor(m => m.Property1, htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[text]]\" value=\"HtmlEncode[[propValue]]\" />",
|
||||
"<input attr=\"HtmlEncode[[value]]\" id=\"HtmlEncode[[Property1]]\" " +
|
||||
"name=\"HtmlEncode[[Property1]]\" type=\"HtmlEncode[[text]]\" value=\"HtmlEncode[[propValue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(textBoxForResult));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextBoxFor_Throws_IfFullNameEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMessage = "The name of an HTML field cannot be null or empty. Instead use methods " +
|
||||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
};
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("propValue");
|
||||
helper.ViewContext.ClientValidationEnabled = false;
|
||||
|
||||
// Act & Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => helper.TextBoxFor(m => m, htmlAttributes),
|
||||
paramName: "expression",
|
||||
exceptionMessage: expectedMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextBoxFor_DoesNotThrow_IfFullNameEmpty_WithNameAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var htmlAttributes = new
|
||||
{
|
||||
attr = "value",
|
||||
name = "-expression-",
|
||||
};
|
||||
|
||||
var helper = DefaultTemplatesUtilities.GetHtmlHelper("propValue");
|
||||
helper.ViewContext.ClientValidationEnabled = false;
|
||||
|
||||
// Act
|
||||
var textBoxForResult = helper.TextBoxFor(m => m, htmlAttributes);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"<input attr=\"HtmlEncode[[value]]\" " +
|
||||
"name=\"HtmlEncode[[-expression-]]\" type=\"HtmlEncode[[text]]\" value=\"HtmlEncode[[propValue]]\" />",
|
||||
HtmlContentUtilities.HtmlContentToString(textBoxForResult));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCurrentValues_WithNullExpression_Throws()
|
||||
public void GetCurrentValues_WithNullExpression_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
|
|
@ -77,19 +77,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
||||
|
||||
var expected = "The name of an HTML field cannot be null or empty. Instead use " +
|
||||
"methods Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
// Act and assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => htmlGenerator.GetCurrentValues(
|
||||
viewContext,
|
||||
modelExplorer,
|
||||
expression: null,
|
||||
allowMultiple: true),
|
||||
"expression",
|
||||
expected);
|
||||
// Act and Assert (does not throw).
|
||||
htmlGenerator.GetCurrentValues(viewContext, modelExplorer, expression: null, allowMultiple: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -105,20 +94,49 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
// Act and assert
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => htmlGenerator.GenerateSelect(
|
||||
viewContext,
|
||||
modelExplorer,
|
||||
"label",
|
||||
null,
|
||||
new List<SelectListItem>(),
|
||||
true,
|
||||
null),
|
||||
expression: null,
|
||||
selectList: new List<SelectListItem>(),
|
||||
allowMultiple: true,
|
||||
htmlAttributes: null),
|
||||
"expression",
|
||||
expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateSelect_WithNullExpression_WithNameAttribute_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "-expression-";
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var htmlGenerator = GetGenerator(metadataProvider);
|
||||
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
||||
var htmlAttributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "name", expected },
|
||||
};
|
||||
|
||||
// Act
|
||||
var tagBuilder = htmlGenerator.GenerateSelect(
|
||||
viewContext,
|
||||
modelExplorer,
|
||||
"label",
|
||||
expression: null,
|
||||
selectList: new List<SelectListItem>(),
|
||||
allowMultiple: true,
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "name");
|
||||
Assert.Equal(expected, attribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateTextArea_WithNullExpression_Throws()
|
||||
{
|
||||
|
|
@ -132,19 +150,47 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
// Act and assert
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => htmlGenerator.GenerateTextArea(
|
||||
viewContext,
|
||||
modelExplorer,
|
||||
null,
|
||||
1,
|
||||
1,
|
||||
null),
|
||||
expression: null,
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
htmlAttributes: null),
|
||||
"expression",
|
||||
expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateTextArea_WithNullExpression_WithNameAttribute_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "-expression-";
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var htmlGenerator = GetGenerator(metadataProvider);
|
||||
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
||||
var htmlAttributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "name", expected },
|
||||
};
|
||||
|
||||
// Act
|
||||
var tagBuilder = htmlGenerator.GenerateTextArea(
|
||||
viewContext,
|
||||
modelExplorer,
|
||||
expression: null,
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "name");
|
||||
Assert.Equal(expected, attribute.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateValidationMessage_WithNullExpression_Throws()
|
||||
{
|
||||
|
|
@ -158,13 +204,47 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
"Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper.Editor or Microsoft.AspNetCore.Mvc.Rendering." +
|
||||
"IHtmlHelper`1.EditorFor with a non-empty htmlFieldName argument value.";
|
||||
|
||||
// Act and assert
|
||||
// Act and Assert
|
||||
ExceptionAssert.ThrowsArgument(
|
||||
() => htmlGenerator.GenerateValidationMessage(viewContext, null, null, "Message", "tag", null),
|
||||
() => htmlGenerator.GenerateValidationMessage(
|
||||
viewContext,
|
||||
modelExplorer: null,
|
||||
expression: null,
|
||||
message: "Message",
|
||||
tag: "tag",
|
||||
htmlAttributes: null),
|
||||
"expression",
|
||||
expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateValidationMessage_WithNullExpression_WithValidationForAttribute_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
var expected = "-expression-";
|
||||
var metadataProvider = new TestModelMetadataProvider();
|
||||
var htmlGenerator = GetGenerator(metadataProvider);
|
||||
var viewContext = GetViewContext<Model>(model: null, metadataProvider: metadataProvider);
|
||||
var modelExplorer = metadataProvider.GetModelExplorerForType(typeof(string), model: null);
|
||||
var htmlAttributes = new Dictionary<string, object>
|
||||
{
|
||||
{ "data-valmsg-for", expected },
|
||||
};
|
||||
|
||||
// Act
|
||||
var tagBuilder = htmlGenerator.GenerateValidationMessage(
|
||||
viewContext,
|
||||
modelExplorer: null,
|
||||
expression: null,
|
||||
message: "Message",
|
||||
tag: "tag",
|
||||
htmlAttributes: htmlAttributes);
|
||||
|
||||
// Assert
|
||||
var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "data-valmsg-for");
|
||||
Assert.Equal(expected, attribute.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue