首页 > 解决方案 > 为什么 autorest 用 Swagger 中的对象替换我的自定义结构?

问题描述

我创建了一个自定义readonly struct来定义我称之为的不可变值类型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 被删除并替换为 Object

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

细节

这是版本信息。

标签: c#azure-functionsswaggerswashbuckleautorest

解决方案


我开始研究Swashbuckle.AspNetCore.SwaggerGen的源代码,以了解我readonly struct的解释方式。这一切都发生在JsonSerializerDataContractResolver类中,在GetDataContractForType确定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(最后一个陈述)。

然后我继续查看现有的测试,看看这个类是如何使用的,看看我是否可以改变任何东西。令人惊讶的是,我发现了一个名为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);
}

就我而言,我希望将 myTenantId映射到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现在被认为是stringSwagger 中的 a。

TenantId 作为字符串


推荐阅读