65.9K
CodeProject 正在变化。 阅读更多。
Home

在带有 JavaScript 客户端的 ASP.NET 7 中使用 DateOnly

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2022 年 11 月 16 日

CPOL

4分钟阅读

viewsIcon

9931

解决 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 调用中由 AngularHttpClient 序列化时,该值被序列化为 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 组件将提供如上所述的数据,导致后端获取昨天的日期。

与其使用默认的 NativeDateAdapterMomentDateAdaptor 更适合跨时区应用程序。

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 对象存储 UTC datetime
  • 一些日期选择器组件,如 Angular Material DatePicker,提供一个 JavaScript date 对象,其值可能因使用哪个 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 日:初始版本
© . All rights reserved.