为啥 autorest 用 Swagger 中的对象替换我的自定义结构?

Posted

技术标签:

【中文标题】为啥 autorest 用 Swagger 中的对象替换我的自定义结构?【英文标题】:Why autorest is replacing my custom struct by an object in Swagger?为什么 autorest 用 Swagger 中的对象替换我的自定义结构? 【发布时间】:2021-09-06 01:51:49 【问题描述】:

我创建了一个自定义的readonlystruct 来定义我称之为TenantId 的不可变值类型:

[DebuggerDisplay("ID=m_internalId.ToString()")]
[JsonConverter(typeof(TenantIdJsonConverter))]
public readonly struct TenantId : IEquatable<TenantId>

    private readonly Guid m_internalId;

    public static TenantId New => new(Guid.NewGuid());

    private TenantId(Guid id)
    
        m_internalId = id;
    

    public TenantId(TenantId otherTenantId)
    
       m_internalId = otherTenantId.m_internalId;
    

    ...

我还定义了一个名为 PurchaseContract 的合同,它是 HTTP 响应的一部分:

[JsonObject(MemberSerialization.OptIn)]
public sealed class PurchaseContract

    [JsonProperty(PropertyName = "tenantId")]
    public TenantId TenantId  get; 
        
    [JsonProperty(PropertyName = "total")]
    public double Total  get; 

最后,我设置了一个 HTTP 触发函数,它将返回一个 PurchaseContract 的实例。目前,已经在ProducesResponseTypeAttribute进行了描述:

[ApiExplorerSettings(GroupName = "Purchases")]
[ProducesResponseType(typeof(PurchaseContract), (int) HttpStatusCode.OK)]
[FunctionName("v1-get-purchase")]
public Task<IActionResult> RunAsync
(
    [HttpTrigger(AuthorizationLevel.Anonymous, "GET", Route = "v1/purchases")]
    HttpRequest httpRequest,
    [SwaggerIgnore] 
            ClaimsPrincipal claimsPrincipal
)

    //  Stuff to do.
    return Task.FromResult((IActionResult)new OkResult());

在我的Startup 课程中,我正在设置这样的招摇:

private static void ConfigureSwashBuckle(IFunctionsHostBuilder functionsHostBuilder)

    functionsHostBuilder.AddSwashBuckle(Assembly.GetExecutingAssembly(), options =>
            
                options.SpecVersion = OpenApiSpecVersion.OpenApi3_0;
                options.AddCodeParameter = true;
                options.PrependOperationWithRoutePrefix = true;
                options.XmlPath = "FunctionApp.xml";
                options.Documents = new []
                
                    new SwaggerDocument
                    
                        Title = "My API,
                        Version = "v1",
                        Name = "v1",
                        Description = "Description of my API",
                    
                ;
            );
        

在swagger UI页面中,我可以看到它看起来不错:

问题

使用 Autorest 创建 C# 客户端时出现意外结果。不知何故,TenantId 结构被删除并替换为object

为什么会这样?我应该怎么做才能自动生成 TenantId,就像客户端中的 PurchaseContract 一样?

详情

这是版本信息。

在netcore3.1上运行的Function App V3; OpenApi 3.0; Autorest Core 3.0.6274、autorest.csharp' (~2.3.79->2.3.91) 和 autorest.modeler' (2.3.55->2.3.55); NuGet 包AzureExtensions.Swashbuckle;

【问题讨论】:

【参考方案1】:

我开始研究Swashbuckle.AspNetCore.SwaggerGen 的源代码,以了解我的readonly struct 是如何被解释的。这一切都发生在JsonSerializerDataContractResolver 类中,在方法@​​987654328@ 中,该方法为提供的类型确定DataContract

public DataContract GetDataContractForType(Type type)

    if (type.IsOneOf(typeof(object), typeof(JsonDocument), typeof(JsonElement)))
    
        ...
    

    if (PrimitiveTypesAndFormats.ContainsKey(type))
    
        ...
    

    if (type.IsEnum)
    
        ...
    

    if (IsSupportedDictionary(type, out Type keyType, out Type valueType))
    
        ...
    

    if (IsSupportedCollection(type, out Type itemType))
    
        ...
    

    return DataContract.ForObject(
        underlyingType: type,
        properties: GetDataPropertiesFor(type, out Type extensionDataType),
        extensionDataType: extensionDataType,
        jsonConverter: JsonConverterFunc);

我的自定义 struct TenantId 不符合这些条件中的任何一个,因此,它回退到被视为 object(最后一条语句)。

然后我继续查看现有的tests,以了解该类的使用情况,看看我是否可以更改任何内容。令人惊讶的是,我发现了一个名为 GenerateSchema_SupportsOption_CustomTypeMappings 的测试(第 356 行),它显示了一种提供自定义映射的方法(参见该方法的第一条语句):

[Theory]
[InlineData(typeof(ComplexType), typeof(ComplexType), "string")]
[InlineData(typeof(GenericType<int, string>), typeof(GenericType<int, string>), "string")]
[InlineData(typeof(GenericType<,>), typeof(GenericType<int, int>), "string")]
public void GenerateSchema_SupportsOption_CustomTypeMappings(
            Type mappingType,
            Type type,
            string expectedSchemaType)

    var subject = Subject(configureGenerator: c => c.CustomTypeMappings.Add(mappingType, () => new OpenApiSchema  Type = "string" ));

    var schema = subject.GenerateSchema(type, new SchemaRepository());

    Assert.Equal(expectedSchemaType, schema.Type);
    Assert.Empty(schema.Properties);

就我而言,我想让我的TenantId 映射到string。为此,我在 Function App 启动时编辑了 SwashBuckle 的配置:

private static void ConfigureSwashBuckle(IFunctionsHostBuilder functionsHostBuilder)

    functionsHostBuilder.AddSwashBuckle(Assembly.GetExecutingAssembly(), options =>
    
        ...
        options.ConfigureSwaggerGen = (swaggerGenOptions) => swaggerGenOptions.MapType<TenantId>(() => new OpenApiSchema Type = "string");
    );

就是这样,TenantId 现在在 Swagger 中被视为 string

【讨论】:

以上是关于为啥 autorest 用 Swagger 中的对象替换我的自定义结构?的主要内容,如果未能解决你的问题,请参考以下文章

AutoRest - 具有 C# 和 Razor 模板的 Swagger 规范代码生成器。

为啥 dto 中的类型在 swagger 中不可见?

Autorest 生成需要 beta 包的 Client Sdk

如何降级 Autorest 扩展 - 特别是 C# 扩展?

注意:从配置中选择的 AutoRest 核心版本:~2.0.4413 当我安装了 3.06187

我可以在 blazor 中使用 Autorest 客户端吗