Store `GetFullHtmlFieldName()` and `CreateSanitizedId()` values
- #3918 - don't repeat allocations for identical calls; helps w/ e.g. label / input / validation clusters - add `NameAndIdProvider`; it stores `PreviousNameAndId` in `HttpContext.Items` - `PreviousNameAndId` allocated only when `string`s are allocated e.g. "id"s were sanitized
This commit is contained in:
parent
3ef7d01bb6
commit
4e1ec39a1f
|
|
@ -0,0 +1,214 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides cached values for "name" and "id" HTML attributes.
|
||||
/// </summary>
|
||||
public static class NameAndIdProvider
|
||||
{
|
||||
private static readonly object PreviousNameAndIdKey = typeof(PreviousNameAndId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a valid HTML 4.01 "id" attribute value for an element with the given <paramref name="fullName"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewContext">A <see cref="ViewContext"/> instance for the current scope.</param>
|
||||
/// <param name="fullName">
|
||||
/// The fully-qualified expression name, ignoring the current model. Also the original HTML element name.
|
||||
/// </param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
/// The <see cref="string"/> (normally a single <see cref="char"/>) to substitute for invalid characters in
|
||||
/// <paramref name="fullName"/>.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Valid HTML 4.01 "id" attribute value for an element with the given <paramref name="fullName"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Similar to <see cref="TagBuilder.CreateSanitizedId"/> but caches value for repeated invocations.
|
||||
/// </remarks>
|
||||
public static string CreateSanitizedId(ViewContext viewContext, string fullName, string invalidCharReplacement)
|
||||
{
|
||||
if (viewContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
if (invalidCharReplacement == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(invalidCharReplacement));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Check cache to avoid whatever TagBuilder.CreateSanitizedId() may do.
|
||||
var items = viewContext.HttpContext.Items;
|
||||
object previousNameAndIdObject;
|
||||
PreviousNameAndId previousNameAndId = null;
|
||||
if (items.TryGetValue(PreviousNameAndIdKey, out previousNameAndIdObject) &&
|
||||
(previousNameAndId = (PreviousNameAndId)previousNameAndIdObject) != null &&
|
||||
string.Equals(previousNameAndId.FullName, fullName, StringComparison.Ordinal))
|
||||
{
|
||||
return previousNameAndId.SanitizedId;
|
||||
}
|
||||
|
||||
var sanitizedId = TagBuilder.CreateSanitizedId(fullName, invalidCharReplacement);
|
||||
|
||||
if (previousNameAndId == null)
|
||||
{
|
||||
// Do not create a PreviousNameAndId when TagBuilder.CreateSanitizedId() only examined fullName.
|
||||
if (string.Equals(fullName, sanitizedId, StringComparison.Ordinal))
|
||||
{
|
||||
return sanitizedId;
|
||||
}
|
||||
|
||||
previousNameAndId = new PreviousNameAndId();
|
||||
items[PreviousNameAndIdKey] = previousNameAndId;
|
||||
}
|
||||
|
||||
previousNameAndId.FullName = fullName;
|
||||
previousNameAndId.SanitizedId = sanitizedId;
|
||||
|
||||
return previousNameAndId.SanitizedId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a valid HTML 4.01 "id" attribute for an element with the given <paramref name="fullName"/>. Does
|
||||
/// nothing if <see cref="TagBuilder.Attributes"/> already contains an "id" attribute or the
|
||||
/// <paramref name="fullName"/> is <c>null</c> or empty.
|
||||
/// </summary>
|
||||
/// <param name="viewContext">A <see cref="ViewContext"/> instance for the current scope.</param>
|
||||
/// <param name="tagBuilder">A <see cref="TagBuilder"/> instance that will contain the "id" attribute.</param>
|
||||
/// <param name="fullName">
|
||||
/// The fully-qualified expression name, ignoring the current model. Also the original HTML element name.
|
||||
/// </param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
/// The <see cref="string"/> (normally a single <see cref="char"/>) to substitute for invalid characters in
|
||||
/// <paramref name="fullName"/>.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// Similar to <see cref="TagBuilder.GenerateId"/> but caches value for repeated invocations.
|
||||
/// </remarks>
|
||||
/// <seealso cref="CreateSanitizedId"/>
|
||||
public static void GenerateId(
|
||||
ViewContext viewContext,
|
||||
TagBuilder tagBuilder,
|
||||
string fullName,
|
||||
string invalidCharReplacement)
|
||||
{
|
||||
if (viewContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
if (tagBuilder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(tagBuilder));
|
||||
}
|
||||
|
||||
if (invalidCharReplacement == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(invalidCharReplacement));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tagBuilder.Attributes.ContainsKey("id"))
|
||||
{
|
||||
var sanitizedId = CreateSanitizedId(viewContext, fullName, invalidCharReplacement);
|
||||
|
||||
// Duplicate check for null or empty to cover the corner case where fullName contains only invalid
|
||||
// characters and invalidCharReplacement is empty.
|
||||
if (!string.IsNullOrEmpty(sanitizedId))
|
||||
{
|
||||
tagBuilder.Attributes["id"] = sanitizedId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full HTML element name for the specified <paramref name="expression"/>.
|
||||
/// </summary>
|
||||
/// <param name="viewContext">A <see cref="ViewContext"/> instance for the current scope.</param>
|
||||
/// <param name="expression">Expression name, relative to the current model.</param>
|
||||
/// <returns>Fully-qualified expression name for <paramref name="expression"/>.</returns>
|
||||
/// <remarks>
|
||||
/// Similar to <see cref="TemplateInfo.GetFullHtmlFieldName"/> but caches value for repeated invocations.
|
||||
/// </remarks>
|
||||
public static string GetFullHtmlFieldName(ViewContext viewContext, string expression)
|
||||
{
|
||||
var htmlFieldPrefix = viewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
|
||||
if (string.IsNullOrEmpty(expression))
|
||||
{
|
||||
return htmlFieldPrefix;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(htmlFieldPrefix))
|
||||
{
|
||||
return expression;
|
||||
}
|
||||
|
||||
// Need to concatenate. See if we've already done that.
|
||||
var items = viewContext.HttpContext.Items;
|
||||
object previousNameAndIdObject;
|
||||
PreviousNameAndId previousNameAndId = null;
|
||||
if (items.TryGetValue(PreviousNameAndIdKey, out previousNameAndIdObject) &&
|
||||
(previousNameAndId = (PreviousNameAndId)previousNameAndIdObject) != null &&
|
||||
string.Equals(previousNameAndId.HtmlFieldPrefix, htmlFieldPrefix, StringComparison.Ordinal) &&
|
||||
string.Equals(previousNameAndId.Expression, expression, StringComparison.Ordinal))
|
||||
{
|
||||
return previousNameAndId.OutputFullName;
|
||||
}
|
||||
|
||||
if (previousNameAndId == null)
|
||||
{
|
||||
previousNameAndId = new PreviousNameAndId();
|
||||
items[PreviousNameAndIdKey] = previousNameAndId;
|
||||
}
|
||||
|
||||
previousNameAndId.HtmlFieldPrefix = htmlFieldPrefix;
|
||||
previousNameAndId.Expression = expression;
|
||||
if (expression.StartsWith("[", StringComparison.Ordinal))
|
||||
{
|
||||
// The expression might represent an indexer access, in which case with a 'dot' would be invalid.
|
||||
previousNameAndId.OutputFullName = htmlFieldPrefix + expression;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousNameAndId.OutputFullName = htmlFieldPrefix + "." + expression;
|
||||
}
|
||||
|
||||
return previousNameAndId.OutputFullName;
|
||||
}
|
||||
|
||||
private class PreviousNameAndId
|
||||
{
|
||||
// Cached ambient input for NameAndIdProvider.GetFullHtmlFieldName(). TemplateInfo.HtmlFieldPrefix may
|
||||
// change during the lifetime of a ViewContext.
|
||||
public string HtmlFieldPrefix { get; set; }
|
||||
|
||||
// Cached input for NameAndIdProvider.GetFullHtmlFieldName().
|
||||
public string Expression { get; set; }
|
||||
|
||||
// Cached return value for NameAndIdProvider.GetFullHtmlFieldName().
|
||||
public string OutputFullName { get; set; }
|
||||
|
||||
// Cached input for NameAndIdProvider.CreateSanitizedId(). Since IHtmlHelper.GenerateIdFromName() is
|
||||
// available to all, there is no guarantee this is equal to OutputFullName when CreateSanitizedId() is
|
||||
// called.
|
||||
public string FullName { get; set; }
|
||||
|
||||
// Cached return value for NameAndIdProvider.CreateSanitizedId().
|
||||
public string SanitizedId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,9 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
/// <summary>
|
||||
/// Returns a valid HTML 4.01 "id" attribute value for an element with the given <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The original element name.</param>
|
||||
/// <param name="name">
|
||||
/// The fully-qualified expression name, ignoring the current model. Also the original HTML element name.
|
||||
/// </param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
/// The <see cref="string"/> (normally a single <see cref="char"/>) to substitute for invalid characters in
|
||||
/// <paramref name="name"/>.
|
||||
|
|
@ -159,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
stringBuffer.Append(firstChar);
|
||||
|
||||
// Characters until 'firstIndexOfInvalidCharacter' have already been checked for validity.
|
||||
// So just copying them. This avoids running them through Html401IdUtil.IsValidIdCharacter again.
|
||||
// So just copy them. This avoids running them through Html401IdUtil.IsValidIdCharacter again.
|
||||
for (var index = 1; index < firstIndexOfInvalidCharacter; index++)
|
||||
{
|
||||
stringBuffer.Append(name[index]);
|
||||
|
|
@ -182,13 +184,18 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a sanitized ID attribute for the tag by using the specified name.
|
||||
/// Adds a valid HTML 4.01 "id" attribute for an element with the given <paramref name="name"/>. Does
|
||||
/// nothing if <see cref="Attributes"/> already contains an "id" attribute or the <paramref name="name"/>
|
||||
/// is <c>null</c> or empty.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to use to generate an ID attribute.</param>
|
||||
/// <param name="name">
|
||||
/// The fully-qualified expression name, ignoring the current model. Also the original HTML element name.
|
||||
/// </param>
|
||||
/// <param name="invalidCharReplacement">
|
||||
/// The <see cref="string"/> (normally a single <see cref="char"/>) to substitute for invalid characters in
|
||||
/// <paramref name="name"/>.
|
||||
/// </param>
|
||||
/// <seealso cref="CreateSanitizedId(string, string)"/>
|
||||
public void GenerateId(string name, string invalidCharReplacement)
|
||||
{
|
||||
if (invalidCharReplacement == null)
|
||||
|
|
@ -196,9 +203,17 @@ namespace Microsoft.AspNetCore.Mvc.Rendering
|
|||
throw new ArgumentNullException(nameof(invalidCharReplacement));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Attributes.ContainsKey("id"))
|
||||
{
|
||||
var sanitizedId = CreateSanitizedId(name, invalidCharReplacement);
|
||||
|
||||
// Duplicate check for null or empty to cover the corner case where name contains only invalid
|
||||
// characters and invalidCharReplacement is empty.
|
||||
if (!string.IsNullOrEmpty(sanitizedId))
|
||||
{
|
||||
Attributes["id"] = sanitizedId;
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
|
||||
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
|
||||
/// <param name="clientValidatorCache">The <see cref="ClientValidatorCache"/> that provides
|
||||
/// a list of <see cref="IClientModelValidator"/>s.</param>
|
||||
/// <param name="clientValidatorCache">
|
||||
/// The <see cref="ClientValidatorCache"/> that provides a list of <see cref="IClientModelValidator"/>s.
|
||||
/// </param>
|
||||
[Obsolete("This constructor is obsolete and will be removed in a future version. The recommended " +
|
||||
"alternative is to use the other public constructor.")]
|
||||
public DefaultHtmlGenerator(
|
||||
|
|
@ -64,14 +65,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
IModelMetadataProvider metadataProvider,
|
||||
IUrlHelperFactory urlHelperFactory,
|
||||
HtmlEncoder htmlEncoder,
|
||||
ClientValidatorCache clientValidatorCache) : this(
|
||||
antiforgery,
|
||||
optionsAccessor,
|
||||
metadataProvider,
|
||||
urlHelperFactory,
|
||||
htmlEncoder,
|
||||
clientValidatorCache,
|
||||
new DefaultValidationHtmlAttributeProvider(optionsAccessor, metadataProvider, clientValidatorCache))
|
||||
ClientValidatorCache clientValidatorCache)
|
||||
: this(
|
||||
antiforgery,
|
||||
optionsAccessor,
|
||||
metadataProvider,
|
||||
urlHelperFactory,
|
||||
htmlEncoder,
|
||||
clientValidatorCache,
|
||||
new DefaultValidationHtmlAttributeProvider(optionsAccessor, metadataProvider, clientValidatorCache))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +86,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <param name="metadataProvider">The <see cref="IModelMetadataProvider"/>.</param>
|
||||
/// <param name="urlHelperFactory">The <see cref="IUrlHelperFactory"/>.</param>
|
||||
/// <param name="htmlEncoder">The <see cref="HtmlEncoder"/>.</param>
|
||||
/// <param name="clientValidatorCache">The <see cref="ClientValidatorCache"/> that provides
|
||||
/// a list of <see cref="IClientModelValidator"/>s.</param>
|
||||
/// <param name="clientValidatorCache">
|
||||
/// The <see cref="ClientValidatorCache"/> that provides a list of <see cref="IClientModelValidator"/>s.
|
||||
/// </param>
|
||||
/// <param name="validationAttributeProvider">The <see cref="ValidationHtmlAttributeProvider"/>.</param>
|
||||
public DefaultHtmlGenerator(
|
||||
IAntiforgery antiforgery,
|
||||
|
|
@ -280,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
tagBuilder.MergeAttribute("value", "false");
|
||||
tagBuilder.TagRenderMode = TagRenderMode.SelfClosing;
|
||||
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
tagBuilder.MergeAttribute("name", fullName);
|
||||
|
||||
return tagBuilder;
|
||||
|
|
@ -424,8 +427,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var tagBuilder = new TagBuilder("label");
|
||||
var idString =
|
||||
TagBuilder.CreateSanitizedId(GetFullHtmlFieldName(viewContext, expression), IdAttributeDotReplacement);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
var idString = NameAndIdProvider.CreateSanitizedId(viewContext, fullName, IdAttributeDotReplacement);
|
||||
tagBuilder.Attributes.Add("for", idString);
|
||||
tagBuilder.InnerHtml.SetContent(resolvedLabelText);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), replaceExisting: true);
|
||||
|
|
@ -599,7 +602,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
|
@ -628,7 +631,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
tagBuilder.InnerHtml.SetHtmlContent(listItemBuilder);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
||||
tagBuilder.MergeAttribute("name", fullName, true /* replaceExisting */);
|
||||
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
|
||||
NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement);
|
||||
if (allowMultiple)
|
||||
{
|
||||
tagBuilder.MergeAttribute("multiple", "multiple");
|
||||
|
|
@ -675,7 +678,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
Resources.HtmlHelper_TextAreaParameterOutOfRange);
|
||||
}
|
||||
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
|
@ -702,7 +705,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
}
|
||||
|
||||
var tagBuilder = new TagBuilder("textarea");
|
||||
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
|
||||
NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement);
|
||||
tagBuilder.MergeAttributes(GetHtmlAttributeDictionaryOrNull(htmlAttributes), true);
|
||||
if (rows > 0)
|
||||
{
|
||||
|
|
@ -776,7 +779,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
|
@ -967,7 +970,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(viewContext));
|
||||
}
|
||||
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
|
@ -1135,12 +1138,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return tagBuilder;
|
||||
}
|
||||
|
||||
internal static string GetFullHtmlFieldName(ViewContext viewContext, string expression)
|
||||
{
|
||||
var fullName = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
|
||||
return fullName;
|
||||
}
|
||||
|
||||
internal static object GetModelStateValue(ViewContext viewContext, string key, Type destinationType)
|
||||
{
|
||||
ModelStateEntry entry;
|
||||
|
|
@ -1216,7 +1213,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
// Not valid to use TextBoxForModel() and so on in a top-level view; would end up with an unnamed input
|
||||
// elements. But we support the *ForModel() methods in any lower-level template, once HtmlFieldPrefix is
|
||||
// non-empty.
|
||||
var fullName = GetFullHtmlFieldName(viewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
|
|
@ -1319,7 +1316,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
if (setId)
|
||||
{
|
||||
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
|
||||
NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement);
|
||||
}
|
||||
|
||||
// If there are any errors for a named field, we add the CSS attribute.
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
throw new ArgumentNullException(nameof(fullName));
|
||||
}
|
||||
|
||||
return TagBuilder.CreateSanitizedId(fullName, IdAttributeDotReplacement);
|
||||
return NameAndIdProvider.CreateSanitizedId(ViewContext, fullName, IdAttributeDotReplacement);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1001,10 +1001,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
protected virtual string GenerateId(string expression)
|
||||
{
|
||||
var fullName = DefaultHtmlGenerator.GetFullHtmlFieldName(ViewContext, expression: expression);
|
||||
var id = TagBuilder.CreateSanitizedId(fullName, IdAttributeDotReplacement);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(ViewContext, expression);
|
||||
|
||||
return id;
|
||||
return GenerateIdFromName(fullName);
|
||||
}
|
||||
|
||||
protected virtual IHtmlContent GenerateLabel(
|
||||
|
|
@ -1056,7 +1055,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
protected virtual string GenerateName(string expression)
|
||||
{
|
||||
var fullName = DefaultHtmlGenerator.GetFullHtmlFieldName(ViewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(ViewContext, expression);
|
||||
return fullName;
|
||||
}
|
||||
|
||||
|
|
@ -1190,7 +1189,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
|
||||
protected virtual string GenerateValue(string expression, object value, string format, bool useViewData)
|
||||
{
|
||||
var fullName = DefaultHtmlGenerator.GetFullHtmlFieldName(ViewContext, expression);
|
||||
var fullName = NameAndIdProvider.GetFullHtmlFieldName(ViewContext, expression);
|
||||
var attemptedValue =
|
||||
(string)DefaultHtmlGenerator.GetModelStateValue(ViewContext, fullName, typeof(string));
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
UrlEncoder urlEncoder,
|
||||
ExpressionTextCache expressionTextCache)
|
||||
: base(
|
||||
htmlGenerator,
|
||||
viewEngine,
|
||||
metadataProvider,
|
||||
bufferScope,
|
||||
htmlEncoder,
|
||||
urlEncoder)
|
||||
htmlGenerator,
|
||||
viewEngine,
|
||||
metadataProvider,
|
||||
bufferScope,
|
||||
htmlEncoder,
|
||||
urlEncoder)
|
||||
{
|
||||
if (expressionTextCache == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
||||
|
|
@ -10,6 +11,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// </summary>
|
||||
public class HtmlHelperOptions
|
||||
{
|
||||
private string _idAttributeDotReplacement = "_";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Html5DateRenderingMode.Html5DateRenderingMode"/> value.
|
||||
/// </summary>
|
||||
|
|
@ -23,7 +26,22 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
/// <summary>
|
||||
/// Gets or sets the <see cref="string"/> that replaces periods in the ID attribute of an element.
|
||||
/// </summary>
|
||||
public string IdAttributeDotReplacement { get; set; } = "_";
|
||||
public string IdAttributeDotReplacement
|
||||
{
|
||||
get
|
||||
{
|
||||
return _idAttributeDotReplacement;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
_idAttributeDotReplacement = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value that indicates whether client-side validation is enabled.
|
||||
|
|
@ -31,13 +49,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
public bool ClientValidationEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element name used to wrap a top-level message generated by
|
||||
/// Gets or sets the element name used to wrap a top-level message generated by
|
||||
/// <see cref="IHtmlHelper.ValidationMessage"/> and other overloads.
|
||||
/// </summary>
|
||||
public string ValidationMessageElement { get; set; } = "span";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the element name used to wrap a top-level message generated by
|
||||
/// Gets or sets the element name used to wrap a top-level message generated by
|
||||
/// <see cref="IHtmlHelper.ValidationSummary"/> and other overloads.
|
||||
/// </summary>
|
||||
public string ValidationSummaryMessageElement { get; set; } = "span";
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
{
|
||||
public class TemplateInfo
|
||||
{
|
||||
private string _htmlFieldPrefix;
|
||||
private object _formattedModelValue;
|
||||
|
||||
// Keep a collection of visited objects to prevent infinite recursion.
|
||||
private HashSet<object> _visitedObjects;
|
||||
private readonly HashSet<object> _visitedObjects;
|
||||
|
||||
private object _formattedModelValue;
|
||||
private string _htmlFieldPrefix;
|
||||
|
||||
public TemplateInfo()
|
||||
{
|
||||
|
|
@ -65,26 +65,31 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
|
|||
return _visitedObjects.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full HTML element name for the specified <paramref name="partialFieldName"/>.
|
||||
/// </summary>
|
||||
/// <param name="partialFieldName">Expression name, relative to the current model.</param>
|
||||
/// <returns>Fully-qualified expression name for <paramref name="partialFieldName"/>.</returns>
|
||||
public string GetFullHtmlFieldName(string partialFieldName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(partialFieldName))
|
||||
{
|
||||
return HtmlFieldPrefix;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(HtmlFieldPrefix))
|
||||
|
||||
if (string.IsNullOrEmpty(HtmlFieldPrefix))
|
||||
{
|
||||
return partialFieldName;
|
||||
}
|
||||
else if (partialFieldName.StartsWith("[", StringComparison.Ordinal))
|
||||
|
||||
if (partialFieldName.StartsWith("[", StringComparison.Ordinal))
|
||||
{
|
||||
// The partialFieldName might represent an indexer access, in which case combining
|
||||
// with a 'dot' would be invalid.
|
||||
return HtmlFieldPrefix + partialFieldName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HtmlFieldPrefix + "." + partialFieldName;
|
||||
}
|
||||
|
||||
return HtmlFieldPrefix + "." + partialFieldName;
|
||||
}
|
||||
|
||||
public bool Visited(ModelExplorer modelExplorer)
|
||||
|
|
|
|||
Loading…
Reference in New Issue