1

I'm executing the URL

https://localhost:44310/api/Licensee/{"name":"stan"}

in address field of my browser, getting the error

"title": "Unsupported Media Type", "status": 415

which is described as

... the origin server is refusing to service the request because the payload is in a format not supported by this method on the target resource.

The suggested troubleshot is

... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...

I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.

The controller looks like this.

[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
  return Ok(await Licensee.GetLicenseeByParameters(parameters));

}

I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.

services.AddControllers()
  .AddJsonOptions(_ =>
  {
    _.JsonSerializerOptions.AllowTrailingCommas = true;
    _.JsonSerializerOptions.PropertyNamingPolicy = null;
    _.JsonSerializerOptions.DictionaryKeyPolicy = null;
    _.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
  });

My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.

How can I resolve this (or at least troubleshoot further)?

Community
  • 1
  • 1
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • You can't pass JSON in the url like that. You have to use querystring params – Brad Firesheets Dec 17 '19 at 22:03
  • But I really want to... The reason is that there might be a **loooot** of parameters and I don't want to refactor the controller's signature each time. I guess passing the parameters in body might be an option. But how do I pass a body from URL line? Is it possible at all? Also, do you have a reference on where it says that JSON can't be used that way, please? I'd like to angrily read it. :) – Konrad Viltersten Dec 17 '19 at 22:07
  • You cant do things that are not supported, no matter how much you want to do them. JSON is supported in request.body however. – JK. Dec 17 '19 at 22:25
  • @JK. Not on GET. I will have to switch to POST. Then I have to learn the very rookie testers to use Swagger... Sight... – Konrad Viltersten Dec 17 '19 at 22:30
  • Well of course not. When doing HTTP GET, there is no request body. – howcheng Dec 18 '19 at 00:04

1 Answers1

3

The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time

  1. Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:

    [HttpGet("/api/licensee")]
    public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
    {
        return Json(parameters);
    }
    

    The first time the DTO is:

    public class LicenseeParameters
    {
        public string Name {get;set;}
        public string Note {get;set;}
    }
    

    What you need is to send a HTTP Request as below:

    GET /api/licensee?name=stan&note=it+works
    

    And later you decide to change the LicenseeParameters:

    public class LicenseeParameters
    {
        public string Name {get;set;}
        public string Note {get;set;}
    
        public List<SubNode> Children{get;set;} // a complex array
    }
    

    You don't have to change the controller signature. Just send a payload in this way:

    GET /api/licensee?name=stan&note=it+works&children[0].nodeName=it&children[1].nodeName=minus
    

    The conversion is : . represents property and [] represents collection or dictionary.

  2. In case you do want to send a json string within URL, what you need is to create a custom model binder.

    internal class LicenseeParametersModelBinder : IModelBinder
    {
        private readonly JsonSerializerOptions _jsonOpts;
    
        public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
        {
            this._jsonOpts = jsonOpts.Value;
        }
    
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            var name= bindingContext.FieldName;
            var type = bindingContext.ModelType;
            try{
                var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
                var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
                bindingContext.Result = ModelBindingResult.Success(obj);
            }
            catch (JsonException ex){
                bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
            }
            return Task.CompletedTask;
        }
    }
    

    and register the model binder as below:

    [HttpGet("/api/licensee/{parameters}")]
    public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
    {
        return Json(parameters);
    }
    

    Finally, you can send a json within URL(suppose the property name is case insensive):

    GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
    
  3. The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.

itminus
  • 23,772
  • 2
  • 53
  • 88