MVC Einführung/Tutorial in Custom Editor Templates inklusive der Einbindung von HTML5 Attributen im Model


Wenn ich bisher für meine Viewdaten eine extra CSS Klasse oder ein weiteres HTML5 Attribut benötigt habe, dann sah das z.B. folgendermaßen aus.

 @Html.TextBoxFor(model => model.CurrentDate, 
                new { @class = "input-small " + WebConstants.DatePickerCssClass, 
                           @Value = Model.CurrentDate.ToString(WebConstants.InputDateTimeFormat),
								type="date" })

Da kann man schnell den Überblick verlieren und außerdem gibt es noch ne Menge HTML5 Attribute die man anwenden kann. Es gibt aber noch eine weitere Möglichkeit das “Problem” anzugehen und die heißt “MVC Editor Templates”.

1. Ein paar Grundlagen zu den MVC Editor Templates

Sobald Ihr die Funktion “@HTML.EditorFor(….)” oder “@HTML.DisplayFor(…)” nutzt greift MVC auf Editor Templates zurück, wenn es keine Custom Templates findet, dann nimmt es seine eigenen die von MS vordefiniert wurden. Einen sehr guten Artikel darüber hat Brad Wilson geschrieben. MVC unterscheidet in “EditorTemplates” zum Bearbeiten von Werten und “DisplayTemplates” zum Anzeigen von Werten.

Die Eigenen Templates können entweder unter dem jeweiligen View abgelegt werden (im Ordner “DisplayTemplates” oder “EditorTemplates”) oder unter dem Shared Folder. MVC prüft dann erst den jeweiligen passenden View Folder, wenn Ihr spezielle Templates für einen View erstellt habt und wenn MVC dort keine findet, wird weiter im Shared Folder nach Templates geschaut.

imageimage

Standardmäßig gibt es für Alle Standarddatentypen wie “Datetime”, “Int32/64”, “String” und “Bool” ein Template. In MVC kann man aber bei den Modeldaten noch ein Attribut “DataType” festlegen und für jeden DataType gibt es ebenfalls noch ein Standardtemplate.

Bei der Verwendung des DataType Attributs ist außerdem zu beachten, das der verwendete DataType Vorrang vor dem Datentyp den Properties selbst hat. Im Folgenden Beispiel wird also nicht das Template “DateTime” verwendet, sondern es wird das Template “Time” aufgerufen.

Das DataType Attribut hat noch weitere Auswirkungen auf unsere Templates, denn das DataType Attribut legt z.B. fest wie der Ausgabewert “vorformatiert” wird. Wenn es sich z.B. um ein Datetime Attribut handelt mit dem DataType “Time” dann wird bei der Ausgabe nur noch die Zeit ausgegeben z.B. “13:34”, oder wenn für DataType “Date” steht, dann wir z.B. “23.04.2013” ausgegeben und beim DataType “DateTime” wird z.B. “23.04.2013 13:34” ausgegeben.

/// 
/// Verwendung des DataType Attributes Time, veranlasst MVC
/// nicht das "DateTime" Template zu nutzen
/// sondern, es wird das "Time" Template genommen.
/// 
[DataType(DataType.Time)]
[Display(Name = "Aktuelle Zeit")]
public DateTime CurrentTime { get; set; }

Wenn man also ein Element mit “EditorFor” erstellt, dann prüft MVC welcher Datentyp dargestellt werden soll und sucht ein passendes Template heraus und wenn es keines vom User findet, werden die eigenen von MVC benutzt.

Die Funktion “EditorFor” bietet aber auch die Möglichkeit zu sagen, welches Template verwendet werden soll, wenn man z.B. spezielle Custom Templates angelegt hat.

</pre>
<div class="control-group">@Html.LabelFor(model => model.Datum, new { @class = "control-label" })
<div class="controls">@* Aufruf des Standardtemplates für unser Datum. *@ @Html.EditorFor(model => model.Datum) @Html.ValidationMessageFor(model => model.Datum)</div>
</div>
<div class="control-group">@Html.LabelFor(model => model.Datum, new { @class = "control-label" })
<div class="controls">@* Aufruf des Custom Templates "Custom.Date" für unser Datum. *@ @Html.EditorFor(model => model.Datum, "Custom.Date") @Html.ValidationMessageFor(model => model.Datum)</div>
</div>
<pre>

Oder man kann bereits im Model festlegen welches Custom Template genutzt werden soll mittels des Attributs “UIHint”

[HtmlTag(Autofocus = true)]
[DataType(DataType.Date)]
[Display(Name = "Geburtstag")]
[UIHint("Custom.Date")]
public DateTime Geburtsdatum { get; set; }

Aber wie sieht ein Editor Template eigentlich aus? Dies kann sehr unterschiedlich sein, von sehr einfachen Varianten die nur eine zusätzliche CSS Klasse setzten, bis hin zu Templates die direkt DiplayValue und EditorValue anzeigen und in die passende Tagstruktur einbinden.

@* Text.cshtml *@
@model String

@* Einfaches Template was nur den Wert wieder in einer Textbox darstellt *@
@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = "span4"})

Es ist auch möglich nicht “@Html.TextBox” zu nutzen und das komplette Control selbst zu “erstellen” als “input”, hier kommt es ganz darauf an was man machen will und wie viel Aufwand man aufwenden möchte. Auch das direkte Einbinden von JavaScript Code ist kein Problem.

@* Custom.Int32.cshtml *@
@model Int32

@* Im Template direkt eine bestimmte Formatierung angeben *@</pre>
<div class="control-group"><strong>Custom Int32 Template</strong> @Html.Encode(ViewData.ModelMetadata.DisplayName):
<div class="controls">@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue)</div>
</div>
<pre>

2. Einbinden von HTML5 Attributen mit Hilfe eines Attributes im Model

Ich bin häufig vom “EditorFor” gewechselt zu “TextBoxFor” um eigene HTML Attribute angeben zu können wie den Type oder um die Formatierung des Values bestimmen zu können (wie im Beispiel ganz zu beginn). Hier verliert man aber sehr schnell den Überblick wo man was im View gesetzt hat. Hier kann das Direkte Setzten der passenden HTML5 Attribute oder beliebiger anderer Attribute direkt im Model weiterhelfen.

Die Umsetzung, dazu ist letztendlich sehr einfach, denn man muss in seiner Attributklasse nur das Interface “IMetadataAware” einbinden und die dazu gehörende Methode “OnMetadataCreated(ModelMetadata metadata)” einbinden und schon werden von MVC die passenden Werte auch im Template zur Verfügung gestellt.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class HtmlTagAttribute : Attribute, IMetadataAware
{
    /// 
    /// String der den Key für die "AdditionalValues" Collection enthält in der unsere Attributliste liegt.
    /// 
    public const string HtmlTagAdditionalValuesKey = "HtmlTagAdditionalValues";

    /// 
    /// Wird beim Erstellen der Metadaten für Unser Model aufgerufen und setzt in der passenden Collection
    /// unsere AttributCollection auf die wir dann in den Templates zugreifen können.
    ///  
    /// 
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        //metadata.TemplateHint = "Strings";
        metadata.AdditionalValues[HtmlTagAdditionalValuesKey] = OptionalAttributes();
    }

    /// 
    /// Maximale Länge des Eingabefeldes
    /// 
    public int MaxLength { get; set; }

    /// 
    ///  The min and max attributes works with the following input 
    ///  EHtmlInputTypes: number, range, date, datetime, datetime-local, month, time and week.
    /// 
    public int Min { get; set; }

    /// 
    /// Gibt an ob ein MinValues angegeben wurde
    /// 
    public bool HasMinValues { get; set; }

    /// 
    ///  The min and max attributes works with the following input
    ///  EHtmlInputTypes: number, range, date, datetime, datetime-local, month, time and week.
    /// 
    public int Max { get; set; }

    /// 
    /// The pattern attribute works with the following input types: text, search, url, tel, email, and password.
    /// e.g.: pattern="[A-Za-z]{3}"
    /// 
    public string Pattern { get; set; }

    /// 
    /// The hint is displayed in the input field when it is empty, and disappears when the field gets focus.
    /// 
    public string Placeholder { get; set; }

    /// 
    /// Gibt an ob es sich nur um ein Readonly Feld handelt.
    /// 
    public bool ReadOnly { get; set; }

    /// 
    /// Gibt an ob das Feld Disabled sein soll
    /// 
    public bool Disabled { get; set; }

    /// 
    /// Specifies the shortcut key to activate/focus the element
    /// 
    public string AccessKey { get; set; }

    public int TabIndex { get; set; }
    public string CssClass { get; set; }

    /// 
    /// Gibt an ob auf das Element der Autofokus beim Pageload gesetzt werden soll
    /// Achtung sollte nur einmal Pro "seite" / "Model" verwendet werden.
    /// 
    public bool Autofocus { get; set; }

    /// 
    /// Der Jeweilige Inputtype, hier muss selbst darauf geachtet
    /// werden das dieser nicht mit dem .NET Inputtype "kollidiert"
    ///  und zusammenpasst. - HtmlInputTypes Klasse enthält alle gängigen Input Types
    /// 
    public string InputType { get; set; }

    /// 
    /// Erstellt aus den jeweils gesetzten Attributen eine Liste aus Attributname und Value.
    /// 
    /// 
    public Dictionary OptionalAttributes()
    {
        var options = new Dictionary();

        if (MaxLength != 0)
        {
            options.Add("maxlength", MaxLength);
        }

        if (ReadOnly)
        {
            options.Add("readonly", "readonly");
        }

        if (Disabled)
        {
            options.Add("disabled", "disabled");
        }

        if (Autofocus)
        {
            options.Add("autofocus", null);
        }

        if (!string.IsNullOrWhiteSpace(AccessKey))
        {
            options.Add("accessKey", AccessKey);
        }

        if (TabIndex != 0)
        {
            options.Add("tabindex", TabIndex);
        }

        if (!string.IsNullOrWhiteSpace(CssClass))
        {
            options.Add("class", CssClass);
        }

        if (!string.IsNullOrWhiteSpace(InputType))
        {
            //Der Name des Enums entspricht dem Type direkt.
            options.Add("type", InputType);
        }

        if (HasMinValues)
        {
            options.Add("min", Min);
        }

        if (Max > 0)
        {
            options.Add("max", Max);
        }

        if (!string.IsNullOrEmpty(Pattern))
        {
            options.Add("pattern", Pattern);
        }

        if (!string.IsNullOrEmpty(Placeholder))
        {
            options.Add("placeholder", Placeholder);
        }

        return options;
    }
}

Da wir die Werte die wir als Attribute in unser HTML Element aufnehmen wollen als KeyValuePair<string,object> Collection benötigen, gibt es in unserer Attributklasse die Funktion “OptionalAttributes()” die aus unseren Gesetzten Werten die passende Collection baut.

/// 
/// Die Input Types die wir zur Verfügung stellen.
/// 
public static class HtmlInputTypes
{
    public const string Text = "text";
    public const string Password = "password";
    public const string Datetime = "datetime";
    public const string Date = "date";
    public const string Month = "month";
    public const string Time = "time";
    public const string Week = "week";
    public const string Number = "number";
    public const string Range = "range";
    public const string Email = "email";
    public const string Url = "url";
    public const string Tel = "tel";
};

Verwendet werden kann das ganze dann z.B. folgendermaßen in unserem Model. Es muss nur auf die Verwendung des Properties “InputType” unseres Attributs aufgepasst werden, oder eine entsprechende Logik in den jeweiligen Controls eingebunden werden.

[HtmlTag(Autofocus = true, InputType = HtmlInputTypes.Time)]
[DataType(DataType.Time)]
[Display(Name = "Geburtszeit")]
public DateTime BirthTime { get; set; }

[HtmlTag(MaxLength = 5, Min=10, Max=50000, CssClass = "input-xxlarge")]
[Display(Name = "Geld in Euro")]
public int Geld { get; set; }

Und umgesetzt wird das ganze dann entsprechend in jedem Template wo die Attribute gesetzt werden sollen. Wir können auch im Template noch bestimmte Werte für das jeweilige Template automatisch setzten lassen oder neue hinzufügen.

@model DateTime
@using Mvc4WebApiKoTb.Helpers.MvcBasic

@{
    var values = new Dictionary();
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey(HtmlTagAttribute.HtmlTagAdditionalValuesKey))
    {
        values = (Dictionary)ViewData.ModelMetadata.AdditionalValues[HtmlTagAttribute.HtmlTagAdditionalValuesKey];
    }
    
}

@Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, values)
@* Custom.Date.cshtml *@
@model DateTime
@using Mvc4WebApiKoTb.Helpers.MvcBasic

@{
    var values = new Dictionary();
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey(HtmlTagAttribute.HtmlTagAdditionalValuesKey))
    {
        values = (Dictionary)ViewData.ModelMetadata.AdditionalValues[HtmlTagAttribute.HtmlTagAdditionalValuesKey];
    }

    var anzeige = string.Empty;
    if (ViewData.TemplateInfo.FormattedModelValue != null)
    {
        DateTime datum = (DateTime) ViewData.TemplateInfo.FormattedModelValue;
        //Hier wird das übergebene Datum direkt Formatiert ausgegeben
        anzeige = datum.ToString("dd.MM.yyyy");
    }
}

<strong>Custom Date Value</strong> 
@Html.TextBox("", anzeige, values)

Damit haben wir ein eigenes Template für DateTime, welches automatisch die passenden Attribute unserem HTML Element hinzufügt oder sogar das Datum selbst formatiert.

3. Quellen und Code auf Codeplex

http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html
http://stackoverflow.com/questions/7313284/how-can-editor-templates-display-templates-recognize-any-attributes-assigned-t
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687

Codeplex:

Dann unter “Source Code” –> “Browse” –> “Testprojekte” –> “Mvc4WebApiKoTb” hier findet Ihr dann die Wichtigsten Dateien im Webprojekt unter “Views/Testtemplate”, “Models/Testtemplates” und “Helpers/MvcBasic”. Das Projekt sollte auch so lauffähig sein.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s