locked
POST with optional querystring values RRS feed

  • Question

  • User-1424942225 posted

    I have an API endpoint which is implemented as follows:

    [RoutePrefix("Validations")]
    public class ValidationsController : ApiController
    {
      [HttpPost, Route("Bsb")]
      public IHttpActionResult ValidateBsb([FromUri] string value = null)
      {
        var validator = new BankStateBranchValidator(DbContext.BankStateBranches);
        var data = new ValidationsResult
        {
          IsValid = validator.IsValid(value ?? string.Empty)
        };
    
        data.Error = data.IsValid
          ? null
          : "The BSB you have entered does not appear to be valid. Please check the value and try again.";
    
        return Ok(data);
      }
    }

    I can call the API successfully with the following API and get the expected result back:

    • POST /Validations/Bsb?value=012345
    • POST /Validations/Bsb

    But if I make the following API call, then it fails with a 400 Bad Request generated by the framework itself (i.e. a breakpoint inside the action doesn't get hit):

    • POST /Validations/Bsb?value=

    How can I get the API to accept an empty value of value, when it seems to mess up model binding?

    Tuesday, September 29, 2020 2:53 AM

All replies

  • User-1151440187 posted

    I have an API endpoint which is implemented as follows:

    [RoutePrefix("Validations")]
    public class ValidationsController
    {
      [HttpPost, Route("Bsb")]
      public IHttpActionResult ValidateBsb(]FromUri] string value)
      {
        var validator = new BankStateBranchValidator(DbContext.BankStateBranches);
        var data = new ValidationsResult
        {
          IsValid = validator.IsValid(value ?? string.Empty)
        };
    
        data.Error = data.IsValid
          ? null
          : "The BSB you have entered does not appear to be valid. Please check the value and try again.";
    
        return Ok(data);
      }
    }

    I can call the API successfully with the following API and get the expected result back:

    • POST /Validations/Bsb?value=012345
    • POST /Validations/Bsb

    But if I make the following API call, then it fails with a 400 Bad Request generated by the framework itself (i.e. a breakpoint inside the action doesn't get hit):

    • POST /Validations/Bsb?value=

    How can I get the API to accept an empty value of value, when it seems to mess up model binding?

    I think in the post action method you should write the parameter like :

    public ActionResult Get(string value = "") or public ActionResult Get(string value = null) 

    Hope it will help you.

    Thanks!

    Tuesday, September 29, 2020 4:16 AM
  • User1120430333 posted

    But if I make the following API call, then it fails with a 400 Bad Request generated by the framework itself (i.e. a breakpoint inside the action doesn't get hit):

    The Web server throws the HTTP 400 Bad Request. No program code for the Web program is ever executed on the Web server, becuase the code was never reached,  and therefore,  no breakpoint is ever going to be hit. 

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400

    Tuesday, September 29, 2020 4:21 AM
  • User1686398519 posted

    Hi WyldeKarrde, 

    I want to confirm some information with you:

    1. WyldeKarrde

      public class ValidationsController
      1. ApiController seems to be missing here.
      2. public class ValidationsController:ApiController
    2. WyldeKarrde

      it fails with a 400 Bad Request generated by the framework itself (i.e. a breakpoint inside the action doesn't get hit
      • Where did you set the breakpoint?
    3. WyldeKarrde

      get the API to accept an empty value of value
      1. How do you request the api? 
      2. I use "/Validations/Bsb?value=" to test, the value of value is empty.
      3. Below is the code I tested:
        • using (var client = new HttpClient())
          {
               var response = await client.PostAsync("https://localhost:44345/Validations/Bsb?value=",new StringContent(""));
               if (response.IsSuccessStatusCode)
               {
               }
          }

    Best Regards,

    YihuiSun

    Tuesday, September 29, 2020 6:39 AM
  • User-1424942225 posted

    Hi YihuiSun,

    Thanks for pointing out my copy/paste errors! I've updated the original code example (it was originally a partial class and the `ApiController` base class was defined in a different part of the partial class); I've also added the default value for the parameter. (your question 1)

    To answer your second question: the breakpoint is set at the first line of the controller's action: 

    var validator = new BankStateBranchValidator(DbContext.BankStateBranches);

    For your third question: the API is actually being run by an automated test suite, so I just give the tests a range of URIs to test. It uses RestSharp rather than HttpClient, but apart from that, there's nothing special. I ran Fiddler to capture the raw failing request:

    POST https://my.test.server/Validations/Bsb?value= HTTP/1.1
    Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
    User-Agent: RestSharp/106.8.10.0
    Host: my.test.server
    Content-Length: 0
    Accept-Encoding: gzip, deflate

    and the raw response is:

    HTTP/1.1 400 Bad Request
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 127
    Content-Type: application/json; charset=utf-8
    Expires: -1
    Server: Microsoft-IIS/10.0
    Date: Wed, 30 Sep 2020 05:18:32 GMT
    
    {"Message":"The request is invalid.","ModelState":{"value.String":["A value is required but was not present in the request."]}}

    Wednesday, September 30, 2020 5:39 AM
  • User-1424942225 posted

    I tried a different approach. Firstly, create a new class

    public class BsbValue
    {
      public string value { get;set; }
    }

    then change the controller action to bind to that class:

    [HttpPost, Route("Bsb")]
    public IHttpActionResult ValidateBsb([FromUri]BsbValue value)
    {
      var validator = new BankStateBranchValidator(DbContext.BankStateBranches);
      var data = new ValidationsResult
      {
        IsValid = validator.IsValid(value?.value ?? string.Empty)
      };
    
      data.Error = data.IsValid
        ? null
        : "The BSB you have entered does not appear to be valid. Please check the value and try again.";
    
      return Ok(data);
    }
    

    and that works correctly for all scenarios; the only issue that prevents me from using that is that it messes up the API definition (which is auto-generated using Swashbuckle). But at least it narrows down the issue to how WebAPI binds simple types to parameters.

    Which doesn't explain why YihuiSun seems to have it working as expected in their screenshot...

    Wednesday, September 30, 2020 5:46 AM
  • User-1424942225 posted

    OK, so it's probably a long-winded way of doing it, but this works for me: I created a custom model binder as follows:

    public class OptionalSimpleTypeModelBinderProvider : ModelBinderProvider
    {
      public override IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)
      {
        return new OptionalSimpleTypeModelBinder();
      }
    }
    
    public class OptionalSimpleTypeModelBinder : IModelBinder
    {
      public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
      {
        var queryString = actionContext.Request.RequestUri.ParseQueryString();
        bindingContext.Model = queryString[bindingContext.ModelName] ?? string.Empty;
        return true;
      }
    }

    then modify the controller action to be :

    public IHttpActionResult ValidateBsb([ModelBinder(typeof(OptionalSimpleTypeModelBinderProvider))]string value = null)
    {
      // etc etc
    }

    It probably needs a little more work to make it useful for other types other than string, but at least for my immediate problem, this works (and doesn't break the Swagger definition either).

    Wednesday, September 30, 2020 6:53 AM
  • User521825913 posted

    400 Bad Request generated by the framework itself (i.e. a breakpoint inside the action doesn't get hit):

    The 400 (Bad Request) status code indicates that the server cannot or will not process the request because the received syntax is invalid, nonsensical, or exceeds some limitation on what the server is willing to process. It means that the request itself has somehow incorrect or corrupted and the server couldn't understand it. The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method . Therefore, it prevents the website from being properly displayed. The main thing to understand is that the 400 Bad Request error is a client-side error. The cause of a 400 error can be a wrongly written URL or a URL that contains unrecognizable characters. Another cause of the error might be an invalid or expired cookie.  Also, if you try to upload a file that's too large. If the server is programmed with a file size limit, then you might encounter a 400 error.

    Wednesday, December 30, 2020 7:04 AM