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

如何用NSwag为ServiceStack应用生成完整OpenAPI v3规范?

用NSwag为ServiceStack应用生成完整OpenAPI v3规范的解决方案

问题背景

需要为基于ServiceStack(SS)的应用生成完整OpenAPI v3规范,SS的契约定义在DTO类中与实现完全解耦。但当前使用JsonSchemaGenerator仅生成了components部分,paths、parameters等核心内容缺失。

示例DTO代码

[Route("/hello", "GET", Summary = @"Default hello service.", Notes = "Longer description for hello services")]
[Route("/hello/{Name}", "GET", Summary = @"Additional hello service", Notes="Longer description for hello services")]
public class Hello : IReturn<HelloResponse>
{
    [ApiMember(Name = "Name", Description = "Name Description",
        ParameterType = "path", DataType = "string", IsRequired = true)]
    [ApiAllowableValues("Name", typeof(string))]
    public string Name { get; set; }
}

public class HelloResponse
{
    public string Result { get; set; }
}

当前生成的不完整OpenAPI规范

{"openapi":"3.0.0","info":{"title":"Swagger specification","version":"1.0.0"},"servers":[{"url":"http:///"}],"components":{"schemas":{"Hello":{"type":"object","additionalProperties":false,"properties":{"Name":{"type":"string","nullable":true}}},"HelloResponse":{"type":"object","additionalProperties":false,"properties":{"Result":{"type":"string","nullable":true}}}}}

当前使用的生成代码

var modelAssemblies = LoadFromAssemblies
            .Where(assembly => assembly.FullName != null && assembly.FullName.Contains(".ServiceModel"));

var types = modelAssemblies.SelectMany(a => a.GetTypes());

var document = CreateDocument();

var generatorSettings = new JsonSchemaGeneratorSettings {SchemaType = SchemaType.OpenApi3};
var generator = new JsonSchemaGenerator(generatorSettings);
var schemaResolver = new OpenApiSchemaResolver(document, generatorSettings);

foreach (var type in types)
{
    generator.Generate(type, schemaResolver);
}
var json = document.ToJson();
var yaml = document.ToYaml();

return document;

解决方案

核心设计思路

JsonSchemaGenerator仅负责生成Schema(即components.schemas部分),要生成完整规范,需主动解析ServiceStack的特性(如Route、ApiMember),将DTO映射为OpenAPI的paths、operations、parameters等结构,手动填充到OpenApiDocument中。核心步骤:

  • 筛选所有实现IReturn或IReturn<T>的DTO类型(ServiceStack服务契约标识)
  • 解析DTO上的Route特性,提取路径、HTTP方法、摘要、描述
  • 解析DTO属性上的ApiMember特性,生成对应请求参数(路径/查询等)
  • 为每个路由创建OpenApiPathItem和OpenApiOperation,关联参数与响应模型
  • 将生成的路径项添加到document.Paths,确保响应模型已生成到components.schemas

具体实现代码示例

var modelAssemblies = LoadFromAssemblies
    .Where(assembly => assembly.FullName != null && assembly.FullName.Contains(".ServiceModel"));

var types = modelAssemblies.SelectMany(a => a.GetTypes());

var document = CreateDocument();

// 先生成所有Schema(components.schemas)
var generatorSettings = new JsonSchemaGeneratorSettings { SchemaType = SchemaType.OpenApi3 };
var generator = new JsonSchemaGenerator(generatorSettings);
var schemaResolver = new OpenApiSchemaResolver(document, generatorSettings);
foreach (var type in types)
{
    generator.Generate(type, schemaResolver);
}

// 解析ServiceStack路由,填充paths部分
var serviceDtoTypes = types.Where(t => t.GetInterfaces().Any(i => 
    i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>) || 
    i == typeof(IReturn)));

foreach (var dtoType in serviceDtoTypes)
{
    var routeAttributes = dtoType.GetCustomAttributes<RouteAttribute>(inherit: false);
    foreach (var route in routeAttributes)
    {
        // 拆分并处理多个HTTP方法
        var httpMethods = route.Verb.Split(',').Select(m => m.Trim().ToLowerInvariant());
        var path = route.Path;

        // 创建或获取路径项
        if (!document.Paths.TryGetValue(path, out var pathItem))
        {
            pathItem = new OpenApiPathItem();
            document.Paths[path] = pathItem;
        }

        // 构建Operation
        var operation = new OpenApiOperation
        {
            Summary = route.Summary,
            Description = route.Notes,
            OperationId = $"Execute{dtoType.Name}"
        };

        // 解析DTO属性为请求参数
        var properties = dtoType.GetProperties();
        foreach (var prop in properties)
        {
            var apiMemberAttr = prop.GetCustomAttribute<ApiMemberAttribute>();
            if (apiMemberAttr == null) continue;

            var paramLocation = apiMemberAttr.ParameterType switch
            {
                "path" => OpenApiParameterLocation.Path,
                "query" => OpenApiParameterLocation.Query,
                _ => OpenApiParameterLocation.Query // 默认映射为查询参数
            };

            var parameter = new OpenApiParameter
            {
                Name = apiMemberAttr.Name ?? prop.Name,
                Description = apiMemberAttr.Description,
                Required = apiMemberAttr.IsRequired,
                In = paramLocation,
                Schema = schemaResolver.Resolve(prop.PropertyType, prop.Name)
            };

            operation.Parameters.Add(parameter);
        }

        // 绑定响应模型(从IReturn<T>提取)
        var returnInterface = dtoType.GetInterfaces().FirstOrDefault(i => 
            i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReturn<>));
        if (returnInterface != null)
        {
            var responseType = returnInterface.GetGenericArguments()[0];
            var responseSchema = schemaResolver.Resolve(responseType, responseType.Name);
            operation.Responses["200"] = new OpenApiResponse
            {
                Description = "Success",
                Content = new Dictionary<string, OpenApiMediaType>
                {
                    ["application/json"] = new OpenApiMediaType { Schema = responseSchema }
                }
            };
        }

        // 将Operation绑定到对应HTTP方法
        foreach (var method in httpMethods)
        {
            switch (method)
            {
                case "get": pathItem.Get = operation; break;
                case "post": pathItem.Post = operation; break;
                case "put": pathItem.Put = operation; break;
                case "delete": pathItem.Delete = operation; break;
                // 可扩展支持其他HTTP方法
            }
        }
    }
}

var json = document.ToJson();
var yaml = document.ToYaml();
return document;

关键说明

  • 必须先生成Schema再处理路径,确保响应模型和参数类型已存在于components.schemas
  • 通过IReturn<T>接口自动识别响应类型,关联到Operation的200响应
  • 支持Route特性中的多HTTP方法,每个方法绑定对应Operation
  • 映射ApiMember的ParameterType到OpenAPI参数位置(path/query等)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:06:42