如何将前端ViewModel映射到后端并实现通用JSON生成?
通用ViewModel JSON生成器实现方案
问题背景
我需要实现一个通用JSON生成器,能够处理前端传入的任意ViewModel类型。已知可以通过System.Type.GetType从字符串获取类型,但不知道如何在当前MediatR+WebAPI的场景中落地。
前端计划发送包含两个要素的数据:一是ViewModel类型的字符串标识,二是带过滤条件的ViewModel数据数组。后端需要基于该数据生成符合自定义规则的JSON(比如根据特性修改属性名称),再转换为其他文件格式。
当前已有代码结构如下:
1. MediatR请求模型
public class GenerateJSONQuery<T> : IRequest<string> { public string TypeOfList { get; set; } public List<T> Data { get; set; } }
2. MediatR处理器
public class GenerateJSONQueryHandler<T> : IRequestHandler<GenerateJSONQuery<T>, string> { private readonly IddeeaODPDbContext _context; private readonly IMapper _mapper; public GenerateJSONQueryHandler(IddeeaODPDbContext context, IMapper mapper) { _context = context; _mapper = mapper; } public async Task<string> Handle(GenerateJSONQuery<T> request, CancellationToken cancellationToken) { // 核心需求:将request.Data转换为指定ViewModel类型的列表,并生成符合自定义规则的JSON } }
3. WebAPI控制器
public class GenerateFilesController : ApiBaseController { public GenerateFilesController(IOptions<AppSettings> appSettings) : base(appSettings) { } [HttpPost] [SwaggerOperation(Tags = new[] { "Administration/Document" })] public async Task<string> List<T>([FromBody] GenerateJSONQuery<T> data, [FromHeader] string Authorization) { return await Mediator.Send(data); } }
4. 示例ViewModel(带自定义特性)
public class ClientNewbornByBirthDateViewModel { [TranslatedFieldName("Identifier", LanguageEnum.EN)] public int Id { get; set; } public string Institution { get; set; } [TranslatedFieldName("Men", LanguageEnum.EN)] public int MaleTotal { get; set; } [TranslatedFieldName("Women", LanguageEnum.EN)] public int FemaleTotal { get; set; } public int Year { get; set; } public int Month { get; set; } }
核心痛点:当前泛型控制器无法自动推断前端传入的ViewModel类型,需要通过反射实现动态类型转换与处理。
解决方案
1. 调整前端传入格式与控制器逻辑
首先替换泛型请求模型为非泛型DTO,避免WebAPI无法解析泛型参数的问题:
// 新的请求DTO public class GenerateJSONRequestDto { // 完整类型名(含命名空间),如"YourNamespace.ClientNewbornByBirthDateViewModel" public string ViewModelTypeName { get; set; } // 前端直接序列化后的ViewModel数组JSON字符串 public string RawDataJson { get; set; } }
修改控制器,通过反射动态构建MediatR请求并发送:
[HttpPost] [SwaggerOperation(Tags = new[] { "Administration/Document" })] public async Task<string> GenerateJson([FromBody] GenerateJSONRequestDto request, [FromHeader] string Authorization) { // 1. 根据类型字符串获取ViewModel类型 var viewModelType = Type.GetType(request.ViewModelTypeName); if (viewModelType == null) throw new ArgumentException("无效的ViewModel类型,请传入完整命名空间的类型名"); // 2. 构建泛型的GenerateJSONQuery实例 var genericQueryType = typeof(GenerateJSONQuery<>).MakeGenericType(viewModelType); var queryInstance = Activator.CreateInstance(genericQueryType); // 3. 为TypeOfList赋值 genericQueryType.GetProperty(nameof(GenerateJSONQuery<object>.TypeOfList))?.SetValue(queryInstance, request.ViewModelTypeName); // 4. 反序列化RawDataJson为目标类型的列表 var listType = typeof(List<>).MakeGenericType(viewModelType); var data = JsonConvert.DeserializeObject(request.RawDataJson, listType); genericQueryType.GetProperty(nameof(GenerateJSONQuery<object>.Data))?.SetValue(queryInstance, data); // 5. 调用MediatR发送请求 var sendMethod = typeof(IMediator).GetMethod(nameof(IMediator.Send))?.MakeGenericMethod(typeof(string)); var result = await (Task<string>)sendMethod.Invoke(Mediator, new[] { queryInstance, CancellationToken.None }); return result; }
2. 完善处理器的自定义JSON生成逻辑
在处理器中,利用自定义JSON契约解析器处理TranslatedFieldName特性,实现属性名称映射:
public async Task<string> Handle(GenerateJSONQuery<T> request, CancellationToken cancellationToken) { // 配置自定义序列化规则 var jsonSettings = new JsonSerializerSettings { ContractResolver = new TranslatedFieldContractResolver() }; // 生成符合要求的JSON return JsonConvert.SerializeObject(request.Data, jsonSettings); } // 自定义契约解析器,处理TranslatedFieldName特性 public class TranslatedFieldContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); // 读取特性并替换属性名 var translatedAttr = member.GetCustomAttribute<TranslatedFieldNameAttribute>(); if (translatedAttr != null) { // 若需多语言支持,可在此处注入IHttpContextAccessor获取当前请求语言,动态选择对应字段名 property.PropertyName = translatedAttr.FieldName; } return property; } }
3. 关键注意事项
- 类型名完整性:前端传入的
ViewModelTypeName必须是完整的类型名,包含命名空间;若类型在外部程序集,还需附加程序集名称(如YourNamespace.ClientNewbornByBirthDateViewModel, YourAssemblyName)。 - 前端序列化:前端需将过滤后的ViewModel数组直接序列化为JSON字符串,避免泛型序列化导致的类型信息丢失。
- 多语言扩展:若需要支持多语言属性名,可在
TranslatedFieldContractResolver中注入IHttpContextAccessor,从请求头或Query参数中获取当前语言,动态匹配TranslatedFieldName特性对应的值。
内容的提问来源于stack exchange,提问作者Lube
相关产品推荐
相关产品推荐

