Search code examples
swaggerswagger-uiswashbucklewebapi

Swagger/Swashbuckle do not recognize JObject in request model


My Environment: Asp.NET WebAPI、NET Framework 4.5.2、Swashbuckle.Core 5.6.0

For some reason, My controller must Inherit the previous same Controller, like this

public class User100Controller : ApiController
{
    [HttpGet]
    [AllowAnonymous]
    public virtual string Get()
    {
        return "1.0.0";
    }
}

public class User101Controller : User100Controller
{
    [HttpGet]
    [AllowAnonymous]
    public override string Get()
    {
        return "1.0.1";
    }
}

Run directly, and swagger ui page show correctly

sample picture, pls right click

But if I add a Post Action with params, swagger ui do not recongnize Request Model

UserModel and UserModelSex

/// <summary>
/// UserModel
/// </summary>
public class UserModel
{
    /// <summary>
    /// Name
    /// </summary>
    public string name { get; set; } = string.Empty;

    /// <summary>
    /// Age
    /// </summary>
    public int age { get; set; } = 0;
}

/// <summary>
/// UserModelSex
/// </summary>
public class UserModelSex : UserModel
{
    /// <summary>
    /// Sex
    /// </summary>
    public int sex { get; set; } = -1;
}

User100Controller

[HttpPost]
[SwaggerResponse(200, "success", typeof(UserModel))]
public virtual IHttpActionResult SaveUser([FromBody] UserModel model)
{
    if (string.IsNullOrEmpty(model.name) || model.age == 0)
    {
        return Ok("error");
    }

    //...
    return Ok("success");
}

sample picture, pls right click

This is correct effect, Now if I override this SaveUser Action in User101ontroller, and pass the new request model UserModelSex, I got an error, Because of Override Method must have the same param List with the parent Method, So I change it like this

public class User100Controller : ApiController
{
    [HttpPost]
    [SwaggerResponse(200, "success", typeof(UserModel))]
    public virtual IHttpActionResult SaveUser([FromBody] JObject json)
    {
        var model = json.ToObject<UserModel>();
        if (string.IsNullOrEmpty(model.name) || model.age == 0)
        {
            return Ok("error");
        }

        //...
        return Ok("success");
    }
}

public class User101Controller : User100Controller
{
    [HttpPost]
    [SwaggerResponse(200, "success", typeof(UserModelSex))]
    public override IHttpActionResult SaveUser([FromBody] JObject json)
    {
        var model = json.ToObject<UserModelSex>();
        if (string.IsNullOrEmpty(model.name) || model.age == 0 || model.sex == -1)
        {
            return Ok("error");
        }

        //...
        return Ok("success");
    }
}

swagger ui do not recongnize JObject

sample picture, pls right click

how to show UserModel and UserModeSex in request model, like pic 2


Solution

  • Finally, I solve it by myself

    • add SwaggerRequestModelAttribute.cs
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class SwaggerRequestModelAttribute : Attribute
    {
        public Type RequestModel { get; private set; }
    
        public string ModelName { get; private set; }
    
        public SwaggerRequestModelAttribute(Type requestModel)
        {
            RequestModel = requestModel;
            ModelName = requestModel.Name;
        }
    }
    
    • then, Mark [SwaggerRequestModel] to the Controllers
    [HttpPost]
    [SwaggerRequestModel(typeof(UserModel))]
    public virtual IHttpActionResult SaveUser([FromBody] JObject json)
    {
        var model = json.ToObject<UserModel>();
        if (string.IsNullOrEmpty(model.name) || model.age == 0)
        {
            return Ok("error");
        }
    
        //...
        return Ok("success");
    }
    
    • Add ModelInBodyOperationFilter.cs
    public class ModelInBodyOperationFilter : IOperationFilter
    {
        public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
        {
            if (operation.parameters == null) operation.parameters = new List<Parameter>();
    
            var attribute = apiDescription.GetControllerAndActionAttributes<SwaggerRequestModelAttribute>();
            if (attribute.Any())
            {
                if (operation.parameters.Count > 0 && operation.parameters[0].schema.type == "object")
                {
                    if (!schemaRegistry.Definitions.ContainsKey(attribute.First().ModelName))
                        schemaRegistry.GetOrRegister(attribute.First().RequestModel);
    
                    operation.parameters.RemoveAt(0);
                    operation.parameters.Add(new Parameter
                    {
                        name = "-",
                        @in = "body",
                        required = true,
                        schema = new Schema { @ref = $"#/definitions/{attribute.First().RequestModel.Namespace}.{attribute.First().ModelName}" }
                    });
                }
            }
        }
    }
    
    • also, in SwaggerConfig.Register()
    c.UseFullTypeNameInSchemaIds();
    c.OperationFilter<ModelInBodyOperationFilter>();
    
    • at last, run the app and preview it

    sample picture, pls right click