locked
Passing multiple or complex parameters to a GET handler RRS feed

  • Question

  • User349337957 posted

    I feel like I'm trying to do a really simple thing, but I can't figure out how to do it.  Basically, I have a Web API controller that I am using to query data using complex and arbitrary data.  For example, let's pretend my Controller is called YelpController and its intent is to allow the consumer to find restaurants.  I would want to do something like this:

    // here's my search criteria; these are all optional
    public class SearchCriteria 
    { 
         public int Latitude { get; set; } 
         public int Longitude { get; set; } 
         public string Cuisine { get; set; } 
         public ExpenseCategory { get; set; } 
    }
    
    public IEnumerable<Restaurant> GetByCriteria(SearchCriteria filters)
    {
        // do stuff
        return matches;
    }
    

    I'd like to issue this via an ajax request, like this:

    $.ajax(
    {
        url: "/api/Yelp/GetByCriteria",
        type: "GET",
        contentType: "application/json",
        data: JSON.stringify({ Latitude: 47.6097, Longitude: -122.3331 }),
        success: function (result) {
            alert(result.Result);
        }
    });

    Clearly, this doesn't work since I can't send a "data" payload along with a GET request.  I could do this with a POST, but that seems hacky.

    Instead of using one wrapper class as a single input parameter, I could split it out and have 4 parameters to the method and pass them in via a query string (eg /api/Yelp/GetByCriteria?latitude=xxx&longitude=yyy), but that is REALLY brittle and would require my API to change every time I add a new parameter.

    Also, I clearly cannot use OData/IQueryable; my datastore is NoSQL and, besides, the parameters I want to pass aren't even supported currently.

    So, what's the PROPER solution for this scenario?  This seems like a super common use case, but I can't find anyone else with the same problem

    Friday, May 25, 2012 12:58 AM

Answers

  • User401360897 posted

    A custom filter will be help you here. You need to pass just a single json querystring in url,  for example,

    ?json={"Latitude"%3A+47.6097%2C+"Longitude"%3A+-122.3331}

    Edit: you can improve this method by doing,

        public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
        {
            Type _type;
            string _queryStringKey;
            public BindJson(Type type, string queryStringKey)
            {
                _type = type;
                _queryStringKey = queryStringKey;
            }
            public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var json = actionContext.Request.RequestUri.ParseQueryString()[_queryStringKey];
                var serializer = new JavaScriptSerializer();
                actionContext.ActionArguments[_queryStringKey] = serializer.Deserialize(json, _type);
            }
        }
    
        public class YelpController : ApiController
        {
            [BindJson(typeof(SearchCriteria), "json")]
            public IEnumerable<Restaurant> GetByCriteria(SearchCriteria json)
            {
                
    
                // do stuff
                return new List<Restaurant>();
            }
    
        }



    You can construct this with JSON.stringify method. Here is a possible filter,

        public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
        {
            public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var json = actionContext.Request.RequestUri.ParseQueryString()["json"];
                var serializer = new JavaScriptSerializer();
                actionContext.ActionArguments["filters"] = serializer.Deserialize(json, typeof(SearchCriteria));
            }
        }
    
        public class YelpController : ApiController
        {
            [BindJson]
            public IEnumerable<Restaurant> GetByCriteria(SearchCriteria filters)
            {
                
    
                // do stuff
                return new List<Restaurant>();
            }
    
        }



    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 25, 2012 3:37 AM

All replies

  • User401360897 posted

    A custom filter will be help you here. You need to pass just a single json querystring in url,  for example,

    ?json={"Latitude"%3A+47.6097%2C+"Longitude"%3A+-122.3331}

    Edit: you can improve this method by doing,

        public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
        {
            Type _type;
            string _queryStringKey;
            public BindJson(Type type, string queryStringKey)
            {
                _type = type;
                _queryStringKey = queryStringKey;
            }
            public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var json = actionContext.Request.RequestUri.ParseQueryString()[_queryStringKey];
                var serializer = new JavaScriptSerializer();
                actionContext.ActionArguments[_queryStringKey] = serializer.Deserialize(json, _type);
            }
        }
    
        public class YelpController : ApiController
        {
            [BindJson(typeof(SearchCriteria), "json")]
            public IEnumerable<Restaurant> GetByCriteria(SearchCriteria json)
            {
                
    
                // do stuff
                return new List<Restaurant>();
            }
    
        }



    You can construct this with JSON.stringify method. Here is a possible filter,

        public class BindJson : System.Web.Http.Filters.ActionFilterAttribute
        {
            public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
            {
                var json = actionContext.Request.RequestUri.ParseQueryString()["json"];
                var serializer = new JavaScriptSerializer();
                actionContext.ActionArguments["filters"] = serializer.Deserialize(json, typeof(SearchCriteria));
            }
        }
    
        public class YelpController : ApiController
        {
            [BindJson]
            public IEnumerable<Restaurant> GetByCriteria(SearchCriteria filters)
            {
                
    
                // do stuff
                return new List<Restaurant>();
            }
    
        }



    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, May 25, 2012 3:37 AM
  • User349337957 posted

    Excellent.  Passing URL encoded stringified JSON in a query string is probably not the most elegant solution, but so far it seems to work.  Thanks!

    Friday, May 25, 2012 11:10 AM
  • User-356739632 posted

    Hi Thanks for the solution

    Thursday, February 21, 2013 10:23 AM
  • User1115704286 posted

    Interesting question. I have the same dilemma - what's the best way to pass a json query object as a parameter.

    However, cramming it in the URL doesn't seem quite right to me - isn't it better to just POST the thing, and return the result in the response body? That's also a bit non-standard as the response bodies of POST requests are not usually used to transfer data but as far as I'm aware it's supported.

    Thursday, October 31, 2013 3:39 PM
  • User-1588480722 posted

    Instead of using one wrapper class as a single input parameter, I could split it out and have 4 parameters to the method and pass them in via a query string (eg /api/Yelp/GetByCriteria?latitude=xxx&longitude=yyy), but that is REALLY brittle and would require my API to change every time I add a new parameter.

    What is wrong with this? As I see, this is the CORRECT way to do. It is not brittle. Let me guess why you think it is brittle - you are assuming that you need to have multiple input parameters in the web api method. No!!! You can maintain the same complex object as the input parameter and add [FromUri] attributes to each of the members.

    Sunday, November 3, 2013 11:53 PM