如何用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
相关产品推荐
相关产品推荐

