Aufgeteiltes Datumsfeld mit ASP.NET MVC Editor Templates
In vielen Projekten gibt es die Anforderung dass der User im Registrierungsformular ein Datum nicht in ein Textfeld eingeben sondern aus 3 Dropdownlisten für Tag, Monat und Jahr auswählen soll. Mit etwas Code und einem eigenen Editor Template für ASP.NET MVC 2 gestaltet sich die Umsetzung sehr einfach und elegant.
Den Anfang machen zwei kleine Hilfklassen: die SplittedDate und die SplittedDateListHelper Klasse. Die erste definiert das Custom Date Format für das aufgeteilte Datum. Auf diese Klasse wird später der Modelbinder die Auswahl der einzelnen Dropdowns mappen.
public class SplittedDate {
public int Day { get; set; }
public int Month { get; set; }
public int Year { get; set; }
public SplittedDate() : this(DateTime.Now) {}
public SplittedDate(DateTime birthday)
: this(birthday.Day, birthday.Month, birthday.Year) {}
public SplittedDate(int day, int month, int year) {
Day = day;
Month = month;
Year = year;
}
public DateTime AsDateTime() {
return new DateTime(Year, Month, Day);
}
public override string ToString() {
return new StringBuilder()
.Append(Day)
.Append(".")
.Append(Month)
.Append(".")
.Append(Year).ToString();
}
}
Die zweite Klasse ist ein ViewHelper und erzeugt Listen für den Tag (1 -31), den Monat (Januar – Dezember) und das Jahr (100 Jahre). Da die einzelnen Listen bereits SelectListItems enthalten, können sie ganz einfach mit dem MVC eigenen ViewHelper for Dropdowns verwendet werden.
public class SplittedDateListHelper {
private static IList<SelectListItem> _days;
private static IList<SelectListItem> _months;
private static IList<SelectListItem> _years;
public static IList<SelectListItem> Days {
get {
if (_days == null) {
_days = new List<SelectListItem> {
new SelectListItem { Text = "Tag", Value = "-1" }};
for (var i = 1; i <= 31; i++) {
_days.Add(new SelectListItem {
Text = i.ToString(), Value = i.ToString()});
}
}
return _days;
}
}
public static IList<SelectListItem> Months {
get {
if (_months == null) {
_months = new List<SelectListItem> {
new SelectListItem {Text = "Monat", Value = "-1"},
new SelectListItem {Text = "Januar", Value = "1"},
new SelectListItem {Text = "Februar", Value = "2"},
new SelectListItem {Text = "März", Value = "3"},
new SelectListItem {Text = "April", Value = "4"},
new SelectListItem {Text = "Mai", Value = "5"},
new SelectListItem {Text = "Juni", Value = "6"},
new SelectListItem {Text = "Juli", Value = "7"},
new SelectListItem {Text = "August", Value = "8"},
new SelectListItem {Text = "September", Value = "9"},
new SelectListItem {Text = "Oktober", Value = "10"},
new SelectListItem {Text = "November", Value = "11"},
new SelectListItem {Text = "Dezember", Value = "12"}
};
}
return _months;
}
}
public static IList<SelectListItem> Years {
get {
if (_years == null) {
_years = new List<SelectListItem> {
new SelectListItem { Text = "Jahr", Value = "-1" } };
var currentYear = DateTime.Now.Year;
for (var i = currentYear; i >= currentYear - 100; i--) {
_years.Add(new SelectListItem { Text = i.ToString(), Value = i.ToString() });
}
}
return _years;
}
}
}
Doch wie erhält man nun die 3 gewünschten Dropdown-Listen im Eingabeformular? Hierfür bietet ASP.NET MVC 2 mit den Editor Templates eine einfache und elegante Lösung an. Editor Templates sind im Grunde Partial Views für einen bestimmten Objekttyp und erlauben es dem Entwickler sehr genau festzulegen wie und welche Eingabeelemente (eben der Editor) für den Objekttyp gerendert werden sollen. Eine hervorragende Einführung zu den Templates in ASP.NET MVC 2 gibt Brad Wilson in seinem Blog. Um nun ein Editor Template für die SplittedDate Klasse zu erstellen, wird im Ordner ~/Views/Shared/EditorTemplates eine Partial View namens SplittedDate.ascx mit folgendem Inhalt angelegt:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="TestProject.Web.Mvc.Helper" %>
<p class="splitDate-field">
<label for="DayOfBirth_Day">
Tag</label>
<select id="DayOfBirth_Day" name="DayOfBirth.Day">
<% foreach (var day in SplittedDateListHelper.Days)
{ %>
<option value="<%= day.Value %>">
<%= day.Text %></option>
<% } %>
</select>
</p>
<p class="splitDate-field">
<label for="DayOfBirth_Month">
Monat</label>
<select id="DayOfBirth_Month" name="DayOfBirth.Month">
<% foreach (var month in SplittedDateListHelper.Months)
{ %>
<option value="<%= month.Value %>">
<%= month.Text %></option>
<% } %>
</select>
</p>
<p class="splitDate-field">
<label for="DayOfBirth_Year">
Jahr</label>
<select id="DayOfBirth_Year" name="DayOfBirth.Year">
<% foreach (var year in SplittedDateListHelper.Years)
{ %>
<option value="<%= year.Value %>">
<%= year.Text %></option>
<% } %>
</select>
</p>
<br class="clear">
Der Code im Template ist nichts umwerfend Neues. Man erzeugt lediglich für jedes Property der SplittedDate Klasse eine entsprechende Dropdown Liste, wobei man darauf achten muss im jeweiligen name Attribute des select Tags die richtige Property zu verwenden, da so der Model Binder die Auswahl automatisch auf das entsprechende Objekt im ViewModel mappen kann z.B. für das Jahr <select id=”DayOfBirth_Year” name=”DayOfBirth.Year”>.
Nachdem nun alle Bausteine vorhanden sind, ist es an der Zeit sie zu einem großen Ganzen zusammenzufügen. Angenommen man benötigt das aufgeteilte Datum für die Angabe des Geburtstages in einem Registrierungsformular. So würde man sich zunächst ein ViewModel ähnlich dem folgenden für das Formular bauen.
public class CreateAccountViewModel {
[Required(ErrorMessage = "Darf nicht leer sein.")]
public string Alias { get; set; }
[Required(ErrorMessage = "Darf nicht leer sein.")]
public string Password { get; set; }
[Required(ErrorMessage = "Darf nicht leer sein.")]
public string ConfirmPassword { get; set; }
[Required, ValidateSplittedDatetime]
public SplittedDate DayOfBirth { get; set; }
}
Wie man sieht findet hier das neue SplittedDate Objekt Anwendung. Im nächsten Schritt würde man das dazugehörige Formular bauen. (Ich zeige es hier nur auszugsweise]
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TestProject.Web.Mvc.Models.CreateAccountViewModel>" %>
<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="MainContent">
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm("Create", "Account", FormMethod.Post, new { id = "signup" }))
{ %>
<fieldset>
<table>
<tbody>
<tr>
<th>
<label for="Alias">
Benutzername</label>
</th>
<td class="form-field">
<%= Html.EditorFor(m => m.Alias) %>
</td>
<td>
<div class="form-hint">
<span class="hint-text">Wählen Sie einen Benutzernamen</span>
<%= Html.ValidationMessageFor(m => m.Alias) %>
</div>
</td>
</tr>
...
<tr>
<th>
<label for="DayOfBirth">
Geburtstag</label>
</th>
<td class="form-field">
<%= Html.EditorFor(m => m.DayOfBirth) %>
</td>
<td>
<div class="form-hint">
<span class="hint-text">Ihr Geburtstag</span>
<%= Html.ValidationMessageFor(m => m.DayOfBirth) %>
</div>
</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" id="submitForm" class="formButton white" value="Registieren" />
</p>
</fieldset>
</asp:Content>
Und so sieht schlußendlich das Ergebnis aus:
