ASP.NET 7 had most issues from ASP.NET 6 resolved. But, for Web frontend, you still need to be careful, especially with date picker components.
Introduction
DateOnly
was introduced in .NET 6, however without proper serialization and binding for ASP.NET applications. To utilize DateOnly
in ASP.NET 6 applications, you have to do some extra programming as discussed in "DateOnly in .NET 6 and ASP.NET Core 6". ASP.NET 7 released on 8th November, 2022 had most issues resolved. However, for Web frontend, you still need to be careful, especially with date picker components. Depending on your design approaches, you may still need Fonlow.DateOnlyJsonConverter.
Issues
HTML provides a native date input:
<input type="date" id="start" name="trip-start"
value="2022-10-23" min="2022-01-01" max="2023-06-31">
The value of the input is a string
representing a date in YYYY-MM-DD format, or empty. If you use the date
input in a JavaScript application with binding like this one in an Angular 2+ app:
<input type="date" id="start" name="trip-start"
[(ngModel)]="d" min="2021-01-01" max="2023-06-30">
The object assigned to variable "d
" in JavaScript is of the string
type, rather than the Date
type. "YYYY-MM-DD
" is good for deserialization of binding in ASP.NET 7 backend, however, not straightforward for client side calculation, since you have to convert it to a Date
object first.
Some Web UI libraries like Angular Material Components suites provide a beautiful date picker transforming string
"YYYY-MM-DD
" to a Date object which stores UTC value, and this is straightforward for client side calculation. When the date
object is serialized in an AJAX call by HttpClient
of Angular
, the value is serialized into a ISO 8601 datetime string
. For example, picking "2022-12-25
" may result in "2022-12-24T14:00:00.000Z
" while the local time representation of the date object is "Sun Dec 25 2022 00:00:00 GMT+1000 (Australian Eastern Standard Time)
", however, ASP.NET 7 binding will fail in some cases.
Case 1: DateOnly in POST
Without the converter, ASP.NET 7 binding parses "2022-12-24T14:00:00.000Z
" and give a DateOnly.MinValue (0001-01-01)
value in PostDateOnly()
to the body of the Web API function and return an empty string (empty JSON string object, status 200), or give a null
in PostDateONlyNullable()
and return empty body (status 204) in the HTTP response.
[HttpPost]
[Route("ForDateOnly")]
public DateOnly PostDateOnly([FromBody] DateOnly d)
{
return d;
}
[HttpPost]
[Route("DateOnlyNullable")]
public DateOnly? PostDateOnlyNullable([FromBody] DateOnly? d)
{
return d;
}
Case 2: DateOnly as a Property of a Complex Object in POST
If the expected DateOnly
object is of a nested property of a complex object, ASP.NET 7 binding will fail entirely and interpret the complex object as null
.
API:
[HttpPost]
[Route("createPerson")]
public long CreatePerson([FromBody] Person p)
{
Debug.WriteLine("CreatePerson: " + p.Name);
...
}
POST Payload:
{
"name": "John Smith1668562590592",
"givenName": "John",
"surname": "Smith",
"dob": "1969-12-28T00:00:00.000Z",
"baptised": "1980-01-31T00:00:00.000Z"
}
Using Angular Material Component Suite
DatePicker of Angular Material Component Suite could be customized through multiple means. By default, the DatePicker
components will give data as described above, causing the backend to get yesterday's date.
Rather than using the default NativeDateAdapter
, MomentDateAdaptor
is more comprehensive for cross-timezone applications.
import { MAT_MOMENT_DATE_ADAPTER_OPTIONS, MAT_MOMENT_DATE_FORMATS,
MomentDateAdapter } from '@angular/material-moment-adapter';
{ provide: DateAdapter, useClass: MomentDateAdapter,
deps: [MAT_DATE_LOCALE, MAT_MOMENT_DATE_ADAPTER_OPTIONS] },
{ provide: MAT_DATE_FORMATS, useValue: MAT_MOMENT_DATE_FORMATS },
{ provide: MAT_MOMENT_DATE_ADAPTER_OPTIONS, useValue: { useUtc: true, strict: true } },
Picking date 2022-12-25
will result in a date object "Sun Dec 25 2022 00:00:00 GMT+0000
" and the serialized value is "2022-12-25T00:00:00.000Z
".
ASP.NET 6 application codes with Fonlow.DateOnlyJsonConverter
and ASP.NET 7 application codes without the converter can work perfectly with such Angular SPA.
If you have ASP.NET 6 codes following the advices in "DateOnly in .NET 6 and ASP.NET Core 6" and have just upgraded to ASP.NET 7, the legacy codes should be working right away while having the converter. The converter is compatible with the new DateOnly
handing in ASP.NET 7. However, you may consider the converter is obsolete, and remove it from your ASP.NET 7 startup codes, then test respective test cases to ensure really nothing is broken.
Points of Interest
When developing Web applications dealing with date only info like date of birth, you need to consider the following:
- HTML
date
input gives a string
representing a date in YYYY-MM-DD
. - JavaScript
date
object stores UTC datetime
. - Some date picker components like Angular Material
DatePicker
give a JavaScript date
object which value may vary depending on which DateAdapter
to use, with respective options. - Whether the client calls should contain timezone info, for example, through the HTTP request headers.
You had better test some of the following cases, and also make the client and the server sit in different timezones. For example, I am in Australia UTC+10 timezone, and have a server at UTC-10 timezone, so I have a 20 hours window to test cross-timezone issues between the clients and the service.
Test Cases
Service:
[HttpPost]
[Route("ForDateOnly")]
public DateOnly PostDateOnly([FromBody] DateOnly d)
{
return d;
}
JS Client:
it('postDateOnly', (done) => {
const dt = new Date(Date.parse('2018-12-25'));
service.postDateOnly(dt).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-25');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postDateOnlyWithUtc', (done) => {
const dt = new Date(Date.parse('2018-12-25T00:00:00.000Z'));
service.postDateOnly(dt).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-25');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postDateOnlyWithAusMidnight', (done) => {
const dt = new Date(Date.parse('2018-12-24T14:00:00.000Z'));
service.postDateOnly(dt).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-24');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postDateOnlyText', (done) => {
let obj: any = '2018-12-25';
service.postDateOnly(obj).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-25');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postDateOnlyUtcText', (done) => {
let obj: any = '2018-12-25T00:00:00.000Z';
service.postDateOnly(obj).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-25');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
it('postDateOnlyAusMidnightText', (done) => {
let obj: any = '2018-12-24T23:00:01.001Z';
service.postDateOnly(obj).subscribe(
data => {
const v: any = data;
expect(v).toEqual('2018-12-24');
done();
},
error => {
fail(errorResponseToString(error));
done();
}
);
}
);
Hints
.NET 7 provides a helper class DateOnlyConverter. I would be looking forward to someone writing an article about where to utilize it.
History
- 16th November, 2022: Initial version