.NET Core 3.1 Web API 没有正确往返 DateTime

.NET Core 3.1 Web API does not round trip DateTime properly

我有一个具有 DateTime 属性的 class。一个端点returns这个class,另一个把它作为一个[FromBody]参数。

从 Web API 端点返回此 class 的对象会将其序列化为 JSON,小数点后七位。这是代码。

using System;
using System.Collections.Generic;

namespace Models.SiteData
{
    public class SiteDataRequest
    {
        public SiteDataRequest()
        {
            Responses = new List<SiteData>();
            Metadata = new Metadata();
        }

        public Metadata Metadata { get; set; }
        public List<SiteData> Responses { get; set; }



        private static Random rnd = new Random();
        public void GenerateData(int responseCount, int stationCount, int inversionTowersCount, int equipmentStatusCount)
        {

            var statistics = new Statistics()
            {
                StatusRecords = equipmentStatusCount,
                ExecutionTime = 10,
                ReadingPeriods = responseCount,
                StationRecords = stationCount,
                TowersRecords = inversionTowersCount
            };

            Metadata = new Metadata
            {
                RequestDate = DateTime.Now,
                RequestSite = "TEST_SITE",
                RequirePeriodStart = DateTime.Now.AddMinutes(-30),
                RequirePeriodEnd = DateTime.Now.AddMinutes(30),
                Statistics = statistics,
                TransactionID = Guid.NewGuid().ToString()
            };

            var startTime = DateTime.Now;

            var responseToGenerator = responseCount; 
            for (var responseIndex = 0; responseIndex < responseToGenerator; responseIndex++)
            {

                var response = new SiteData
                {
                    Timestamp = startTime,
                };
                Responses.Add(response);

                var stationsToGenerate = stationCount;
                for (var stationIndex = 0; stationIndex < stationsToGenerate; stationIndex++)
                {
                    var station = new Station
                    {
                        id = $"station_{stationIndex}"
                    };
                    GenerateData(station, Station.Names, 0f, 5000f);
                    response.Stations.Add(station);
                }
                for (var inversionTowerIndex = 0; inversionTowerIndex < inversionTowersCount; inversionTowerIndex++)
                {
                    var tower = new InversionTower
                    {
                        id = $"invtower_{inversionTowerIndex}"
                    };
                    GenerateData(tower, InversionTower.Names, 0f, 5000f);
                    response.InversionTowers.Add(tower);
                }
                for (var equipmentStatusIndex = 0; equipmentStatusIndex < equipmentStatusCount; equipmentStatusIndex++)
                {
                    var equipmentStatus = new EquipmentStatus
                    {
                        id = $"equipstatus_{equipmentStatusIndex}"
                    };
                    GenerateData(equipmentStatus, EquipmentStatus.Names, 0f, 5000f);
                    response.EquipmentStatus.Add(equipmentStatus);
                }

                startTime = startTime.AddSeconds(1);
            }
        }

        private static readonly Random r = new Random();
        internal void GenerateData(ValueRange objectToSet, float minimum, float maximum)
        {
            var minValue = 0;
            var maxValue = (int)rnd.Next(minValue, (int)maximum * 1000);
            var value = r.Next(minValue, maxValue);

            objectToSet.Low = minValue / 1000f;
            objectToSet.High = maxValue / 1000f;

            objectToSet.Value = value / 1000f;
        }
        internal void GenerateData(SensorValue sensorValue, float minimum, float maximum)
        {
            var minValue = (int)minimum * 1000;
            var maxValue = (int)maximum * 1000;
            var value = r.Next(minValue, maxValue);
            sensorValue.Value = value / 1000f;
        }

        internal void GenerateData(Station objectToSet, List<string> names, float minimum, float maximum)
        {
            foreach (var n in names)
            {
                var esv = new SensorValue { Name = n };
                GenerateData(esv, minimum, maximum);
                objectToSet.SensorValues.Add(esv);
            }
        }

        internal void GenerateData(InversionTower objectToSet, List<string> names, float minimum, float maximum)
        {
            foreach (var n in names)
            {
                var esv = new SensorValue { Name = n };
                GenerateData(esv, minimum, maximum);
                objectToSet.SensorValues.Add(esv);
            }
        }

        internal void GenerateData(EquipmentStatus objectToSet, List<string> names, float min, float max)
        {
            foreach (var n in names)
            {
                var value = new ValueRange();
                GenerateData(value, min, max);

                var esv = new EquipmentStatusValue { Name = n, Reading = value };
                objectToSet.Readings.Add(esv);
            }
        }
    }
}
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Npgsql;
using System;
using System.Threading.Tasks;

namespace Controllers
{
    [Route("api/sitedata")]
    [ApiController]
    public class SiteDataApiController : BaseController
    {
        private readonly ILogger logger;
        private readonly IUserService _userService;
        private readonly ISiteVerifier _siteVerifier;
        private readonly ISiteDataPackageRepository _repoSiteDataPackage;
        private readonly SiteDataService _siteDataService;

        public SiteDataApiController(ILogger<SiteDataApiController> logger, ISiteDataPackageRepository repoSiteDataPackage, IUserService userService, ISiteVerifier siteVerifier, SiteDataService siteDataService)
        {
            _siteVerifier = siteVerifier;
            _siteDataService = siteDataService;
            _repoSiteDataPackage = repoSiteDataPackage;
            this.logger = logger;
            _userService = userService;
        }

        [Authorize,HttpPost]
        public async Task<ActionResult> ReceiveData([FromBody] SiteDataRequest siteDataRequest)
        {
            var requestUserId = _userService.GetUserId(HttpContext.User);
            var siteDataResponse = _siteDataService.VerifyData(siteDataRequest);


            if (siteDataResponse.Success)
            {
                logger.LogInformation("SiteData is processed for {site} on {date}", siteDataRequest.Metadata.RequestSite, siteDataRequest.Metadata.RequestDate);
                try
                {
                    var insertResult = await _repoSiteDataPackage.InsertAsync(siteDataRequest);
                    return Ok(insertResult);
                }
                catch (NpgsqlException ex)
                {
                    return TranslateExceptionToResult(ex);
                }
            }
            else
            {
                logger.LogWarning("SiteData is invalid {messages}", siteDataResponse.Messages);
                return BadRequest(siteDataResponse);
            }


        }
        /// <summary>
        /// Do not deliver this in the final project, this is to generate sample data.
        /// </summary>
        /// <returns></returns>
        [Authorize,HttpPost("sample")]
        public SiteDataRequest GenerateSampleData()
        {
            var sd = new SiteDataRequest();
            sd.GenerateData(5, 10, 5, 4);
            return sd;
        }

    }
}

这是您通过调用 /sample 端点获得的 JSON 的开始。

{
  "metadata": {
    "transactionID": "1cac2cff-d28b-457b-8dff-dab05fa751a5",
    "requestDate": "2022-02-16T18:03:36.7186639+10:00",
    "requestSite": "TEST_SITE",
    "requirePeriodStart": "2022-02-16T17:33:36.7187564+10:00",
    "requirePeriodEnd": "2022-02-16T18:33:36.7188009+10:00",

将此提供给 ReceiveData 端点会触发 400 错误。

当我将 JSON 和 trim 日期字符串捕获到三位或更少的小数位时,突然可以解析 JSON。

是什么控制了这个隐式序列化?如果它是配置驱动的,为什么输入和输出不同,因为两个端点在同一个控制器中?

这个.Net serializer is serializing DateTime in different precisions好像有关系。评论,如果不是问题。

要控制框架完成的隐式序列化,请使用自定义 JsonConverter

使用序列化程序或在 Startup.cs 中注册它,使用 AddMvc().AddJsonOptions(...)AddControllers().AddJsonOptions(...)

日期问题似乎与日期的默认序列化有关。

using System;

namespace _deser
{
    class Program
    {
        static void Main(string[] args)
        {
            var d = DateTime.Now;
            Console.WriteLine($"Default: {d.ToString("o")}");
            d = d.ToUniversalTime();
            Console.WriteLine($"Default UTC: {d.ToString("o")}");
            Console.WriteLine($"Custom UTC: {d.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")}");
        }
    }
}

使用 UTC 可以让您使用文字 Z。更简单。

生产

Default serialisation: 2022-02-17T08:40:13.0051941+10:00
Default serialisation of UTC: 2022-02-16T22:40:13.0133860Z
Custom serialisation of UTC: 2022-02-16T22:40:13.013Z

当我着手修复时,我发现有人已经这样做了,但只定义了一个小数点后三位的图片字符串(并且未能控制序列化时产生的位数)。

我想选择这个精度是因为 JavaScript 使用 Unix 时间,它是从 1970-01-01 00:00:00.000Z 纪元开始的毫秒整数计数。

问题在于,当您添加显式图片字符串时,您必须使用 ParseExact,这意味着您必须定义 所有 图片字符串。我找不到处理任意数量位置的方法,所以我为 0 到 7 个位置提供了八个图片字符串。