Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Model Binder not invoking from Swagger UI

I am using .Net framework 4.6.1 and Swashbuckle version 5.3.2 in my WebApi project. Swagger UI is not giving an option to send the input as a request body to my POST Api which uses a custom model binder.

- Model Used :

    [ModelBinder(typeof(FieldValueModelBinder))]
    public class Employee
    {
        public int EmployeeID { get; set; }
        public string EmployeeName { get; set; }
        public string City { get; set; }
    }

- API Post method used:

    [HttpPost]
    // POST: api/Employee
    public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)
    {
        if (!ModelState.IsValid)
            return Request.CreateResponse(HttpStatusCode.BadRequest, "Please provide valid input");
        else
            //Add Employee logic here
            return Request.CreateResponse(HttpStatusCode.OK, "Employee added sucessfully");
    }

- Model Binder used :

public class FieldValueModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    /// <summary>
    /// Store received data in API in KeyValuePair
    /// </summary>
    private List<KeyValuePair<string, string>> kvps;

    /// <summary>
    /// Storing error while binding data in Model class
    /// </summary>
    private Dictionary<string, string> dictionaryErrors = new Dictionary<string, string>();

    /// <summary>
    /// Implementing Base method and binding received data in API to its respected property in Model class
    /// </summary>
    /// <param name="actionContext">Http Action Context</param>
    /// <param name="bindingContext">Model Binding Context</param>
    /// <returns>True if no error while binding. False if any error occurs during model binding</returns>
    public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
    {
        try
        {
            var bodyString = actionContext.Request.Content.ReadAsStringAsync().Result;
            if (actionContext.Request.Method.Method.ToUpper().Equals("GET"))
            {
                var uriContext = HttpUtility.ParseQueryString(actionContext.Request.RequestUri.Query);
                if (uriContext.HasKeys())
                {
                    this.kvps = uriContext.AllKeys.ToDictionary(k => k, k => uriContext[k]).ToList<KeyValuePair<string, string>>();
                }
            }
            else if (!string.IsNullOrEmpty(bodyString))
            {
                this.kvps = this.ConvertToKvps(bodyString);
            }
            else
            {
                bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide valid input data.");
                return false;
            }
        }
        catch (Exception ex)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Please provide data in a valid format.");
            return false;
        }

        // Initiate primary object
        var obj = Activator.CreateInstance(bindingContext.ModelType);
        try
        {
            this.SetPropertyValues(obj);
        }
        catch (Exception ex)
        {
            if (this.dictionaryErrors.Any())
            {
                foreach (KeyValuePair<string, string> keyValuePair in this.dictionaryErrors)
                {
                    bindingContext.ModelState.AddModelError(keyValuePair.Key, keyValuePair.Value);
                }
            }
            else
            {
                bindingContext.ModelState.AddModelError("Internal Error", ex.Message);
            }

            this.dictionaryErrors.Clear();
            return false;
        }

        // Assign completed Mapped object to Model
        bindingContext.Model = obj;
        return true;
    }

I am facing below issues:

  • When we use ‘ModelBinder’ in our post method, Swagger UI is displaying this screen where the input parameter are posted in a query string and CustomModelBinder is invoked and tries to read request body to perform model binding and validation and gets null in this case.

    Public HttpResponseMessage Post([ModelBinder(typeof(FieldValueModelBinder))]Employee emp)

    See Image Swagger UI with ModelBinder

  • When we use ‘FromBody’ in our post method, Swagger UI displays this
    screen where we can send the input in a request body, but in this
    case CustomModelBinder is not invoked and we are not able to perform modelbinding and validation.

    public HttpResponseMessage Post([FromBody]Employee emp)

    See Image Swagger UI with FromBody

  • When we try using both ‘modelbinder’ and ‘frombody’, Swagger UI takes the input as a query and we get the below response:

    See Image Swagger UI with ModelBinder and FromBody both

Tried with Postman, the API works fine and we are able to pass the input in request body and get the proper output. The custom model binding also works and populates the error message in case of invalid model state and we can then use those messages to send in the response.

See Image Api call from Postman

See Image ModelState Error

What needs to be changed to invoke the custom model binder from Swagger UI while posting input data to API in request body. Please Suggest.

like image 936
Nidhi Sonar Avatar asked Nov 08 '22 11:11

Nidhi Sonar


1 Answers

You can do that with an IDocumentFilter here is the code:

private class ApplyDocumentVendorExtensions : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
    {
        if (swaggerDoc != null)
        {
            foreach (var path in swaggerDoc.paths)
            {
                if (path.Value.post != null && path.Value.post.parameters != null )
                {
                    var parameters = path.Value.post.parameters;
                    if (parameters.Count == 3 && parameters[0].name.StartsWith("emp"))
                    {
                        path.Value.post.parameters = EmployeeBodyParam;
                    }
                }
            }
        }
    }

    private IList<Parameter> EmployeeBodyParam
    {
        get
        {
            return new List<Parameter>
            {
                new Parameter {
                    name = "emp",
                    @in = "body",
                    required = true,
                    schema = new Schema {
                        @ref = "#/definitions/Employee"
                    }
                }
            };
        }
    }
}
like image 81
Helder Sepulveda Avatar answered Nov 28 '22 12:11

Helder Sepulveda