Improve `HtmlHelper` extension points
- address all of #659 and a bit of #874 (avoid `public virtual` methods in `HtmlHelper`) - make `MetadataProvider` and `GetClientValidationRules()` `public` and therefore available to extension methods - remove unused `GetValidationAttributes()` overload - make remaining `GetValidationAttributes()` overload (and not `GetClientValidationRules()`) `virtual`, allowing derived classes to change the attributes without overriding all callers - reverse `GetValidationAttributes()` and `GetClientValidationRules()` parameter order to match precedence - add `GenerateName()` and `GenerateValidationSummary()` to make `protected virtual` method names consistent - `Name()`, `ValidationSummary()` and `TextArea()` are no longer `virtual` because `protected virtual Generate*()` methods exist for all
This commit is contained in:
parent
b8960219b4
commit
40eb05f7e4
|
|
@ -100,7 +100,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IModelMetadataProvider MetadataProvider { get; private set; }
|
/// <inheritdoc />
|
||||||
|
public IModelMetadataProvider MetadataProvider { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public HtmlString ActionLink(
|
public HtmlString ActionLink(
|
||||||
|
|
@ -328,10 +329,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual HtmlString Name(string name)
|
public HtmlString Name(string name)
|
||||||
{
|
{
|
||||||
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
return GenerateName(name);
|
||||||
return new HtmlString(Encode(fullName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -449,85 +449,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual HtmlString ValidationSummary(bool excludePropertyErrors,
|
public HtmlString ValidationSummary(bool excludePropertyErrors,
|
||||||
string message,
|
string message,
|
||||||
IDictionary<string, object> htmlAttributes,
|
IDictionary<string, object> htmlAttributes,
|
||||||
string tag)
|
string tag)
|
||||||
{
|
{
|
||||||
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
return GenerateValidationSummary(excludePropertyErrors, message, htmlAttributes, tag);
|
||||||
if (ViewData.ModelState.IsValid && (formContext == null || excludePropertyErrors))
|
|
||||||
{
|
|
||||||
// No client side validation/updates
|
|
||||||
return HtmlString.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string wrappedMessage;
|
|
||||||
if (!string.IsNullOrEmpty(message))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(tag))
|
|
||||||
{
|
|
||||||
tag = ViewContext.ValidationSummaryMessageElement;
|
|
||||||
}
|
|
||||||
var messageTag = new TagBuilder(tag);
|
|
||||||
messageTag.SetInnerText(message);
|
|
||||||
wrappedMessage = messageTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wrappedMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If excludePropertyErrors is true, describe any validation issue with the current model in a single item.
|
|
||||||
// Otherwise, list individual property errors.
|
|
||||||
var htmlSummary = new StringBuilder();
|
|
||||||
var modelStates = ValidationHelpers.GetModelStateList(ViewData, excludePropertyErrors);
|
|
||||||
|
|
||||||
foreach (var modelState in modelStates)
|
|
||||||
{
|
|
||||||
foreach (var modelError in modelState.Errors)
|
|
||||||
{
|
|
||||||
var errorText = ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState: null);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(errorText))
|
|
||||||
{
|
|
||||||
var listItem = new TagBuilder("li");
|
|
||||||
listItem.SetInnerText(errorText);
|
|
||||||
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (htmlSummary.Length == 0)
|
|
||||||
{
|
|
||||||
htmlSummary.AppendLine(HiddenListItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
var unorderedList = new TagBuilder("ul")
|
|
||||||
{
|
|
||||||
InnerHtml = htmlSummary.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
var divBuilder = new TagBuilder("div");
|
|
||||||
divBuilder.MergeAttributes(htmlAttributes);
|
|
||||||
|
|
||||||
if (ViewData.ModelState.IsValid)
|
|
||||||
{
|
|
||||||
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryValidCssClassName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName);
|
|
||||||
}
|
|
||||||
|
|
||||||
divBuilder.InnerHtml = wrappedMessage + unorderedList.ToString(TagRenderMode.Normal);
|
|
||||||
|
|
||||||
if (formContext != null && !excludePropertyErrors)
|
|
||||||
{
|
|
||||||
// Inform the client where to replace the list of property errors after validation.
|
|
||||||
divBuilder.MergeAttribute("data-valmsg-summary", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
return divBuilder.ToHtmlString(TagRenderMode.Normal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -549,7 +476,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual HtmlString TextArea(string name, string value, int rows, int columns, object htmlAttributes)
|
public HtmlString TextArea(string name, string value, int rows, int columns, object htmlAttributes)
|
||||||
{
|
{
|
||||||
var metadata = ExpressionMetadataProvider.FromStringExpression(name, ViewData, MetadataProvider);
|
var metadata = ExpressionMetadataProvider.FromStringExpression(name, ViewData, MetadataProvider);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
|
|
@ -609,14 +536,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IDictionary<string, object> GetValidationAttributes(string name)
|
|
||||||
{
|
|
||||||
return GetValidationAttributes(name, metadata: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only render attributes if client-side validation is enabled, and then only if we've
|
// Only render attributes if client-side validation is enabled, and then only if we've
|
||||||
// never rendered validation for a field with this name in this form.
|
// never rendered validation for a field with this name in this form.
|
||||||
protected IDictionary<string, object> GetValidationAttributes(string name, ModelMetadata metadata)
|
protected virtual IDictionary<string, object> GetValidationAttributes(ModelMetadata metadata, string name)
|
||||||
{
|
{
|
||||||
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
||||||
if (formContext == null)
|
if (formContext == null)
|
||||||
|
|
@ -631,7 +553,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
formContext.RenderedField(fullName, true);
|
formContext.RenderedField(fullName, true);
|
||||||
var clientRules = GetClientValidationRules(name, metadata);
|
var clientRules = GetClientValidationRules(metadata, name);
|
||||||
return UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules);
|
return UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(clientRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -875,6 +797,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
htmlAttributes: htmlAttributes);
|
htmlAttributes: htmlAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual HtmlString GenerateName(string name)
|
||||||
|
{
|
||||||
|
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||||
|
return new HtmlString(Encode(fullName));
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual HtmlString GeneratePassword(ModelMetadata metadata, string name, object value,
|
protected virtual HtmlString GeneratePassword(ModelMetadata metadata, string name, object value,
|
||||||
object htmlAttributes)
|
object htmlAttributes)
|
||||||
{
|
{
|
||||||
|
|
@ -1043,7 +971,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
|
tagBuilder.MergeAttributes(GetValidationAttributes(metadata, name));
|
||||||
|
|
||||||
return tagBuilder.ToHtmlString(TagRenderMode.Normal);
|
return tagBuilder.ToHtmlString(TagRenderMode.Normal);
|
||||||
}
|
}
|
||||||
|
|
@ -1094,7 +1022,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
}
|
}
|
||||||
|
|
||||||
tagBuilder.MergeAttribute("name", fullName, true);
|
tagBuilder.MergeAttribute("name", fullName, true);
|
||||||
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
|
tagBuilder.MergeAttributes(GetValidationAttributes(metadata, name));
|
||||||
|
|
||||||
// If there are any errors for a named field, we add this CSS attribute.
|
// If there are any errors for a named field, we add this CSS attribute.
|
||||||
if (modelState != null && modelState.Errors.Count > 0)
|
if (modelState != null && modelState.Errors.Count > 0)
|
||||||
|
|
@ -1212,7 +1140,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
tagBuilder.AddCssClass(ValidationInputCssClassName);
|
tagBuilder.AddCssClass(ValidationInputCssClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
|
tagBuilder.MergeAttributes(GetValidationAttributes(metadata, name));
|
||||||
|
|
||||||
if (inputType == InputType.CheckBox)
|
if (inputType == InputType.CheckBox)
|
||||||
{
|
{
|
||||||
|
|
@ -1302,6 +1230,88 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
return builder.ToHtmlString(TagRenderMode.Normal);
|
return builder.ToHtmlString(TagRenderMode.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual HtmlString GenerateValidationSummary(
|
||||||
|
bool excludePropertyErrors,
|
||||||
|
string message,
|
||||||
|
IDictionary<string, object> htmlAttributes,
|
||||||
|
string tag)
|
||||||
|
{
|
||||||
|
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
||||||
|
if (ViewData.ModelState.IsValid && (formContext == null || excludePropertyErrors))
|
||||||
|
{
|
||||||
|
// No client side validation/updates
|
||||||
|
return HtmlString.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
string wrappedMessage;
|
||||||
|
if (!string.IsNullOrEmpty(message))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(tag))
|
||||||
|
{
|
||||||
|
tag = ViewContext.ValidationSummaryMessageElement;
|
||||||
|
}
|
||||||
|
var messageTag = new TagBuilder(tag);
|
||||||
|
messageTag.SetInnerText(message);
|
||||||
|
wrappedMessage = messageTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wrappedMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If excludePropertyErrors is true, describe any validation issue with the current model in a single item.
|
||||||
|
// Otherwise, list individual property errors.
|
||||||
|
var htmlSummary = new StringBuilder();
|
||||||
|
var modelStates = ValidationHelpers.GetModelStateList(ViewData, excludePropertyErrors);
|
||||||
|
|
||||||
|
foreach (var modelState in modelStates)
|
||||||
|
{
|
||||||
|
foreach (var modelError in modelState.Errors)
|
||||||
|
{
|
||||||
|
var errorText = ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState: null);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(errorText))
|
||||||
|
{
|
||||||
|
var listItem = new TagBuilder("li");
|
||||||
|
listItem.SetInnerText(errorText);
|
||||||
|
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (htmlSummary.Length == 0)
|
||||||
|
{
|
||||||
|
htmlSummary.AppendLine(HiddenListItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
var unorderedList = new TagBuilder("ul")
|
||||||
|
{
|
||||||
|
InnerHtml = htmlSummary.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
var divBuilder = new TagBuilder("div");
|
||||||
|
divBuilder.MergeAttributes(htmlAttributes);
|
||||||
|
|
||||||
|
if (ViewData.ModelState.IsValid)
|
||||||
|
{
|
||||||
|
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryValidCssClassName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName);
|
||||||
|
}
|
||||||
|
|
||||||
|
divBuilder.InnerHtml = wrappedMessage + unorderedList.ToString(TagRenderMode.Normal);
|
||||||
|
|
||||||
|
if (formContext != null && !excludePropertyErrors)
|
||||||
|
{
|
||||||
|
// Inform the client where to replace the list of property errors after validation.
|
||||||
|
divBuilder.MergeAttribute("data-valmsg-summary", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
return divBuilder.ToHtmlString(TagRenderMode.Normal);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual HtmlString GenerateValue(string name, object value, string format, bool useViewData)
|
protected virtual HtmlString GenerateValue(string name, object value, string format, bool useViewData)
|
||||||
{
|
{
|
||||||
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||||
|
|
@ -1336,9 +1346,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
return new HtmlString(Encode(resolvedValue));
|
return new HtmlString(Encode(resolvedValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules(
|
/// <inheritdoc />
|
||||||
string name,
|
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
|
||||||
ModelMetadata metadata)
|
ModelMetadata metadata,
|
||||||
|
string name)
|
||||||
{
|
{
|
||||||
var actionBindingContext = _actionBindingContextProvider.GetActionBindingContextAsync(ViewContext).Result;
|
var actionBindingContext = _actionBindingContextProvider.GetActionBindingContextAsync(ViewContext).Result;
|
||||||
metadata = metadata ??
|
metadata = metadata ??
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Rendering
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +24,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string IdAttributeDotReplacement { get; set; }
|
string IdAttributeDotReplacement { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the metadata provider. Intended for use in <see cref="IHtmlHelper"/> extension methods.
|
||||||
|
/// </summary>
|
||||||
|
IModelMetadataProvider MetadataProvider { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the view bag.
|
/// Gets the view bag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -245,6 +251,17 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
/// <returns>The ID of the HTML element.</returns>
|
/// <returns>The ID of the HTML element.</returns>
|
||||||
string GenerateIdFromName(string name);
|
string GenerateIdFromName(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns information about about client validation rules for the given <paramref name="metadata"/> or
|
||||||
|
/// <paramref name="name"/>. Intended for use in <see cref="IHtmlHelper"/> extension methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="metadata">Metadata about the <see langref="object"/> of interest.</param>
|
||||||
|
/// <param name="name">Expression name, relative to the current model. Used to determine
|
||||||
|
/// <see cref="ModelMetadata"/> when <paramref name="metadata"/> is <see langref="null"/>; ignored
|
||||||
|
/// otherwise.</param>
|
||||||
|
/// <returns>An <see cref="IEnumerable{ModelClientValidationRule}"/> containing the relevant rules.</returns>
|
||||||
|
IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, string name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Render an input element of type "hidden".
|
/// Render an input element of type "hidden".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue