From 59ba1a61935aecd3a12be4357ca73ac6191db3f4 Mon Sep 17 00:00:00 2001 From: hang <872297557@qq.com> Date: Mon, 19 Jan 2026 13:52:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E5=8F=AF=E7=A9=BA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BC=A0=E5=8F=82=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A2=84=E5=A4=84=E7=90=86=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IRaCIS.Core.API/Progranm.cs | 3 + .../NullToEmptyStringResolver.cs | 69 ++++++++++++++----- .../LegacyController/ModelActionFilter .cs | 64 +++++++++++++++++ IRaCIS.Core.Application/TestService.cs | 40 ++++++++++- 4 files changed, 158 insertions(+), 18 deletions(-) diff --git a/IRaCIS.Core.API/Progranm.cs b/IRaCIS.Core.API/Progranm.cs index 69be6d74f..c80a2171a 100644 --- a/IRaCIS.Core.API/Progranm.cs +++ b/IRaCIS.Core.API/Progranm.cs @@ -96,6 +96,9 @@ builder.Services.AddJsonLocalization(options => options.ResourcesPath = "Resourc // 异常、参数统一验证过滤器、Json序列化配置、字符串参数绑型统一Trim() builder.Services.AddControllers(options => { + // 插到最前,抢在默认绑定器之前 + options.ModelBinderProviders.Insert(0, new NullableStructModelBinderProvider()); + options.Filters.Add(); options.Filters.Add(); options.Filters.Add(); diff --git a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs index 04bb3a2c0..4a636d653 100644 --- a/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs +++ b/IRaCIS.Core.API/_ServiceExtensions/NewtonsoftJson/NullToEmptyStringResolver.cs @@ -3,8 +3,10 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; namespace IRaCIS.Core.API { @@ -51,34 +53,69 @@ namespace IRaCIS.Core.API } public class NullToEmptyStringValueProvider : IValueProvider { - PropertyInfo _MemberInfo; + PropertyInfo _memberInfo; + private readonly bool _isNullable; public NullToEmptyStringValueProvider(PropertyInfo memberInfo) { - _MemberInfo = memberInfo; + _memberInfo = memberInfo; + } + + + + // DTO → JSON 返回前端的时候 处理null 变为"" 方便前端判断 public object GetValue(object target) { - object result = _MemberInfo.GetValue(target); - if (_MemberInfo.PropertyType == typeof(string) && result == null) result = ""; - else if (_MemberInfo.PropertyType == typeof(String[]) && result == null) result = new string[] { }; - //else if (_MemberInfo.PropertyType == typeof(Nullable) && result == null) result = 0; - //else if (_MemberInfo.PropertyType == typeof(Nullable) && result == null) result = 0.00M; + var result = _memberInfo.GetValue(target); + // 检查类型是否为string或string? + if (_memberInfo.PropertyType == typeof(string) && result == null) + { + #region 返回的时候处理 string string? null 为"" 不区分处理 + + + //// 如果是string?类型,返回null 可以做到,但是得反射,因为 string string? 是编译层区分的 + + //var isNullable1 = _memberInfo.CustomAttributes + // .Any(a => a.AttributeType.Name == "NullableAttribute"); + + ////var isNullable2 = _memberInfo.CustomAttributes + //// .Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); + + //if (isNullable1) + //{ + // return result; + //} + + #endregion + + + // 如果是string类型,返回空字符串 + return string.Empty; + } return result; } + + + //影响 模型绑定时接收前端的值,同时影响 正常 JSON 序列化 public void SetValue(object target, object value) { - if (_MemberInfo.PropertyType == typeof(string)) - { - //去掉前后空格 - _MemberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); + _memberInfo.SetValue(target, value); + + + //// 会影响 string? 传递null 变为"" + + //if (_memberInfo.PropertyType == typeof(string)) + //{ + // //去掉前后空格 + // _memberInfo.SetValue(target, value == null ? string.Empty : value.ToString() == string.Empty ? value : value/*.ToString().Trim()*/); + //} + //else + //{ + // _memberInfo.SetValue(target, value); + //} - } - else - { - _MemberInfo.SetValue(target, value); - } } } diff --git a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs index 63255f4fa..abe4800de 100644 --- a/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs +++ b/IRaCIS.Core.Application/BusinessFilter/LegacyController/ModelActionFilter .cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Localization; using Newtonsoft.Json; @@ -28,3 +29,66 @@ public class ModelActionFilter(IStringLocalizer _localizer) : ActionFilterAttrib } } } + + +public class NullableStructModelBinderProvider : IModelBinderProvider +{ + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + // 获取要绑定的模型类型,比如 Guid?, int?, DateTime? 等 + var type = context.Metadata.ModelType; + + // 检查是否是 Nullable 类型 + // 1. 必须是泛型类型 + // 2. 泛型定义必须是 Nullable<> + if (!type.IsGenericType || + type.GetGenericTypeDefinition() != typeof(Nullable<>)) + return null; // 返回 null 表示"我不处理这个类型" + + + // 获取可空类型内部的实际类型,如 Guid?, int? 中的 Guid, int + var innerType = Nullable.GetUnderlyingType(type)!; + + // 创建默认的模型绑定器(系统的原逻辑) + var fallback = context.CreateBinder(context.Metadata); + + // 创建泛型绑定器类型:NullableEmptyStringToNullBinder + // 比如对于 Guid?,会创建 NullableEmptyStringToNullBinder + var binderType = typeof(NullableEmptyStringToNullBinder<>).MakeGenericType(innerType); + + // 实例化绑定器,传入默认绑定器作为备用 + return (IModelBinder)Activator.CreateInstance(binderType, fallback)!; + } +} + +// 泛型约束:T 必须是值类型(struct),这确保了只处理可空值类型 +public class NullableEmptyStringToNullBinder : IModelBinder where T : struct +{ + private readonly IModelBinder _fallbackBinder; + + // 构造函数接收一个备用的绑定器(系统的默认绑定器) + public NullableEmptyStringToNullBinder(IModelBinder fallbackBinder) + { + _fallbackBinder = fallbackBinder; + } + + public async Task BindModelAsync(ModelBindingContext context) + { + // 获取前端传递的值 + var value = context.ValueProvider.GetValue(context.ModelName).FirstValue; + + // 关键逻辑:如果值是空字符串或空白,直接返回 null + if (string.IsNullOrWhiteSpace(value)) + { + // 设置绑定结果为 null(表示空值) + context.Result = ModelBindingResult.Success(null); + return; + } + + // 非空 → 完全走系统原逻辑 + + // 如果不是空字符串,则使用系统的默认绑定逻辑 + // 比如将 "123" 转换为 int? 123,或将 GUID 字符串转换为 Guid? + await _fallbackBinder.BindModelAsync(context); + } +} \ No newline at end of file diff --git a/IRaCIS.Core.Application/TestService.cs b/IRaCIS.Core.Application/TestService.cs index fd751a860..f7c4723e2 100644 --- a/IRaCIS.Core.Application/TestService.cs +++ b/IRaCIS.Core.Application/TestService.cs @@ -39,6 +39,7 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using System.Collections.Concurrent; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Globalization; using System.IO; @@ -77,6 +78,41 @@ namespace IRaCIS.Core.Application.Service public static int IntValue = 100; + public class ModelVerifyCommand + { + public int? IntNUllValue { get; set; } + public int IntValue { get; set; } + + public string StringValue { get; set; } + + public string? StringNUllValue { get; set; } + + public Guid GuidValue { get; set; } = NewId.NextSequentialGuid(); + + public Guid? GuidNUllValue { get; set; } + + [NotDefault] + public Guid GuidValueNotDefault { get; set; } + + + public bool BoolValue { get; set; } + + public bool? BoolNUllValue { get; set; } + + public DateTime DateTimeValue { get; set; } + + public DateTime? DateTimeNUllValue { get; set; } + } + + //创建一个模型验证的方法 + [AllowAnonymous] + [HttpPost] + public async Task PostModelVerify(ModelVerifyCommand modelVerify) + { + return ResponseOutput.Ok(modelVerify); + } + + [AllowAnonymous] public async Task CreatNewDBStruct() @@ -99,10 +135,10 @@ namespace IRaCIS.Core.Application.Service public async Task DeleteCacheDIR() { - var list= _dicomStudyRepository.Where(t => t.StudyDIRPath!="").Select(t => t.StudyDIRPath).ToList(); + var list = _dicomStudyRepository.Where(t => t.StudyDIRPath != "").Select(t => t.StudyDIRPath).ToList(); - await _IOSSService.DeleteObjects(list.Select(t => t.TrimStart('/')).ToList(),true); + await _IOSSService.DeleteObjects(list.Select(t => t.TrimStart('/')).ToList(), true); return ResponseOutput.Ok(); }