Archiv der Kategorie: HTML-Helpers

MVC Custom/Benutzerdefinierte Messages “verwalten” und zwischen Views und Controllern übergeben und anzeigen – v2.0


Es ist schon eine Weile her, da habe ich in “Grundzügen” in einem Blogpost gezeigt wie man Messages (Meldungen) in MVC so erweitert, das man auch eigene Meldungen anzeigen kann und nicht nur Fehlermeldungen anzeigt. Das ganze habe ich jetzt ein wenig aufgebohrt und mit Twitter Bootstrap Alert Styles umgesetzt. Mit der neuen Methode, ist es möglich gleichzeitig beliebig viele Fehlermeldungen und Messages anzuzeigen, die zum einen z.B. aus dem Modelstate Fehlern zusammengesetzt werden und aus den eigenen Meldungen bestehen. Die eigenen Meldungen müssen hier keine Fehler sein, es kann sich z.B. um Positive Bestätigungen oder Warnmeldungen handeln.

image

Das ganze setzt sich aus einem HTML Helper zusammen der einfach im View aufgerufen wird und die Meldungen dann nur noch darstellt. Die eigenen Meldungen, die wir ausgeben wollen, werden dabei im TempData abgelegt. Diese Variable wir automatisch gelöscht, nach dem Sie einmal abgerufen wurde und wir müssen uns um “nichts” kümmern. Wobei die Messages durch die Modelvalidation, werden immer als Fehler dargestellt und aus dem Modelstate abgerufen.

/// <summary>
/// Prüft ob im TempData Messages hinterlegt wurden und wenn ja dann werden diese angezeigt.
/// nach einem Request ist Tempdata auch direkt wieder leer, ist nur für einen Request aktiv!
/// Außerdem wird überprüft ob die Modeldaten ebenfalls einen Fehler enthalten und dieser wird hier ebenfalls ausgegeben
/// 
/// http://stackoverflow.com/questions/4642845/asp-net-mvc-how-to-preserve-modelstate-errors-across-redirecttoaction
/// </summary>
/// <returns>HTML Konstrukt was eine Custom Message anzeigt.</returns>
public static MvcHtmlString ShowCustomMessage(this HtmlHelper helper)
{
    //Eine neue Messageliste initialisieren
    List<Message> messages = new List<Message>();

    //Prüfen ob der passende Einträge im TempData vorhanden sind, die wird dort selbst abgelegt haben
    if (helper.ViewContext.Controller.TempData[CustomMessage.TempMessageString] != null)
    {
        //den Tempdata Liste der Nachrichten abrufen
        messages = (List<Message>)helper.ViewContext.Controller.TempData[CustomMessage.TempMessageString];
    }

    //Nur prüfen ob Fehler im Modelstate sind, wenn dieser auch nicht valide ist.
    if (!helper.ViewData.ModelState.IsValid)
    {
        //Nach Meldungen die Vom System im Modelstatus abgelegt wurden suchen
        //Ebenfalls den aktuellen Modelstatus nach meldungen durchsuchen, dabei handelt es sich immer um Fehlermeldungen!
        foreach (var key in helper.ViewData.ModelState.Keys)
        {
            foreach (var err in helper.ViewData.ModelState[key].Errors)
            {
                //Bei den Meldungen im Modelstate handelt es sich immer um einen Fehler. Die Fehler dem Modelstate hinzufügen
                messages.Add(new Message(helper.Encode(err.ErrorMessage), MessageTypes.error));
            }
        }    
    }

    string html = string.Empty;
    //Unsere Messages erstellen. Dabei wird jede Message in einer eigenen Fehlermeldung ausgegeben und sortiert ausgeben von Fehler nach OK
    foreach (Message message in messages.OrderByDescending(p => p.MessageType))
    {
        //Festlegen um welchen Fehlertyp es sich handelt
        string errorClass = GetMessageTypeCssString(message.MessageType);

        //Den HTML String zusammenbauen mit den Messages
        html += string.Format("<div class=\"{0}\"><button class=\"close\" data-dismiss=\"alert\" type=\"button\">×</button>{1}</div>", errorClass, message.Text);
    }

    //Damit der HTML String auch als HTML auf der Webseite ausgegeben werden kann, diesen noch umwandeln und den Rückgabewert des Helpers anpassen.    
    return new MvcHtmlString(html);
}

/// <summary>
/// Gibt den passenden CSS Code für den übergebenen Messagetype zurück
/// </summary>
/// <param name="message">Der MessageType für den der passende CSS Code zurück gegeben werden soll</param>
private static string GetMessageTypeCssString(MessageTypes message)
{
    string errorClass = string.Empty;
    switch (message)
    {
        case MessageTypes.error:
            errorClass = "alert alert-error";
            break;
        case MessageTypes.warning:
            errorClass = "alert";
            break;

        case MessageTypes.ok:
            errorClass = "alert alert-success";
            break;

        case MessageTypes.message:
            errorClass = "alert alert-info";
            break;
        default:
            errorClass = "alert";
            break;
    }

    return errorClass;
}

Die eigenen Messages setzten sich dabei aus einem Messagestring und einem Messagetyp zusammen. Der Messagetyp gibt an um welche Art von Meldung es sich handelt, z.B. Fehler, Warnung, Ok oder Meldung.

/// <summary>
/// Gibt an, welcher MessageType auf der Oberfläche angezeigt werden soll, wird im Viewbag abgelegt.
/// </summary>
public enum MessageTypes
{
    ok,
    warning,
    error,
    message
}

/// <summary>
/// Wird zum Darstellen von Nachrichten auf der Webseite benötigt, wenn diese über Views hinweg "versendet" werden.
/// </summary>
public class Message
{
    public string Text { get; set; }
    public MessageTypes MessageType { get; set; }

    /// <summary>
    /// Konstruktor für die Message
    /// </summary>
    /// <param name="text">Der Text der angezeigt werden soll</param>
    /// <param name="messageTypes">Um die Art der Nachricht die angezeigt werden soll</param>
    public Message(string text, MessageTypes messageTypes)
    {
        MessageType = messageTypes;
        Text = text;
    }
}

Das Verwalten der CustomMessages wird mit der folgenden Klasse umgesetzt. Wichtig ist hier, das wir immer den Aktuellen Controller mit übergeben müssen, damit wir auf TempData zugreifen können um die Messages hinzufügen zu können. Im Tempdata befindet sich nur eine Liste mit den jeweils von uns zuletzt hinzugefügten CustomMessages und wenn wir eine neue Message hinzufügen, wird die Liste um diese Meldung erweitert.

/// <summary>
/// Custom Message, damit Nachrichten auch über Views hinweg dargestellt werden können.
/// </summary>
public class CustomMessage
{
    #region static
    /// <summary>
    /// Gibt eine Instanz von CustomMessage zurück um Benutzerdefinierte Messages über Views hinweg anzeigen zu können.
    /// http://stackoverflow.com/questions/4642845/asp-net-mvc-how-to-preserve-modelstate-errors-across-redirecttoaction
    /// </summary>
    /// <param name="controller">Der CurrentController damit auf TempData zugegriffen werden kann.</param>
    /// <returns></returns>
    public static CustomMessage CurrentMessage(Controller controller)
    {
        return new CustomMessage(controller);
    }
    #endregion

    #region Member
    /// <summary>
    /// Constante die für den Tempdata[TempMessageString] benutzt wird.
    /// </summary>
    public const string TempMessageString = "CMTempMessageString";

    /// <summary>
    /// Der aktuelle CurrentController um auf 
    /// </summary>
    public Controller CurrentController { get; set; }
    #endregion

    #region Konstruktor
    /// <summary>
    /// Initialisieren der Methode, der CurrentController wird benötigt damit man auf das TempData zurückgreifen kann
    /// </summary>
    /// <param name="controller">Der aktuelle CurrentController damit TempData verwendet werden kann</param>
    private CustomMessage(Controller controller)
    {
        CurrentController = controller;
    }
    #endregion

    #region Public Functions
    /// <summary>
    /// Hinzufügen der Custommessage zu den TempData, damit dieser Mittels des passendne HTMLHelpers 
    /// auf der passenden Seite angezeigt werden kann.
    /// </summary>
    /// <param name="message">Die Message die angezeigt werden soll</param>
    /// <param name="messageType">Der Messagetyp der angezeigt werden soll</param>
    public void AddMessage(string message, MessageTypes messageType)
    {
        if (message != null)
        {
            //Eine Liste erstellen, damit wird jetzt jede Meldung hinzugefügt und alle können angezeigt werden.
            List<Message> messages = (List<Message>) CurrentController.TempData[TempMessageString];
            //Wenn noch keine Nachrichten hinterlegt wurden, muss die Liste initialisiert werden
            if (messages == null)
            {
                messages = new List<Message>();
            }

            messages.Add(new Message(message, messageType));

            //Die Message im TempData hinterlegen, damit diese dann von der Passenden Helper Methode dargestellt werden kann.
            CurrentController.TempData[TempMessageString] = messages;
        }
    }
    #endregion
}

Das Hinzufügen von CustomMessages ist überall da möglich wo Ihr Zugriff auf euren Controller habt, ich übergebe meist auch an mein Model den aktuellen Controller, um gleich in der Logik die passenden Messages hinzufügen zu können.

 CustomMessage.CurrentMessage(this).AddMessage("Warnung 1", MessageTypes.warning);
 CustomMessage.CurrentMessage(this).AddMessage("Warnung 2", MessageTypes.warning);
 CustomMessage.CurrentMessage(this).AddMessage("Bitte passen Sie auf!", MessageTypes.message);
 CustomMessage.CurrentMessage(this).AddMessage("Speichervorgang erfolgreich", MessageTypes.ok);

Im View selbst wird nur der HTML Helper “ShowCustomMessage” aufgerufen und dieser rendert jeden Fehlermeldung in einer Eigenen Box.

<div class="row">
    <div class="span12">
        @Html.ShowCustomMessage()
    </div>
</div>

Damit das ganze auch “gut” aussieht, ist noch die Einbindung von Twitter Bootstrap notwendig. Ihr könnt aber auch selbst die HTML Templates an eure Bedürfnisse anpassen.

Minify JavaScript und CSS mit AjaxMin


Wie ich bereits in meinem letzten Post erläutert habe gibt es die unterschiedlichsten Möglichkeiten um seine Webseiten zu “verkleinern”. Hier will ich nur kurz zeigen wie schnell man sein JavaScript Code bzw. seinen CSS Code verkleinern kann.

Ich benutzte dafür AjaxMin von Microsoft, welches auf Codeplex zur Verfügung gestellt wird inklusive des Quelltextes. Wir benötigen allerdings nur die “AjaxMin.dll” welche im msi Package enthalten ist und wenn man das installiert, findet man die passende DLL z.B. unter:

C:\Program Files (x86)\Microsoft\Microsoft Ajax Minifier\AjaxMin.dll

Wenn der Standard Installationspfad gewählt wurde.

Ich habe mir hierfür ein einfaches ASP.NET MVC3 Projekt erstellt. In diesem möchte ich einfach die bereits erstellten CSS und JavaScript Dateien Minimiert direkt auf der Seite mit ausgeben. (Ich bin mir dessen bewusst das es sich dabei nicht um eine optimale Lösung handelt, da diese dann immer mit heruntergeladen werden, besser wäre hier ein Handler in dem man das Caching einstellen kann, ich glaube außerdem gelesen zu haben das dies z.B. bei MVC4 bereits integriert ist. Das ganze lässt sich aber auch simpel in einer Standard ASP.NET Anwendung unterbringen.)

Als erstes muss die AjaxMin.dll als Verweis unserem Projekt hinzugefügt werden und dann können wir schon mal in der _Layout.cshtml Datei einfach die alten Script und Style Einträge mit dem folgenden Code ersetzten:

    @Minify.Min(false).Css("Content\\Site.css")
    @Minify.Min(false).JavaScript("Scripts\\jquery-1.5.1.min.js")
    @Minify.Min(false).JavaScript("Scripts\\modernizr-1.7.min.js")

und der passende Code der Minify Klasse, damit auch die Aufrufe in der _Layout.cshtml Datei funktionieren:

public class Minify
{
    #region Static
    /// <summary>
    /// Neues Minify Objekt zurückgeben, welches dann JavaScript oder Css Minifien kann.
    /// </summary>
    /// <param name="debug">Gibt an ob alles Minified werden soll oder nicht, wenn debuggt werden soll.</param>
    public static Minify Min(bool debug)
    {
        return new Minify(debug);
    }
    #endregion

    #region Konstruktor
    private Minify(bool debug)
    {
    }
    #endregion

    #region Member
    /// <summary>
    /// Gibt an ob die Daten Minified werden sollen oder ob diese zum Debuggen normal ausgegeben werden sollen.
    /// </summary>
    public bool Debug { get; set; }
    #endregion

    #region Public Functions
    /// <summary>
    /// CSS Datei Minimieren
    /// </summary>
    /// <param name="fileName">Relativer Pfad vom Webverzeichnis aus zur Datei die Minimiert werden soll</param>
    /// <returns>Minified Stylesheet in einem Style Tag</returns>
    public MvcHtmlString Css(string fileName)
    {
        //Den Pfad heraussuchen in dem die Style Datei gefunden werden kann.
        string path = Path.Combine(HttpContext.Current.Server.MapPath(@"~/"), fileName);
        if (File.Exists(path))
        {
            //Den Style auslesen aus der Original Datei.
            string style = File.ReadAllText(path);
            if (!Debug)
            {
                //Den AjaxMinifier nutzen um die Daten zu "verkleinern"
                Minifier minifier = new Minifier();
                style = minifier.MinifyStyleSheet(style);
            }

            //Das ganze in Style Tags packen und dann zurückgeben
            return new MvcHtmlString(string.Format("<style>{0}</style>", style));
        }

        return new MvcHtmlString(string.Empty);
    }

    /// <summary>
    /// JavaScript Datei Minimieren.
    /// </summary>
    /// <param name="fileName">Relativer Pfad vom Webverzeichnis aus zur Datei die Minimiert werden soll</param>
    /// <returns>Minified JavaScript in einem Script Tag</returns>
    public MvcHtmlString JavaScript(string fileName)
    {
        //Den Pfad heraussuchen in dem die Style Datei gefunden werden kann.
        string path = Path.Combine(HttpContext.Current.Server.MapPath(@"~/"), fileName);
        if (File.Exists(path))
        {
            //Den Style auslesen aus der Original Datei.
            string script = File.ReadAllText(path);
            if (!Debug)
            {
                //Den AjaxMinifier nutzen um die Daten zu "verkleinern"
                Minifier minifier = new Minifier();
                script = minifier.MinifyJavaScript(script);
            }

            //Das ganze in Style Tags packen und dann zurückgeben
            return new MvcHtmlString(string.Format("<script>{0}</script>", script));
        }

        return new MvcHtmlString(string.Empty);
    }
    #endregion
}

Damit lässt sich der eigene CSS und JavaScript Code sehr schnell Minimieren und bei dieser evtl. auch nicht ganz glücklichen Variante hat man zumindest die Requests auf einen Request für die Seite inklusive JavaScript Dateien und CSS “optimiert”.

Das ganze Projekt könnt Ihr auch unter CodePlex finden.

https://squadwuschel.codeplex.com/

Unter: Source Code –> Browse –> Testprojekte –> MvcMinimizerTest

Leider habe ich das AjaxMin Projekt nicht bei NuGet gefunden, denn damit wäre ein Update einfacher, wenn es da jemand finden sollte, bin ich über einen Kommentar dankbar.

MVC Helper von Microsoft (Captcha)


Ich habe gestern eine sehr Interessante Seite direkt von MS gefunden, in der es eine Sehr gute Einführung in ASP.NET MVC gibt und wie sollte es anders sein findet man diese natürlich direkt auf ASP.NET:

http://www.asp.net/mvc/tutorials

nachdem ich mich noch einmal mit den Grundlagen auseinander gesetzt habe, war da auch noch ein Abschnitt über Razor und Helper Klassen:

http://www.asp.net/webmatrix/tutorials/asp-net-web-pages-api-reference

Leider sind diese bei einem ASP.NET MVC Projekt nicht standardmäßig eingebunden, so dass man erst einmal die Referenz auf die “Microsoft.Web.Helpers” hinzufügen muss über den “Library Package Manager”

image

hier muss dann auf der Konsole nur noch:

PM> Install-Package microsoft-web-helpers

eingegeben werden und schon wird die passende Dll dem Projekt hinzugefügt.

(Quelle: http://midnightprogrammer.net/post/Working-With-Microsoft-Web-Helpers-In-MVC-3-Razor-View.aspx)

Jetzt kann man auf die Helper von Microsoft zugreifen. Das kann z.B. folgendermaßen aussehen:

image

Der passende Code mit den Helper Aufrufen sieht dann so aus:

image

Auch das Einbinden eines Captchas ist sehr einfach, hier muss nur noch darauf geachtet werden das man sich vorher zwei Schlüssel (private und public) erstellen lässt unter:

https://www.google.com/recaptcha

Ich habe die beiden Schlüssel dann in der Web.Config bei mir eingebunden und Initialisiere diese dann in der Global.asax im Application_Start(), wenn man dies direkt hier macht, dann braucht man beim Erstellen und Abfragen nicht jedes mal den Schlüssel als Parameter mit übergeben.

image

Zum Rendern des Captchas muss nur noch auf der Webseite folgender Code angegeben werden mit “ReCaptcha.GetHtml()” wird das Catpcha erstellt.

image

Gerendertes Catpcha auf der Webseite:

image

und wenn man dann im Controller prüfen will ob die Eingabe auch korrekt war geht das genauso einfach mit, “ReCaptcha.Validate()”:

image

So einfach kann die Einbindung eines Captchas unter ASP.NET MVC sein.

Erstellen von URL-String in MVC in externen Codeklassen


Wenn man ein wenig mit MVC gearbeitet hat, stößt man recht schnell auf das Problem, das man z.B. Helper Extensions schreibt und dann gerne auf Funktionen aus dem Controller zugreifen möchte oder aus dem View mit dem man ganz einfach URLs erstellen kann wie “HTML.ActionLink”.

Die erste Hürde die man hier hat, ist das Erstellen des passenden Links, dies ist aber mit Hilfe von”UrlHelper” eine einfache Aufgabe, denn wie man im Quelltext erkennen kann, hat man hier die Funktion “Action” zur Verfügung, die einem Anhand des aktuellen RequestContext einen passenden String für die Url zurück gibt. Der muss dann nur noch mittels eines einfachen String.Format in ein “<a href” eingefügt werden und schon hat man einen eigenen Link erstellt.

image

Messages in MVC zwischen Views bzw. Controllern “übergeben/anzeigen”


Zum übermitteln von Nachrichten zwischen den Views gibt es die unterschiedlichsten Möglichkeiten, man könnte z.B. die Meldungen auch in der URL übergeben, was den Nachteil hat, die URL sieht “unsauber” aus und der Fehler kann evtl. mehrfach angezeigt werden. Denn mit den Methoden die bereits in MVC bereitgestellt werden “ModelState.AddModelError” lassen sich nur Fehler auf dem aktuellen View und Controller ausgeben, sobald man eine Fehlermeldung auf einer anderen Seite ausgeben möchte, ist dies nicht mit “Bordmitteln” von MVC möglich.

Daher habe ich mich für das Abspeichern der Nachrichten im Controller TempData entschieden, da diese Daten nach dem ersten Abrufen wieder aus TempData gelöscht werden. Dazu muss man einmal eine Klasse erstellen auf die man im Controller zugreift und am besten noch einen HTML-Helper der einem die Nachricht dann ganz einfach im View ausgibt.

Außerdem wollte ich gerne unterschiedliche Nachrichtentypen haben, und nicht nur Fehler ausgeben, denn es gibt ja z.B. auch noch Bestätigungen das das Speichern erfolgreich war oder Warnungen.

Als erstes erstellen wie eine Nachrichtenklasse:

image

Die wir später als Grundlage für die Nachricht nehmen die wir übermitteln wollen und dann benötigen wir noch eine Klasse für die Logik, damit wir die Nachrichten so einfach wie möglich unserem TempData hinzufügen können. Am besten mit einem Singleton Pattern, damit wir weniger schreiben müssen beim Hinzufügen der Message.

Im Folgenden also die Logikklasse, welche im Konstruktor den aktuellen Controller benötigt, damit auf TempData zugegriffen werden kann und die Message abgelegt wird.

image

(Leider habe ich bisher noch keine andere Lösung gefunden wie man evtl. noch auf TempData zugreifen kann ohne den Controller übergeben zu müssen, denn dann wäre der Aufruf noch kürzer, über Hinweise freue ich mich sehr!)

Hinzufügen einer Message in Controller oder einer anderen Klasse, hier muss nur jeweils der aktuelle Controller mit übergeben werden.

image

image

Die Anzeige der Fehlermeldung ist, dann ganz Einfach über eine HTML-Helper Extension gelöst und kann in jedem View zum Einsatz kommen, dabei spielt es keine Rolle in welchem Controller oder View die Meldung hinzugefügt wurde.

image

Es müssen dann natürlich noch für die jeweiligen MessageTypes die passenden Css Klassen angelegt werden, damit die Messages auch in der passenden Farbe angezeigt werden können.

Im View reicht es dann einfach nur an der passenden Stelle wo der Fehler angezeigt werden soll den Helper aufzurufen und schon funktioniert das ganze einwandfrei:

image