RailRhoad
RailRhoad

Reputation: 2128

Possible for two working ValidationSummary controls in an ASP.NET MVC app?

Since the validation summary just displays the modelstate errors in a html list, does this mean that I can have only one validation summary? Or is there a way I can associate some kind of context to say that these modelstate errors show up on this summary and these go to the other?

Upvotes: 1

Views: 1238

Answers (2)

Shazwazza
Shazwazza

Reputation: 811

Here's a way to do this without having to code your own ValidationSummary and instead just rely on the underlying MVC validation summary to do the output. This also ensures that the Unobtrusive MVC validation is wired up correctly, etc...

The way this works is that the underlying ValidationSummary iterates through the ModelState based on it's HtmlHelper's IViewContainer. So, this just creates a new HtmlHelper with a filtered IViewContainer to filter out all of the ModelState that matches the prefix. So there's a few extension methods below to achieve all of this.

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, 
    string prefix = "",
    bool excludePropertyErrors = false, 
    string message = "", 
    IDictionary<string, object> htmlAttributes = null)
{
    if (prefix.IsNullOrWhiteSpace())
    {
        return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
    }

    //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for 
    //specific model state with the prefix.
    var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix));
    return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
}

private class ViewDataContainer : IViewDataContainer
{
    public ViewDataContainer()
    {
        ViewData = new ViewDataDictionary();
    }
    public ViewDataDictionary ViewData { get; set; }
}

public static IViewDataContainer FilterContainer(this IViewDataContainer container, string prefix)
{
    var newContainer = new ViewDataContainer();
    newContainer.ViewData.ModelState.Merge(container.ViewData.ModelState, prefix);
    return newContainer;
}

public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix)
{
    if (dictionary == null)
        return;
    foreach (var keyValuePair in dictionary.Where(keyValuePair => keyValuePair.Key.StartsWith(prefix + ".")))
    {
        state[keyValuePair.Key] = keyValuePair.Value;
    }
}

Upvotes: 2

JOBG
JOBG

Reputation: 4624

That will require you to write your own Validation Summary, either a HTML helper or Partial View. That will look something like this if you take the partial view way.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<%
    string prefix = ViewData["prefix"].ToString();
    var l = ViewData.ModelState.Where(e => e.Value.Errors.Count != 0 && e.Key.Contains(prefix)).ToList();
    if (l.Count() > 0)
    {
            Response.Write("<div>");
            Response.Write("<span>Please fix fields marked with an asteristk</span>");
            Response.Write("<ul>");
            foreach (KeyValuePair<string, ModelState> keyValuePair in l)
            {
                foreach (ModelError modelError in keyValuePair.Value.Errors)
                {
                %>
                <li><%= Html.Encode(modelError.ErrorMessage)%></li>
                <%
    }
            } Response.Write("</ul>");
            Response.Write("</div>");
    }
    %>

I Assume that you will pass through ViewData some kind of identification (prefix) to your summary, so it know which errors to display on each Summary.

And it will be used like a normal partial View:

<% ViewData["prefix"] = "YOUR_PREFIX_HERE"; %>
<% Html.RenderPartial("CustomValidationSummary"); %>

PD: you can take this same logic and implement a HTML Helper instead, to make it smoother.

EDIT: added HTML Helper Implementation.

public static class CustomValidationSummary
    {
        public static string ValidationSummaryFor(this HtmlHelper htmlHelper, string message, string prefix, IDictionary<string, object> htmlAttributes)
        {
            if (htmlHelper.ViewData.ModelState.IsValid)
            {
                return null;
            }

            var l = htmlHelper.ViewData.ModelState.Where(e => e.Value.Errors.Count != 0 && e.Key.StartsWith(prefix)).ToList();

            // Nothing to do if there aren't any errors
            if (l.Count() == 0)
            {
                return null;
            }

            string messageSpan;
            if (!String.IsNullOrEmpty(message))
            {
                TagBuilder spanTag = new TagBuilder("span");
                spanTag.MergeAttributes(htmlAttributes);
                spanTag.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);
                spanTag.SetInnerText(message);
                messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
            }
            else
            {
                messageSpan = null;
            }

            StringBuilder htmlSummary = new StringBuilder();
            TagBuilder unorderedList = new TagBuilder("ul");
            unorderedList.MergeAttributes(htmlAttributes);
            unorderedList.MergeAttribute("class", HtmlHelper.ValidationSummaryCssClassName);

            foreach (KeyValuePair<string, ModelState> keyValuePair in l)
            {
                foreach (ModelError modelError in keyValuePair.Value.Errors)
                {
                    var errorText = modelError.ErrorMessage;
                    if (!String.IsNullOrEmpty(errorText))
                    {
                        TagBuilder listItem = new TagBuilder("li");
                        listItem.SetInnerText(errorText);
                        htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
                    }
                }
            }

            unorderedList.InnerHtml = htmlSummary.ToString();

            return messageSpan + unorderedList.ToString(TagRenderMode.Normal);
        }

    }

And to use it:

<% = Html.ValidationSummaryFor("Please fix fields marked with an asteristk","prefix",null) %>

Upvotes: 1

Related Questions