Add ValidationSummary helper.
This enables Html.ValidationSummary. This is in reference to WEBFX-97.
This commit is contained in:
parent
b9010072aa
commit
dad87c5239
|
|
@ -10,6 +10,13 @@ namespace MvcSample.Web
|
|||
return View("MyView", User());
|
||||
}
|
||||
|
||||
public IActionResult ValidationSummary()
|
||||
{
|
||||
ModelState.AddModelError("something", "Something happened, show up in validation summary.");
|
||||
|
||||
return View("ValidationSummary");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action that shows metadata when model is <c>null</c>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
<style>
|
||||
.validationSummary {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
background-color: #e4e3e3;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1>ValidationSummary Test Page.</h1>
|
||||
<p>Below are all overloads for Html.ValidationSummary. You should see 5 validation summary titles and 4 validation summary error messages.</p>
|
||||
<br />
|
||||
|
||||
<section class="validationSummary">
|
||||
@Html.ValidationSummary()
|
||||
@Html.ValidationSummary(excludePropertyErrors: true)
|
||||
@Html.ValidationSummary(message: "Hello from validation message summary 1.")
|
||||
@Html.ValidationSummary(excludePropertyErrors: true, message: "Hello from validation message summary 2")
|
||||
@Html.ValidationSummary(message: "Hello from validation message summary 3", htmlAttributes: new { style = "color: red" })
|
||||
@Html.ValidationSummary(excludePropertyErrors: true, message: "Hello from validation message summary 4", htmlAttributes: new { style = "color: green" })
|
||||
@Html.ValidationSummary(message: "Hello from validation message summary 5", htmlAttributes: new Dictionary<string, object> { { "style", "color: blue" } })
|
||||
</section>
|
||||
|
|
@ -7,6 +7,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
{
|
||||
public class ModelMetadata
|
||||
{
|
||||
public static readonly int DefaultOrder = 10000;
|
||||
|
||||
private readonly Type _containerType;
|
||||
private readonly Type _modelType;
|
||||
private readonly string _propertyName;
|
||||
|
|
@ -15,6 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
private bool _convertEmptyStringToNull = true;
|
||||
private object _model;
|
||||
private Func<object> _modelAccessor;
|
||||
private int _order = DefaultOrder;
|
||||
private IEnumerable<ModelMetadata> _properties;
|
||||
private Type _realModelType;
|
||||
|
||||
|
|
@ -57,6 +60,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
|
||||
public virtual bool IsReadOnly { get; set; }
|
||||
|
||||
public virtual int Order
|
||||
{
|
||||
get { return _order; }
|
||||
set { _order = value; }
|
||||
}
|
||||
|
||||
public object Model
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Globalization;
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
|
|
@ -22,6 +23,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
public static readonly string ValidationSummaryCssClassName = "validation-summary-errors";
|
||||
public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid";
|
||||
|
||||
private const string HiddenListItem = @"<li style=""display:none""></li>";
|
||||
|
||||
private ViewContext _viewContext;
|
||||
private IViewEngine _viewEngine;
|
||||
|
||||
|
|
@ -175,6 +178,97 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
await viewEngineResult.View.RenderAsync(newViewContext);
|
||||
}
|
||||
|
||||
public virtual HtmlString ValidationSummary(bool excludePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
|
||||
|
||||
if (ViewData.ModelState.IsValid == true)
|
||||
{
|
||||
if (formContext == null ||
|
||||
ViewContext.UnobtrusiveJavaScriptEnabled &&
|
||||
excludePropertyErrors)
|
||||
{
|
||||
// No client side validation/updates
|
||||
return HtmlString.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
string messageSpan;
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
var spanTag = new TagBuilder("span");
|
||||
spanTag.SetInnerText(message);
|
||||
messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
|
||||
}
|
||||
else
|
||||
{
|
||||
messageSpan = null;
|
||||
}
|
||||
|
||||
var htmlSummary = new StringBuilder();
|
||||
var modelStates = ValidationHelpers.GetModelStateList(ViewData, excludePropertyErrors);
|
||||
|
||||
foreach (var modelState in modelStates)
|
||||
{
|
||||
foreach (var modelError in modelState.Errors)
|
||||
{
|
||||
string 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 == true)
|
||||
{
|
||||
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryValidCssClassName);
|
||||
}
|
||||
else
|
||||
{
|
||||
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName);
|
||||
}
|
||||
|
||||
divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);
|
||||
|
||||
if (formContext != null)
|
||||
{
|
||||
if (ViewContext.UnobtrusiveJavaScriptEnabled)
|
||||
{
|
||||
if (!excludePropertyErrors)
|
||||
{
|
||||
// Only put errors in the validation summary if they're supposed to be included there
|
||||
divBuilder.MergeAttribute("data-valmsg-summary", "true");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// client validation summaries need an ID
|
||||
divBuilder.GenerateId("validationSummary", IdAttributeDotReplacement);
|
||||
formContext.ValidationSummaryId = divBuilder.Attributes["id"];
|
||||
formContext.ReplaceValidationSummary = !excludePropertyErrors;
|
||||
}
|
||||
}
|
||||
|
||||
return divBuilder.ToHtmlString(TagRenderMode.Normal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the HTTP method that handles form input (GET or POST) as a string.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
{
|
||||
public class HtmlString
|
||||
{
|
||||
private static readonly HtmlString _empty = new HtmlString(string.Empty);
|
||||
|
||||
private readonly string _input;
|
||||
|
||||
public HtmlString(string input)
|
||||
|
|
@ -9,6 +11,14 @@
|
|||
_input = input;
|
||||
}
|
||||
|
||||
public static HtmlString Empty
|
||||
{
|
||||
get
|
||||
{
|
||||
return _empty;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _input;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public static class ValidationExtensions
|
||||
{
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper)
|
||||
{
|
||||
return ValidationSummary(htmlHelper, excludePropertyErrors: false);
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, bool excludePropertyErrors)
|
||||
{
|
||||
return ValidationSummary(htmlHelper, excludePropertyErrors, message: null);
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, string message)
|
||||
{
|
||||
return ValidationSummary(htmlHelper, excludePropertyErrors: false, message: message, htmlAttributes: (object)null);
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, bool excludePropertyErrors, string message)
|
||||
{
|
||||
return ValidationSummary(htmlHelper, excludePropertyErrors, message, htmlAttributes: (object)null);
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, string message, object htmlAttributes)
|
||||
{
|
||||
return ValidationSummary(htmlHelper, excludePropertyErrors: false, message: message, htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, bool excludePropertyErrors, string message, object htmlAttributes)
|
||||
{
|
||||
return htmlHelper.ValidationSummary(excludePropertyErrors, message, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
|
||||
}
|
||||
|
||||
public static HtmlString ValidationSummary<T>(this IHtmlHelper<T> htmlHelper, string message, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return htmlHelper.ValidationSummary(excludePropertyErrors: false, message: message, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
internal static class ValidationHelpers
|
||||
{
|
||||
public static string GetUserErrorMessageOrDefault(ModelError modelError, ModelState modelState)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(modelError.ErrorMessage))
|
||||
{
|
||||
return modelError.ErrorMessage;
|
||||
}
|
||||
|
||||
if (modelState == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var attemptedValue = (modelState.Value != null) ? modelState.Value.AttemptedValue : "null";
|
||||
|
||||
return Resources.FormatCommon_ValueNotValidForProperty(attemptedValue);
|
||||
}
|
||||
|
||||
// Returns non-null list of model states, which caller will render in order provided.
|
||||
public static IEnumerable<ModelState> GetModelStateList(ViewDataDictionary viewData, bool excludePropertyErrors)
|
||||
{
|
||||
if (excludePropertyErrors)
|
||||
{
|
||||
ModelState ms;
|
||||
viewData.ModelState.TryGetValue(viewData.TemplateInfo.HtmlFieldPrefix, out ms);
|
||||
|
||||
if (ms != null)
|
||||
{
|
||||
return new[] { ms };
|
||||
}
|
||||
|
||||
return Enumerable.Empty<ModelState>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sort modelStates to respect the ordering in the metadata.
|
||||
// ModelState doesn't refer to ModelMetadata, but we can correlate via the property name.
|
||||
var ordering = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var metadata = viewData.ModelMetadata;
|
||||
if (metadata != null)
|
||||
{
|
||||
foreach (var data in metadata.Properties)
|
||||
{
|
||||
ordering[data.PropertyName] = data.Order;
|
||||
}
|
||||
|
||||
return viewData.ModelState
|
||||
.OrderBy(data => ordering[data.Key])
|
||||
.Select(ms => ms.Value);
|
||||
}
|
||||
|
||||
return viewData.ModelState.Values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
|
|
@ -99,5 +100,14 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <param name="viewData">A <see cref="ViewDataDictionary"/> to pass into the partial view.</param>
|
||||
/// <returns>A task that represents when rendering has completed.</returns>
|
||||
Task RenderPartialAsync([NotNull] string partialViewName, object model, ViewDataDictionary viewData);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an unordered list (ul element) of validation messages that are in the <see cref="ModelStateDictionary"/> object.
|
||||
/// </summary>
|
||||
/// <param name="excludePropertyErrors">true to have the summary display model-level errors only, or false to have the summary display all errors.</param>
|
||||
/// <param name="message">The message to display with the validation summary.</param>
|
||||
/// <param name="htmlAttributes">A dictionary that contains the HTML attributes for the element.</param>
|
||||
/// <returns>An <see cref="HtmlString"/> that contains an unordered list (ul element) of validation messages.</returns>
|
||||
HtmlString ValidationSummary(bool excludePropertyErrors, string message, IDictionary<string, object> htmlAttributes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("Common_PartialViewNotFound"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
internal static string Common_ValueNotValidForProperty
|
||||
{
|
||||
get { return GetString("Common_ValueNotValidForProperty"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid.
|
||||
/// </summary>
|
||||
internal static string FormatCommon_ValueNotValidForProperty(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("Common_ValueNotValidForProperty"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ViewData value must not be null.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -129,6 +129,9 @@
|
|||
<data name="Common_PartialViewNotFound" xml:space="preserve">
|
||||
<value>The partial view '{0}' was not found or no view engine supports the searched locations. The following locations were searched:{1}</value>
|
||||
</data>
|
||||
<data name="Common_ValueNotValidForProperty" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid.</value>
|
||||
</data>
|
||||
<data name="DynamicViewData_ViewDataNull" xml:space="preserve">
|
||||
<value>ViewData value must not be null.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
public ViewContext([NotNull] ViewContext viewContext)
|
||||
: this(viewContext.ServiceProvider, viewContext.HttpContext, viewContext.ViewEngineContext)
|
||||
{ }
|
||||
{
|
||||
UnobtrusiveJavaScriptEnabled = viewContext.UnobtrusiveJavaScriptEnabled;
|
||||
ClientValidationEnabled = viewContext.ClientValidationEnabled;
|
||||
}
|
||||
|
||||
public ViewContext(IServiceProvider serviceProvider, HttpContext httpContext,
|
||||
IDictionary<string, object> viewEngineContext)
|
||||
|
|
@ -25,6 +28,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
HttpContext = httpContext;
|
||||
ViewEngineContext = viewEngineContext;
|
||||
_formContext = _defaultFormContext;
|
||||
UnobtrusiveJavaScriptEnabled = true;
|
||||
ClientValidationEnabled = true;
|
||||
}
|
||||
|
||||
public IViewComponentHelper Component { get; set; }
|
||||
|
|
@ -48,6 +53,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
|
||||
public IUrlHelper Url { get; set; }
|
||||
|
||||
public bool UnobtrusiveJavaScriptEnabled { get; set; }
|
||||
|
||||
public bool ClientValidationEnabled { get; set; }
|
||||
|
||||
public dynamic ViewBag
|
||||
{
|
||||
get
|
||||
|
|
|
|||
Loading…
Reference in New Issue