Closed Korayem closed 8 years ago
There are two issues here:
DateTimeOffset
is indeed not supported datatype at client-side, neither at server-side to some extent, mainly parser does not respect any overloaded operators of non-primitive types,Solution can be as follows:
provide some wrapper for additional fields used in expressions:
public class DateTimeOffsetModel
{
private DateTimeOffset _date;
public DateTimeOffsetModel(DateTimeOffset date)
{
_date = date;
}
public long UtcDateTime => _date.UtcDateTime.Ticks; // use primitive long type (Ticks)
// instead of DateTimeOffset itself
}
complete the main model:
public class Model
{
public Model()
{
StartDate = new DateTimeOffsetModel(new DateTimeOffset(DateTime.Now)); // initialize
EndDate = new DateTimeOffsetModel(new DateTimeOffset(DateTime.Now.AddDays(1)));
}
public DateTimeOffsetModel StartDate { get; set; }
public DateTimeOffsetModel EndDate { get; set; }
[AssertThat("StartDate.UtcDateTime > EndDate.UtcDateTime")]
// ...
add new template under Views -> Shared -> DisplayTemplates named Offset.cshtml:
@model Namespace.Models.DateTimeOffsetModel
@Html.HiddenFor(m => m.UtcDateTime)
in your main view construct proper HTML:
@Html.DisplayFor(model => model.StartDate, "Offset") // point the appropriate template
@Html.DisplayFor(model => model.EndDate, "Offset")
Side note: alternative to providing the template name explicitly in the view, would be either the usage of UIHint
attribute at the model property, or naming the template with the respective datatype name (to be implicitly matched), i.e. DateTimeOffsetModel.cshtml in this particular case.
I am sorry I didn't share the underlyings of my code
All my DateTimeOffset properties use DisplayTemplate:
@model DateTimeOffset?
<span class="datetime">
@(Model != null ? string.Format(ViewData.ModelMetadata.DisplayFormatString ?? "{0:dd/MM/yyyy hh:mm tt}", Model.Value.UtcDateTime, System.Globalization.CultureInfo.InvariantCulture) : string.Empty)
</span>
and EditorTemplate:
@model DateTimeOffset?
@if (Model != null)
{
@Html.TextBox("", Model.Value.LocalDateTime.ToString("dd/MM/yyyy hh:mm tt", System.Globalization.CultureInfo.InvariantCulture), new { @class = "form-control" })
}
else
{
@Html.TextBox("", "")
}
I will try to incorporate your solution into my Templates and Entities and see how it works
Note also that I am using EA directly on my entities without DTOs
Regarding the second issue I've mentioned - since this the commit https://github.com/jwaliszko/ExpressiveAnnotations/commit/96b5caa2d17b5d1bf1cf81a1054950332a65383d, now all the overloaded operators are executed.
Another change - at the client I've just enabled, see https://github.com/jwaliszko/ExpressiveAnnotations/commit/fe913b3094f27665e92449ae91afb248d594cffa, recognition of any ValueParser
attributes applied to nested properties. Due to that there is an alternative solution to that one I've provided above:
provide some wrapper for additional fields used in expressions:
public class DateTimeOffsetModel
{
private DateTimeOffset _date;
public DateTimeOffsetModel(DateTimeOffset date)
{
_date = date;
}
[ValueParser("dateoffset")]
public DateTimeOffset UtcDateTime => _date.UtcDateTime; // see no Ticks,
// the type will be respected now
// by relation operators
}
complete the main model:
same as above
add new template under Views -> Shared -> DisplayTemplates named Offset.cshtml:
same as above
in your main view construct proper HTML:
same as above
extend client-side to respect the ValueParser
:
<script>
ea.addValueParser('dateoffset', function(value) {
return date.Parse(value); // fragile, is the serialized string properly parsed?
});
Side note: alternative to providing custom value parser name explicitly via attribute, would be the usage of relevant type name when registered at client, i.e. ea.addValueParser('datetimeoffset', ...
. In such case name appointed by ValueParser
attribute still has higher priority when interpreted at client, but it is redundant functionality in most cases (so attribute can be removed).
One more comment regarding your HTML. Due to the fact you're not using the HTML helpers, HTML is incorrect. EA cannot identify certain fields, and the same the script cannot create a proper model (for the expression to be evaluated against).
If you're creating the HTML without the helpers you should provide input fields with correct names - property paths - which appear in the expressions. In this particular example it could look like this:
<input name="StartDate.UtcDateTime" value="@Model.StartDate.UtcDateTime.ToString("dd/MM/yyyy")">
<input name="EndDate.UtcDateTime" value="@Model.EndDate.UtcDateTime.ToString("dd/MM/yyyy")">
Usage of custom HTML has big advantage here though - you can serialize the date offset to a string which represents RFC 2822 or ISO 8601 format, acceptable by JS date.Parse()
(see the line pointed above as fragile, which is not fragile anymore when proper date format is used).
Thanks @jwaliszko your answers put me in the right directions. But I took a different approach. Here's what I did
In my Entity:
[AssertThat("DateTimeOffsetInFuture(ScheduledDateTime)")]
public DateTimeOffset ScheduledDateTime { get; set; } = DateTimeOffset.UtcNow;
public bool DateTimeOffsetInFuture(DateTimeOffset scheduledDateTime)
{
return DateTimeOffset.UtcNow.LocalDateTime < scheduledDateTime.LocalDateTime;
}
In my ea.methods.js
I added custom parser that parses all properties of type DateTimeOffset
and implemented DateTimeOffsetInFuture()
using moment.js library
$(function () {
ea.addMethod('DateTimeOffsetInFuture', function (scheduledDateTime) {
var dateselected = moment(scheduledDateTime);
return Date.now() < dateselected;
});
//Properly parse DateTimeOffset
ea.addValueParser('datetimeoffset', function (value, field) {
return moment(value, 'DD/MM/YYYY hh:mm a');
});
});
Currently when I do checks like![image](https://cloud.githubusercontent.com/assets/198332/16699126/6cce8628-4552-11e6-9851-efa2de34f165.png)
[AssertThat(" StartDate.UtcDateTime < EndDate.UtcDateTime", ErrorMessage = "Start Date is after End Date")]
it fails on client sideSame error in case I use
EndDate.LocalDateTime