54

I don´t know why my parameter "ParametroFiltro Filtro" is getting null, the other parameters "page" and "pageSize" is getting OK.

public class ParametroFiltro
{
    public string Codigo { get; set; }
    public string Descricao { get; set; }
}

My ApiController Get method:

public PagedDataModel<ParametroDTO> Get(ParametroFiltro Filtro, int page, int pageSize)

My ajax call:

var fullUrl = "/api/" + self.Api;
$.ajax({
    url: fullUrl,
    type: 'GET',
    dataType: 'json',
    data: { Filtro: { Codigo: '_1', Descricao: 'TESTE' }, page: 1, pageSize: 10 },
    success: function (result) {
        alert(result.Data.length);
        self.Parametros(result.Data);
    }
});
abatishchev
  • 98,240
  • 88
  • 296
  • 433
will
  • 1,002
  • 2
  • 9
  • 20

5 Answers5

85

You are trying to send a complex object with GET method. The reason this is failing is that GET method can't have a body and all the values are being encoded into the URL. You can make this work by using [FromUri], but first you need to change your client side code:

$.ajax({
    url: fullUrl,
    type: 'GET',
    dataType: 'json',
    data: { Codigo: '_1', Descricao: 'TESTE', page: 1, pageSize: 10 },
    success: function (result) {
        alert(result.Data.length);
        self.Parametros(result.Data);
    }
});

This way [FromUri] will be able to pick up your complex object properties directly from the URL if you change your action method like this:

public PagedDataModel<ParametroDTO> Get([FromUri]ParametroFiltro Filtro, int page, int pageSize)

Your previous approach would rather work with POST method which can have a body (but you would still need to use JSON.stringify() to format body as JSON).

tpeczek
  • 23,867
  • 3
  • 74
  • 77
  • Now it has worked, tks a lot. I wasted all this morning trying to solve it. – will Oct 16 '12 at 14:27
  • 3
    Thanks, worked like a charm. The `[FromUri]` is all I needed! Strange that it even needs it in there explicitly... I would think Web API would assume it's in the Uri since it's a `GET` – Mark Pieszak - Trilon.io Dec 17 '14 at 17:28
  • thank you again. Interesting that the complex object's parameter name ("Filtro" in this case) should not be passed in the request - very counterintuitive. I wonder what happens if you have sub objects with fields of the same name (or a list of objects) - presumably it would throw? – sming Feb 10 '16 at 20:24
  • It looks like you have to decorate every complex parameter with [FromUri]. I've tried decorating the controller to no avail (no exceptions just no effect), which is a shame. If anyone finds otherwise, please shout. – sming Feb 10 '16 at 21:12
  • This isn't working for me, the primitive type parameters work but the object parameter is still null. – Homer Oct 04 '16 at 16:28
  • @Homer Can you share some more details? – tpeczek Oct 04 '16 at 21:20
  • BTW, I know the sort in my example is redundant, it was there for testing. Following the example above, I had: `$.ajax({ url: "http://localhost:25237/api/Plant", type: 'GET', dataType: 'json', data: { Sort: "Name", Filter: {Sort: "Name"} }, success: function (result) { alert(result.data.length); } });` `public ApiResult> Get([FromUri] PlantFilter filter, string sort)` On the server, `sort`would get populated, but not `filter.sort`: filter.sort = "", sort = "Name". – Homer Oct 05 '16 at 14:59
  • @Homer I'm unable to reproduce your issue. In general (at least with jQuery version I'm using) this should result in URL like this: `http://localhost:58869/api/complextype/getcomplextypewithprefix?Sort=Name&Filter[Sort]=Description` which I was able to bind correctly with below action: `public string GetComplexTypeWithPrefix([FromUri]PlantFilter filter, string sort) { return "test"; }` Please reach me via email so we can work on this further. – tpeczek Oct 05 '16 at 18:44
  • Thanks for trying @tpeczek, I decided to use the multiple `post` solution from [this answer](http://stackoverflow.com/a/12703423/251614), something like `[ActionName("Search")] [HttpPost] public ApiResult> Search(PlantFilter filter)` – Homer Oct 05 '16 at 18:51
7

Provide the contentType property when you make the ajax call. Use JSON.stringify method to build the JSON data to post. change the type to POST and MVC Model binding will bind the posted data to your class object.

var filter = { "Filtro": { "Codigo": "_1", "Descricao": "TESTE" }, 
                                               "page": "1", "pageSize": "10" }; 
$.ajax({
    url: fullUrl,
    type: 'POST',
    dataType: 'json',
    contentType: 'application/json',
    data: JSON.stringify(filter),
    success: function (result) {
        alert(result.Data.length);
        self.Parametros(result.Data);
    }
});
Shyju
  • 214,206
  • 104
  • 411
  • 497
2

It's also possible to access POST variables via a Newtonsoft.Json.Linq JObject.

For example, this POST:

$.ajax({
    type: 'POST',
    url: 'URL',
    data: { 'Note': note, 'Story': story },
    dataType: 'text',
    success: function (data) { }
});

Can be accessed in an APIController like so:

public void Update([FromBody]JObject data)
{
    var Note = (String)data["Note"];
    var Story = (String)data["Story"];
}
graphicdivine
  • 10,937
  • 7
  • 33
  • 59
  • 5
    Can I achieve this as a GET method? In my scenario I don't need to `Update`, I need to `Get` based on a hypothetical complex object with all its properties set. – Shimmy Weitzhandler Feb 22 '18 at 13:38
1

If you append json data to query string, and parse it later in web api side. you can parse complex object too. It's useful rather than post json object, espeicaly in some special httpget requirement case.

//javascript file 
    var data = { UserID: "10", UserName: "Long", AppInstanceID: "100", ProcessGUID: "BF1CC2EB-D9BD-45FD-BF87-939DD8FF9071" };
    var request = JSON.stringify(data);
    request = encodeURIComponent(request);

    doAjaxGet("/ProductWebApi/api/Workflow/StartProcess?data=", request, function (result) {
        window.console.log(result);
    });

    //webapi file:
    [HttpGet]
    public ResponseResult StartProcess()
    {
        dynamic queryJson = ParseHttpGetJson(Request.RequestUri.Query);
            int appInstanceID = int.Parse(queryJson.AppInstanceID.Value);
        Guid processGUID = Guid.Parse(queryJson.ProcessGUID.Value);
        int userID = int.Parse(queryJson.UserID.Value);
        string userName = queryJson.UserName.Value;
    }

    //utility function:
    public static dynamic ParseHttpGetJson(string query)
    {
        if (!string.IsNullOrEmpty(query))
        {
            try
            {
                var json = query.Substring(7, query.Length - 7); //seperate ?data= characters
                json = System.Web.HttpUtility.UrlDecode(json);
                dynamic queryJson = JsonConvert.DeserializeObject<dynamic>(json);

                return queryJson;
            }
            catch (System.Exception e)
            {
                throw new ApplicationException("can't deserialize object as wrong string content!", e);
            }
        }
        else
        {
            return null;
        }
    }
Bes Ley
  • 1,685
  • 1
  • 20
  • 39
0

In .NET Core, the HttpClient sets the transfer-encoding: chunked header by default. This can cause the .NET Web API controller parameters to be null.

To get around this, you'll need to set the ContentLength header explicitly:

var json = JsonConvert.SerializeObject(myObject);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentLength = json.Length;
var response = await client.PostAsync("http://my-api.com", content);

SO answer if you already know the transfer-encoding header is the issue: How to disable Chunked Transfer Encoding in ASP.Net C# using HttpClient

Related bug which won't be fixed, which gives some insight into the problem: https://github.com/dotnet/runtime/issues/30283

Josh Noe
  • 2,664
  • 2
  • 35
  • 37