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

Autorest生成客户端调用接口 抽象类列表序列化报错

问题描述

在行为测试中调用控制器接口时,使用Postman手动调用可正常运行,但使用Autorest生成的HttpClient调用时失败。已创建专用JsonConverter处理多态序列化逻辑,调用后返回400响应,内容如下:

{
   "errors":{
      "command":[
         "The command field is required."
      ],
      "Sections[0].Title.0":[
         "Could not create an instance of type Section. Type is an interface or abstract class and cannot be instantiated. Path 'Sections[0].Title', line 7, position 14."
      ],
      "Sections[1].Kind.1":[
         "Could not create an instance of type Section. Type is an interface or abstract class and cannot be instantiated. Path 'Sections[1].Kind', line 14, position 13."
      ]
   },
   "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title":"One or more validation errors occurred.",
   "status":400,
   "traceId":"0HMI4P0JALIN1"
}

涉及的模型采用抽象类列表结构定义,相关代码如下:

public enum Kind
{
    CoverPage,
    PageBreak
}
public abstract record Section(Kind Kind, string Name);

public record PageBreak(string Name) : Section(Kind.PageBreak, Name)
{
    public string DisplayedString => _displayedString;
    private static string _displayedString = "............";
}
public record CoverPage(string Name, string Title, string SubTitleFirstLevel, string SubTitleSecondLevel) : Section(Kind.CoverPage, Name);

public record CreateTemplateCommand
{
    [Required]
    public string Name { get; init; }
    [Required]
    public IList<Section> Sections { get; init; }
}

Autorest生成的请求内容与Postman中传入的JSON入参结构完全一致,请求体如下:

{
  "Name": "My Name",
  "Sections": [
    {
      "Title": "Blablabla",
      "SubTitleFirstLevel": "Sous-Titre 1",
      "SubTitleSecondLevel": "Sous-Titre 2",
      "Kind": "CoverPage",
      "Name": "Page de garde"
    },
    {
      "Kind": "PageBreak",
      "Name": "Saut de page"
    }
  ]
}

Autorest生成的接口调用代码如下:

public async Task<HttpOperationResponse> CreateTemplateWithHttpMessagesAsync(int enterpriseId, CreatePlaquetteTemplateCommand body, Dictionary<string, List<string>> customHeaders = null, CancellationToken cancellationToken = default(CancellationToken))
{
    if (body == null)
    {
        throw new ValidationException(ValidationRules.CannotBeNull, "body");
    }
    if (body != null)
    {
        body.Validate();
    }
    // Tracing
    bool _shouldTrace = ServiceClientTracing.IsEnabled;
    string _invocationId = null;
    if (_shouldTrace)
    {
        _invocationId = ServiceClientTracing.NextInvocationId.ToString();
        Dictionary<string, object> tracingParameters = new Dictionary<string, object>();
        tracingParameters.Add("enterpriseId", enterpriseId);
        tracingParameters.Add("body", body);
        tracingParameters.Add("cancellationToken", cancellationToken);
        ServiceClientTracing.Enter(_invocationId, this, "CreateTemplate", tracingParameters);
    }
    // Construct URL
    var _baseUrl = Client.BaseUri.AbsoluteUri;
    var _url = new System.Uri(new System.Uri(_baseUrl + (_baseUrl.EndsWith("/") ? "" : "/")), "api/plaquette/enterprises/{enterpriseId}/Templates").ToString();
    _url = _url.Replace("{enterpriseId}", System.Uri.EscapeDataString(Microsoft.Rest.Serialization.SafeJsonConvert.SerializeObject(enterpriseId, Client.SerializationSettings).Trim('"')));
    // Create HTTP transport objects
    var _httpRequest = new HttpRequestMessage();
    HttpResponseMessage _httpResponse = null;
    _httpRequest.Method = new HttpMethod("POST");
    _httpRequest.RequestUri = new System.Uri(_url);
    // Set Headers


    if (customHeaders != null)
    {
        foreach(var _header in customHeaders)
        {
            if (_httpRequest.Headers.Contains(_header.Key))
            {
                _httpRequest.Headers.Remove(_header.Key);
            }
            _httpRequest.Headers.TryAddWithoutValidation(_header.Key, _header.Value);
        }
    }

    // Serialize Request
    string _requestContent = null;
    if(body != null)
    {
        _requestContent = Microsoft.Rest.Serialization.SafeJsonConvert.SerializeObject(body, Client.SerializationSettings);
        _httpRequest.Content = new StringContent(_requestContent, System.Text.Encoding.UTF8);
        _httpRequest.Content.Headers.ContentType =System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json-patch+json; charset=utf-8");
    }
    // Set Credentials
    if (Client.Credentials != null)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await Client.Credentials.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
    }
    // Send Request
    if (_shouldTrace)
    {
        ServiceClientTracing.SendRequest(_invocationId, _httpRequest);
    }
    cancellationToken.ThrowIfCancellationRequested();
    _httpResponse = await Client.HttpClient.SendAsync(_httpRequest, cancellationToken).ConfigureAwait(false);
    if (_shouldTrace)
    {
        ServiceClientTracing.ReceiveResponse(_invocationId, _httpResponse);
    }
    HttpStatusCode _statusCode = _httpResponse.StatusCode;
    cancellationToken.ThrowIfCancellationRequested();
    string _responseContent = null;
    if ((int)_statusCode != 200 && (int)_statusCode != 201 && (int)_statusCode != 204)
    {
        var ex = new HttpOperationException(string.Format("Operation returned an invalid status code '{0}'", _statusCode));
        if (_httpResponse.Content != null) {
            _responseContent = await _httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
        }
        else {
            _responseContent = string.Empty;
        }
        ex.Request = new HttpRequestMessageWrapper(_httpRequest, _requestContent);
        ex.Response = new HttpResponseMessageWrapper(_httpResponse, _responseContent);
        if (_shouldTrace)
        {
            ServiceClientTracing.Error(_invocationId, ex);
        }
        _httpRequest.Dispose();
        if (_httpResponse != null)
        {
            _httpResponse.Dispose();
        }
        throw ex;
    }
    // Create Result
    var _result = new HttpOperationResponse();
    _result.Request = _httpRequest;
    _result.Response = _httpResponse;
    if (_shouldTrace)
    {
        ServiceClientTracing.Exit(_invocationId, _result);
    }
    return _result;
}

相同入参下Postman调用正常,Autorest生成客户端调用返回400错误,提示无法实例化抽象类Section。

问题根因

核心问题出在Autorest生成代码的请求Content-Type配置错误:

  • 生成的代码强制把请求Content-Type设置为application/json-patch+json,而非标准的application/json。ASP.NET Core会根据请求Content-Type选择对应的输入格式化器:application/json-patch+json会走JSON Patch专用的格式化器,这个格式化器不会加载之前配置的、用于处理Section多态反序列化的自定义JsonConverter,只会用默认逻辑直接反序列化抽象类,自然会抛出无法实例化抽象/接口类型的错误。
  • Postman调用时默认用application/json作为Content-Type,会走配置了自定义多态转换器的JSON格式化器,所以反序列化正常。
  • 响应里连带出现的command字段必填错误,是JSON Patch格式化器解析请求体失败后,模型绑定找不到对应接口参数抛出的连锁错误,不是独立问题。
修复方案
  • 不要直接修改生成的代码(重新生成会被覆盖),在Autorest的生成配置文件中覆写请求Content-Type为application/json,从生成源头修正ContentType配置。
  • 检查Autorest客户端的SerializationSettings配置,把服务端使用的多态JsonConverter注册到该配置的Converters集合中,保证客户端序列化多态类型时的输出逻辑和服务端预期完全一致,避免不同序列化配置导致的结构差异。
  • 修正ContentType后重新生成客户端发起调用,两个400错误会同时消失。

内容的提问来源于stack exchange,提问作者alpachinois

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 06:09:29