C# WebAPI Entity framework+ModelBinding+JSON UTC-time mapping

Date: 2020-12-04
using System;
using System.Linq;
using EfDataAdapter.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace EfDataAdapter
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.UseEntityTypeConfiguration();
            ApplyUtcDateTimeConverter(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }
        private static void ApplyUtcDateTimeConverter(ModelBuilder modelBuilder)
        {
            var dateTimeConverter = new ValueConverter<DateTime, DateTime>(v => v.ToUniversalTime(), v => DateTime.SpecifyKind(v, DateTimeKind.Utc));
            var dateTimeNullableConverter = new ValueConverter<DateTime?, DateTime?>(v => v.HasValue ? v.Value.ToUniversalTime() : (DateTime?)null, v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : (DateTime?)null);
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTime));
                foreach (var property in properties)
                {
                    modelBuilder.Entity(entityType.Name).Property(property.Name)
                        .HasConversion(dateTimeConverter);
                }
                properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTime?));

                foreach (var property in properties)
                {
                    modelBuilder.Entity(entityType.Name).Property(property.Name)
                        .HasConversion(dateTimeNullableConverter);
                }
            }
        }
    }
}


///////////// Edit Api ModelBinding to ensure UTC by default
public class UtcDateTimeModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var value = bindingContext.ValueProvider
                                  .GetValue(bindingContext.ModelName)
                                  .FirstValue;

        // Geen waarde meegegeven
        if (string.IsNullOrWhiteSpace(value))
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }

        if (DateTime.TryParse(value, out var date))
        {
            bindingContext.Result = ModelBindingResult.Success(DateTimeHelper.EnsureUtcDefaultUtc(date));
        }
        return Task.CompletedTask;
    }
}

public class UtcDateTimeModelBinderProvider : IModelBinderProvider
{
    public IModelBinder? GetBinder(ModelBinderProviderContext context)
    {
        var type = context.Metadata.ModelType;

        if (type == typeof(DateTime) || type == typeof(DateTime?))
        {
            return new UtcDateTimeModelBinder();
        }

        return null;
    }
}

/////////////
using Newtonsoft.Json.Serialization;
namespace WebApi
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers(config =>
            {
                config.ModelBinderProviders.Insert(0, new UtcDateTimeModelBinderProvider());
            })
            .AddNewtonsoftJson(options =>
            {
                options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc;
            });
        }
    }
}
43530cookie-checkC# WebAPI Entity framework+ModelBinding+JSON UTC-time mapping