Introduction
This is a POC to determine most simple way to pass collections of complex objects to ASP.NET Web API Controller. New methods are added to Controller and Data Repository to allow collections to be passed to Add
and Update
methods in request payload. To use solution (VS2012), unzip and insure NuGet auto-restore is on for project.
Background
I made a number of attempts to pass collections to Web API, using Json Serialization and Deserialization and/or container object to hold the collections with varying success. The updated versions of ASP.NET Web API allows for behind-the-scenes serialization that enable collections to be passed directly to controller methods as payload in POST or PUT messages. The attached VS2012 Solution demonstrates minimal coding required to allow single call to Web API Controller to process multiple objects. This should be useful for large sets of objects being inserted or updated at the same time, to minimize calls to the API Controller.
I based my test code on the following examples. These are basic ASP.NET Web API tutorial projects using a Product
object passed singly to controller CRUD methods. I added a Console client project for testing new methods for Collections of Product Items instead of single items...here and
here.
I found an example of batching Web API calls but I haven't evaluated it. I think it uses separate calls for each command. With batching, calls to add, update, or delete items can be combined and can be individually monitored. Batching example can be found
here [
^].
Using the Code
I changed WebApiConfig
to include method name. Register
method in WebApiConfig
should look like:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
This is necessary to allow for multiple POST
and PUT
methods to be in the same controller.
If route template is not adjusted, call to controller will generate an error saying there are multiple methods with same signature (it will just look at Http Verb, not method name). Simple controller method to post multiple Products:
public IEnumerable<Product> PostProducts([FromBody]IEnumerable<Product> pList)
{
return repository.AddProducts(pList);
}
Then in repository call the single-item Add(Product)
method for each item in collection:
public IEnumerable<product> AddProducts(IEnumerable<Product> pList)
{
List<product> pNewProducts = new List <product>();
Product prod = new Product();
foreach (Product p in pList)
{
prod = Get(p.Id);
if (p.Equals(Add(p)))
pNewProducts.Add(p);
}
return pNewProducts;
}
When updating a single item, the item id is passed in the URI query string. But in the Put
method for updating records, no ID is sent as separate parameter in URI query string. Instead, each object contains its normal ID field for matching in repository:
public IEnumerable<product> PutProducts([FromBody]List<Product> pList)
{
return repository.UpdateProducts(pList);
}
Created console client to test ProductController
and repository methods. Below is test for adding multiple products -- a List<product>
collection is created and passed in body of PostAsJsonAsync
call to Web API.
Included in the Web API project are original MVC pages for single item add/update. I created the console client to test the adding or updating collections of Products. There is no multiple-item delete method but one could be easily added by passing either a collection of items to a new DELETE
controller method as in the PUT
or POST
, or an array of id values could be passed and each id passed in turn to the original delete method. Note that to delete a collection of items, I think it's necessary to use POST
or PUT
and not DELETE
as http action.
All dependencies have been removed but should be restored during build so long as auto-restore NuGet is activated.
private static async Task<list<product>> postProducts ()
{
HttpClient client = new HttpClient();
List<product> products = new List<product>();
Product p1 = new Product();
p1.Name = "Product4";
p1.Price = 111.1M;
p1.Category = "Condiments";
products.Add(p1);
Product p2 = new Product();
p2.Name = "Product5";
p2.Price = 222.2M;
p2.Category = "Hardware";
products.Add(p2);
Product p3 = new Product();
p3.Name = "Product6";
p3.Price = 333.3M;
p3.Category = "Toys";
products.Add(p3);
response = await client.PostAsJsonAsync<list
<product>>(serviceUrl + @"/PostProducts", products);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine("ERROR: Products Not Posted." + response.ReasonPhrase);
return null;
}
products = await response.Content.ReadAsAsync<list<product>>();
return products;
}
If I can get to it, I'll set up a test to add, update, or delete large numbers of items to determine how much efficiency is gained by submitting a collection to an API controller compared to individual calls for each add/update/delete action.
If anyone has any questions or sees that this code could work better in any way, please let me know. Please bear in mind this is a proof of concept and NOT meant to be production-ready code.
Thanks for reading.