You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将前端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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 08:20:27