Sep 16, 2012

Passing Multiple Parameters to ASP.NET Web API With jQuery

ASP.NET MVC 4 Web API has limited support to map POST form variables to simple parameters of a Web API method. Web API does not deal with multiple posted content values, you can only post a single content value to a Web API Action method. This post explains the different ways to pass multiple parameters to Web API method.

Suppose You have following Web API method:


public HttpResponseMessage PostProduct(int Id,String Name,String Category, decimal Price)
{
		//...
}

and you are trying to call using


  $.ajax({

  url: 'api/products',
  type: 'POST',
  data: { Id: 2012, Name: 'test', Category: 'My Category', Price: 99.5 },
  dataType: 'json',
  success: function (data) {
 alert(data);
 }

 }); 

Unfortunately, Web API can't handle this request and you'll get error. But if you pass parameters using query string, It'll work:


 $.ajax({

  url: 'api/products?Id=2012&Name=test&Category=My%20Category&Price=99.5',
  type: 'POST',                
  dataType: 'json',
  success: function (data) {
  alert(data);
   }

  }); 

But it's not good solution and not applicable for complex objects. So here are the different ways to do it.

Using Model Binding:

Model: Create a model having all parameters to be passed


 public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }

Controller: Use the model as argument type in the method


public HttpResponseMessage PostProduct(Product item)
 {
     item = repository.Add(item);
     var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
     string uri = Url.Link("DefaultApi", new { id = item.Id });
     response.Headers.Location = new Uri(uri);
     return response;
 }

jQuery:


 $.ajax({
  url: 'api/products',
  type: 'POST',
  data: { Id: 2012, Name: 'test', Category: 'My Category', Price: 99.5 },
  dataType: 'json',
  success: function (data) {
                    ...
   }
 }); 

OR


  var product = {
             Id: 2012, Name: 'test', Category: 'My Category', Price: 99.5 
 }

 $.ajax({
  url: 'api/products',
  type: 'POST',
  data: JSON.stringify(product),
  dataType: 'json',
  contentType: "application/json",
  success: function (data) {

  }
 });

Using Custom Parameter Binding:

ASP.NET Web API provides to create a custom Parameter Binding to extend the functionality and allows you to intercept processing of individual parameters. Check Rick's Post to implement it.

JObject:

Web API now uses JSON.NET for it's JSON serializer, So you can use the JObject class to receive a dynamic JSON result, cast it properly or parse it into strongly typed objects.


 public HttpResponseMessage PostProduct(JObject data)
        {
            dynamic json = data;
            Product item = new Product() { 
                Id = json.Id, 
                Name = json.Name, 
                Category = json.Category, 
                Price =json.Price 
            }; 
            item = repository.Add(item);
            var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
            string uri = Url.Link("DefaultApi", new { id = item.Id });
            response.Headers.Location = new Uri(uri);
            return response;
        }

OR

You can directly parse it into strongly typed class:


 public HttpResponseMessage PostProduct(JObject data)
        {

            Product item = data.ToObject<Product>();  
            item = repository.Add(item);
            var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
            string uri = Url.Link("DefaultApi", new { id = item.Id });
            response.Headers.Location = new Uri(uri);
            return response;
        }

FormDataCollection:

You can define FormDataCollection type argument and read parameter one by one manually using .Get() or .GetValues() methods (for multi-select values).

data: { Id: 2012, Name: 'test', Category: 'My Category', Price: 99.5 },

For above data:


 public HttpResponseMessage PostProduct(FormDataCollection data)
        {
            Product item = new Product() { 
                Id = Convert.ToInt32(data.Get("Id")), 
                Name = data.Get("Name"), 
                Category = data.Get("Category"), 
                Price = Convert.ToDecimal(data.Get("Price")) 
            }; 
            item = repository.Add(item);
            var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);
            string uri = Url.Link("DefaultApi", new { id = item.Id });
            response.Headers.Location = new Uri(uri);
            return response;
        }

Query String:

We have seen to access multiple parameters from query string easily. It's very helpful when you have to pass Model with additional parameters. So the additional parameters can be passed using query string.

Controller:


   public HttpResponseMessage PostProduct(Product item, string criteria)
        {
		//...
		}

jQuery:


 $.ajax({
   url: 'api/products?criteria=full',
   type: 'POST',
   data: JSON.stringify(product),
   dataType: 'json',
   contentType: "application/json",
   success: function (data) {
                }
            });

You can parse your query string explicitly in method If you have too many query string values to push into parameters.


  public HttpResponseMessage PostProduct(Product item)
        {           
            var queryItems = Request.RequestUri.ParseQueryString();
            string criteria = queryItems["criteria"];
			//...
	}

Return Multiple Parameters:

Now, the next question is how to return multiple parameters. First thing is to use Model having all parameters and return its instance, but each time we can't create model for each return type. If all parameters have same data-type then we can create and return a collection. But it might be different data-types. The simplest solution is use dynamic return type.


 public dynamic GetProducts()
        {
            var products = repository.GetAll() as IEnumerable<Product>; 
            return new
            {
               Products = products,
               Criteria = "full"
            };
        }

Here is the response:

multiple parameters

Share your opinion how you are passing multiple parameters to Web API. Hopefully, this behavior can be enabled in future release without being a breaking change.