ASP.NET MVC & Bootstrap 3.0: ValidationSummary stylen und Felder markieren

Update: Es gab noch einen kleinen Bug im Code, den ich noch mal eben behoben habe!


Ursprünglicher Beitrag:

Ich habe mal spaßeshalber ein MVC Projekt angefangen und dort ebenfalls das vor wenigen Tagen erschienene Bootstrap 3 implementiert. Nun wollte ich die bekannte ValidationSummary von MVC ein wenig mit Bootstrap stylen, jedoch scheint dies nicht so einfach.

Bei stackoverflow (wo auch sonst) bin ich auf eine eigene Implementierung der ValidationSummary-Ausgabe speziell für Bootstrap gestoßen. Diese ist jedoch nur für die Server-seitige Validierung geeignet, und da ich mich sowieso noch nicht entschlossen hatte, welche Validierung ich nutzen möchte, habe ich kurzerhand die Client-seitige deaktiviert.

Neben der bootstrap-gestylten ValidationSummary zeigte „PeteGo“ noch, wie er seine Eingabefelder für den Benutzer fehlerhaft markiert. Dies ist mir allerdings zu aufwendig, da man natürlich mit jedem neuen Feld auch auch an die Prüfung denken muss. Ja ich weiß, ich kann ja Editor-Templates nutzen, will ich aber nicht 😉

Da ich die Erweiterung von PeteGo sowieso anfassen musste, sie war nur für Bootstrap 2 geeignet, habe ich die einzelnen Fehlermeldungen auch noch einem data--Attribute versehen und markiere per jQuery die Felder als fehlerhaft.

Die Erweiterung:

using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc
{
    public static class HtmlExtensionMethods
    {
        /// <summary>
        /// Returns an error alert that lists each model error, much like the standard ValidationSummary only with
        /// altered markup for the Twitter bootstrap styles.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper, bool closeable)
        {
            string errorMessage = "Please fix the errors listed below and try again.";
            string validationTitle = "Validation error";

            ModelStateDictionary modelErrors = helper.ViewContext.ViewData.ModelState;

            if (!modelErrors.Any(error => error.Value.Errors.Any()))
            {
                return new MvcHtmlString(string.Empty);
            }

            StringBuilder divContent = new StringBuilder();
            
            if (closeable)
            {
                TagBuilder button = new TagBuilder("button");
                button.AddCssClass("close");
                button.MergeAttribute("type", "button");
                button.MergeAttribute("data-dismiss", "alert");
                button.SetInnerText("x");
                divContent.Append(button);
            }

            TagBuilder validationTitleTag = new TagBuilder("strong");
            validationTitleTag.SetInnerText(validationTitle);
            divContent.Append(validationTitleTag);

            divContent.Append(" ");
            divContent.Append(errorMessage);

            StringBuilder ulContent = new StringBuilder();
            
            foreach (KeyValuePair<string, ModelState> modelError in modelErrors)
            {
                string id = modelError.Key;
                foreach(ModelError item in modelError.Value.Errors)
                {
                    TagBuilder li = new TagBuilder("li");
                    li.SetInnerText(item.ErrorMessage);
                    li.MergeAttribute("data-bootstrap-has-error", id);
                    ulContent.Append(li);
                }
            }
            TagBuilder ul = new TagBuilder("ul") {InnerHtml = ulContent.ToString()};
            divContent.Append(ul);

            TagBuilder div = new TagBuilder("div");
            div.AddCssClass("alert");
            div.AddCssClass("alert-error");
            div.AddCssClass("alert-block");
            div.InnerHtml = divContent.ToString();

            return new MvcHtmlString(div.ToString());
        }

        /// <summary>
        /// Overload allowing no arguments.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper)
        {
            return ValidationSummaryBootstrap(helper, true);
        }
    }
}

Und nun die paar Zeilen Javascript, welche dann die Felder entsprechend markieren:

$(function () {
    $('div.alert').each(function () {
        $(this).find('li[data-bootstrap-has-error]').each(
            function () {
                var id = $(this).attr('data-bootstrap-has-error');
                $('input[id=' + id + '],select[id=' + id + ']').parent().addClass('has-error');
            });
    });
});

Vielleicht kann es ja jemand gebrauchen 🙂

Ach ja, falls jemand ein paar Pros / Contras bezüglich Client-/Server-Validierung hat, kann er diese gerne in den Kommentaren hinterlassen.

2 Gedanken zu „ASP.NET MVC & Bootstrap 3.0: ValidationSummary stylen und Felder markieren“

  1. Moin!

    Was mir spontan als Pro für clientseitige Validierung einfällt, ist, dass *grobe* Falscheingaben direkt beim Benutzer abgefangen und nicht erst vom Server validiert werden (Traffic & Last auf dem Server). Zudem kostet die clientseitige Validierung (unter Verwendung von MS vorgesehenen Elemente wie AjaxForm) meist keinen zusätzlichen Mehraufwand (ausgenommen eigene Validierungen). Von daher würde ich, wenn möglich, beide Validierungsmethoden nutzen.

    Als Pro auf Serverseite: Z.B. das nützliche Interface IValidatableObject, womit weitere komplexere Validierungen möglich sind (und man keine Lust/Zeit hat oder es keinen Sinn macht, für diese ein eigenes Attribut zu implementieren 😉 ).

    Just my 2 cents 😉

  2. Also Serverseitig muss auf jeden Fall validiert werden, da ich die Clientseitige ja aushebeln kann.

    Ich werde noch mal schauen, ob ich die ganze Sache schön gestylet bekomme, da du natürlich recht hast, je schneller der Benutzer darauf hingewiesen wird, desto besser. Wie nervig ist es, wenn man sämtliche Formulare ausfüllt und erst nach dem Absenden mitbekommt, dass es gar nicht geht (z.B. Benutzername ist schon belegt).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.