在 swagger 中支持 asp.net core 可选路由参数

64 阅读1分钟

场景

  • swagger 根据 OpenApi v3.x 规范,即使使用 ? 标注为可选,UI 中仍然为 required
    [Route("api/{env:env}/[controller]")]
    [ApiController]
    public class DemoController(IDemoRepo repo) : ControllerBase
    {
        [HttpGet("user/{name?}")]
        public ActionResult<string> GetUserName() => Ok(name);
        /* other codes */
    }
    

解决方法

  • 增加自定的方法修改已经生成的 swagger 文档
    builder.Services.AddSwaggerGen(opt =>
    {
       opt.OperationFilter<ApplyOptionalRouteParameterOperationFilter>();
       /* other codes */
    }
    
    public partial class ApplyOptionalRouteParameterOperationFilter : IOperationFilter
    {
       public void Apply(OpenApiOperation operation, OperationFilterContext context)
       {
           if (operation.Parameters.Count == 0) return;
           // 获取方法以及类上面的 RouteAttribute
           var attrs = context.MethodInfo.GetCustomAttributes(true)
               .Concat(context.MethodInfo.DeclaringType?.GetCustomAttributes(true) ?? [])
               .OfType<IRouteTemplateProvider>().Where(x => x.Template?.Contains('?')??false).ToList();
    
           // 遍历所有 RouteAttribute,如果包含可选参数,则对应修改 OpenAPI 参数定义
           foreach (var route in attrs)
           {
               var matches = RouteTemplateRegex().Matches(route.Template??"");
               foreach (Match match in matches)
               {
                   var name = match.Groups["name"].Value;
                   var parameter = operation.Parameters.FirstOrDefault(x => x.Name == name);
                   if (parameter is not null)
                   {
                       parameter.AllowEmptyValue = true;
                       parameter.Required = false;
                       parameter.Schema.Nullable = true;
                   }
               }
           }
       }
    
       [GeneratedRegex(@"\{(?<name>\w+)(\:\w+)*\?\}")]
       private static partial Regex RouteTemplateRegex();
    }