Bei der Entwicklung einer ASP .NET MVC Anwendung für einen Kunden gab es kürzlich die Anforderung einen Dezimalwert aus der Datenbasis in der UI im Format “HH:MM” anzuzeigen und zu validieren.
Dabei handelt es sich nicht um eine uhrzeitbezogene Umwandlung mit max. 24 Stunden und 59 Minuten. Die Konvertierung in DateTime und die Validierung über Data Annotation [DataType(DataType.DateTime)] wäre in diesem Fall noch relativ einfach.
In meinem Szenario soll ein Dezimalwert von bspw. 135,75 Stunden in das Format 135:45 umgewandelt werden. Nach erfolgloser Recherche nach einer Standardlösung für dieses Problem habe ich meinen eigenen Ansatz umgesetzt.
Mein Lösungsansatz besteht aus einem Datentransformationscontainer, der immer den einen Wert (HH:MM-Formatstring oder Decimal) entgegennimmt und umgehend den jeweiligen Gegenwert aktualisiert.
Der Datentransformationscontainer hat zwei Eigenschaften, eine für den Wert als String und eine für den Wert als Decimal. Für beide Eigenschaften gibt es nun Getter, Setter und einen Konstruktor. Immer wenn eine Eigenschaft gesetzt wird – egal ob im Setter oder im Konstruktor – wird anschließend immer direkt der andere Datentyp über eine Konvertierungslogik mit dem Wert belegt.
'Dieser Daten-Transformations Container erlaub eine Transformation des Decimal-Wertes aus dem Backend 'in einen speziellen Formatstring (HH:MM) Public Class DecimalFormatStringData Private _decimalVal As Decimal Private _formatString As String Private _errrorMsg As String Public Sub New() _formatString = String.Empty End Sub Public Sub New(decimalVal As Decimal) _decimalVal = decimalVal UpdateFromDecimal() End Sub Public Sub New(formatString As String) _formatString = formatString UpdateFromFormatString() End Sub Public Property DecimalValue As Decimal Get Return _decimalVal End Get Set(value As Decimal) _decimalVal = value UpdateFromDecimal() End Set End Property <RegularExpression("^[-]*([0-9]|[0-9][0-9]|[0-9][0-9][0-9]):[0-5][0-9]$", ErrorMessage:="Ungültiges Format: Erwartet wird 'HH:MM'!")> Public Property FormatString As String Get Return _formatString End Get Set(value As String) _formatString = value UpdateFromFormatString() End Set End Property 'Passt den Formatstring an den decimalwert an Private Sub UpdateFromDecimal() _formatString = DecimalToHourMinuteFormatString(_decimalVal) End Sub 'Passt den Decimalwert an den Formatstring an Private Sub UpdateFromFormatString() If _formatString IsNot Nothing Then Dim invalidMsg = "Der Eingabewert entspricht nicht dem erwarteten Format:'Stunden:Minuten' oder 'Stunden' " Dim hours = -1 Dim minutes = -1 If _formatString.Contains(":") Then Dim split = _formatString.Split({":"}, StringSplitOptions.RemoveEmptyEntries) If split.Length >= 2 Then If Int32.TryParse(split(0), hours) AndAlso Int32.TryParse(split(1), minutes) Then Dim span = New TimeSpan(hours, minutes, 0) _decimalVal = Convert.ToDecimal(span.TotalHours) Else _errrorMsg = invalidMsg End If Else _errrorMsg = invalidMsg End If Else 'prüfen, ob nur die volle Stunde angegeben wurde If Int32.TryParse(_formatString, hours) Then Dim span = New TimeSpan(hours, 0, 0) _decimalVal = Convert.ToDecimal(span.TotalHours) Else _errrorMsg = invalidMsg End If End If End If End Sub Public Shared Function DecimalToHourMinuteFormatString(decimalVal As Decimal) As String Dim hours = Math.Floor(decimalVal) Dim minutes = (decimalVal - hours) * 60 Return String.Format("{0:00}:{1:00}", hours, minutes) End Function End Class
Soweit zum logischen Herzstück der Lösung; aber wie gelangt nun der Dezimalwert aus dem Backend als Formatstring in die View und umgekehrt?
Die Lösung besteht im Einsatz von Editor- und Displaytemplates. Definiert man so ein Template für einen Datentyp, wird es automatisch für die Darstellung in der UI bei Verwendung von “@Html.EditorFor” oder “@Html.DisplayFor” verwendet.
Hier der Code aus dem Editortemplate:
@ModelType Demo.MvcApplication.Models.DecimalFormatString.DecimalFormatStringData @Html.EditorFor(Function(m) m.FormatString) @Html.ValidationMessageFor(Function(m) m.FormatString)
Man erkennt, dass im Template auf die Eigenschaft “FormatString” zugegriffen wird. Aus diesem Grund wird in der UI der Formatstring (HH:MM) ausgegeben.
Das Viewmodel enthält nun anstatt des Dezimalwertes eine Eigenschaft vom Typ “DecimalFormatStringData” (den Datentransformationscontainer).
<Display(Name:="Umfang gesamt (HH:MM)")> Public Property UmfangGesamtDecimalFormatData As DecimalFormatStringData
Die Zuweisung des Dezimalwertes aus dem Backend an das Viewmodel sieht wie folgt aus.
If dao.UmfangStundenWoche Is Nothing Then model.UmfangStdProWocheDecimalFormatData = New DecimalFormatStringData() Else model.UmfangStdProWocheDecimalFormatData = New DecimalFormatStringData(src.UmfangStundenWoche.Value) End If
In der View selbst wird nun lediglich der HTML Helper “EditorFor” auf die Eigenschaft des Viewmodel angewendet.
@Html.EditorFor(Function(model) model.UmfangStdWocheDecimalFormatData)
Falls Ihr mal ein ähnliches Problem habt, hoffe ich, dass Euch der Lösungsansatz weiterhelfen konnte.