在带有 JavaScript 客户端的 ASP.NET 7 中使用 DateOnly
解决 ASP.NET 7 应用程序中日期选择器组件的问题。
引言
DateOnly
在 .NET 6 中引入,但没有为 ASP.NET 应用程序提供适当的序列化和绑定。要在 ASP.NET 6 应用程序中使用 DateOnly
,您必须进行一些额外的编程,如 ".NET 6 和 ASP.NET Core 6 中的 DateOnly" 中所讨论的那样。 ASP.NET 7 于 2022 年 11 月 8 日发布,解决了大多数问题。但是,对于 Web 前端,您仍然需要小心,尤其是在使用日期选择器组件时。根据您的设计方法,您可能仍然需要 Fonlow.DateOnlyJsonConverter。
问题
HTML 提供一个原生日期输入
<input type="date" id="start" name="trip-start"
value="2022-10-23" min="2022-01-01" max="2023-06-31">
输入的 value 是一个 string
,表示 YYYY-MM-DD 格式的日期,或者为空。如果您在 JavaScript 应用程序中使用 date
输入,并使用如下 Angular 2+ 应用程序中的绑定
<input type="date" id="start" name="trip-start"
[(ngModel)]="d" min="2021-01-01" max="2023-06-30">
分配给 JavaScript 中变量 "d
" 的对象是 string
类型,而不是 Date
类型。"YYYY-MM-DD
" 适用于 ASP.NET 7 后端的反序列化绑定,但对于客户端计算来说并不直接,因为您必须首先将其转换为 Date
对象。
一些 Web UI 库,如 Angular Material Components 套件,提供漂亮的日期选择器,将 string
"YYYY-MM-DD
" 转换为 Date 对象,该对象存储 UTC 值,这对于客户端计算来说很直接。当 date
对象在 AJAX 调用中由 Angular
的 HttpClient
序列化时,该值被序列化为 ISO 8601 datetime string
。 例如,选择 "2022-12-25
" 可能会导致 "2022-12-24T14:00:00.000Z
",而 date 对象的本地时间表示为 "Sun Dec 25 2022 00:00:00 GMT+1000 (澳大利亚东部标准时间)
",但是,ASP.NET 7 绑定在某些情况下会失败。
案例 1:POST 中的 DateOnly
如果没有转换器,ASP.NET 7 绑定会解析 "2022-12-24T14:00:00.000Z
" 并在 Web API 函数的 body 中提供 DateOnly.MinValue (0001-01-01)
值到 PostDateOnly()
,并返回一个空字符串(空的 JSON 字符串对象,状态 200),或者在 PostDateONlyNullable()
中提供 null
,并在 HTTP 响应中返回空 body(状态 204)。
[HttpPost]
[Route("ForDateOnly")]
public DateOnly PostDateOnly([FromBody] DateOnly d)
{
return d;
}
[HttpPost]
[Route("DateOnlyNullable")]
public DateOnly? PostDateOnlyNullable([FromBody] DateOnly? d)
{
return d;
}
案例 2:DateOnly 作为 POST 中复杂对象的属性
如果预期的 DateOnly
对象是复杂对象的嵌套属性,则 ASP.NET 7 绑定将完全失败并将复杂对象解释为 null
。
API
[HttpPost]
[Route("createPerson")]
public long CreatePerson([FromBody] Person p)
{
Debug.WriteLine("CreatePerson: " + p.Name);
...
}
POST 负载
{
"name": "John Smith1668562590592",
"givenName": "John",
"surname": "Smith",
"dob": "1969-12-28T00:00:00.000Z",
"baptised": "1980-01-31T00:00:00.000Z"
}
使用 Angular Material 组件套件
Angular Material Component Suite 的 DatePicker 可以通过多种方式进行自定义。 默认情况下,DatePicker
组件将提供如上所述的数据,导致后端获取昨天的日期。
与其使用默认的 NativeDateAdapter
,MomentDateAdaptor
更适合跨时区应用程序。
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 } },
选择日期 2022-12-25
将导致日期对象 "Sun Dec 25 2022 00:00:00 GMT+0000
",序列化后的值是 "2022-12-25T00:00:00.000Z
"。
使用 Fonlow.DateOnlyJsonConverter
的 ASP.NET 6 应用程序代码和不使用转换器的 ASP.NET 7 应用程序代码可以与此类 Angular SPA 完美配合。
如果您有 ASP.NET 6 代码遵循 ".NET 6 和 ASP.NET Core 6 中的 DateOnly" 中的建议,并且刚刚升级到 ASP.NET 7,则旧代码应该立即运行,同时拥有转换器。转换器与 ASP.NET 7 中的新 DateOnly
处理兼容。但是,您可以认为该转换器已过时,并将其从 ASP.NET 7 启动代码中删除,然后测试相应的测试用例,以确保确实没有问题。
关注点
在开发处理仅日期信息的 Web 应用程序(例如出生日期)时,您需要考虑以下几点
- HTML
date
输入提供一个string
,表示YYYY-MM-DD
格式的日期。 - JavaScript
date
对象存储 UTCdatetime
。 - 一些日期选择器组件,如 Angular Material
DatePicker
,提供一个 JavaScriptdate
对象,其值可能因使用哪个DateAdapter
而异,并具有相应的选项。 - 客户端调用是否应包含时区信息,例如,通过 HTTP 请求标头。
您最好测试以下一些情况,并使客户端和服务器位于不同的时区。 例如,我在澳大利亚 UTC+10 时区,并且有一个 UTC-10 时区的服务器,因此我有 20 个小时的窗口来测试客户端和服务之间的跨时区问题。
测试用例
Service
[HttpPost]
[Route("ForDateOnly")]
public DateOnly PostDateOnly([FromBody] DateOnly d)
{
return d;
}
JS 客户端
it('postDateOnly', (done) => {
const dt = new Date(Date.parse('2018-12-25')); //JS will serialize it
//to 2018-12-25T00:00:00.000Z.
service.postDateOnly(dt).subscribe(
data => {
const v: any = data; //string 2008-12-25
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')); //JS will
//serialize it to 2018-12-25T00:00:00.000Z.
service.postDateOnly(dt).subscribe(
data => {
const v: any = data; //string 2008-12-25
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')); //Angular Material
//DatePicker by default will give this when picking 2018-12-25
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; //string 2008-12-25
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();
}
);
}
);
提示
.NET 7 提供了一个辅助类 DateOnlyConverter。 我希望有人撰写一篇关于在哪里使用它的文章。
历史
- 2022 年 11 月 16 日:初始版本