Sending a Collection of Entity Items to a Web API Web Service
Binding parameters in ASP.NET MVC Web API 2 can seem like sorcery (but it's not, so headhunters, please don't ask for sorcery abilities in your next job posting). A while back, I posted about how to bind a collection of query string parameters that have the same name to a string array parameter in Web API 2. The trick, of course, was using [FromUri]
in the method parameters. This allowed Web API to do its magic, behind the scenes bindings.
What if you're not using the query string? What if you have a collection of objects on the front end that you need to pass to the backend, and those objects map to Entity objects in your models? For example, what if you have a listing of people in a table on a web application, and you need to update all records in that table. Or perhaps you want to attach a handful of those people in that listing to a company, and your database reflects this with a Company_X_People
many-to-many table. If your models are reflected on the front end as JSON objects, and your models on the backend are reflected as Entity objects, you might want to maintain that pattern rather than explicitly sending key/value pairs.
Ideally, you would want to send in an array of those JSON objects with the updated values and update them on the backend, or in the case of the many-to-many table, you would want to send in an array of those objects, and perform the inserts on the backend.
Based on what you know about Web API, you'd start by gathering those objects in an array and posting them to a web service that expects an array, right? Nope. That fails. Can't find the web service.
Okay, so now you remember the [FromUri]
parameter binding, and you remember that there is also a [FromBody]
parameter binding. That'll work, right? Nope. That hits the web service, but the parameter is null
.
Maybe you can send the data itself in as a string representation of the objects and construct it on the backend? Okay, now you are taking a JSON object, stringifying it, then attaching that as the string value of a key/value pair that you are wrapping in another JSON object to post to the server. On the backend you have to take that stringified data, probably use something like Newtonsoft's JSON.NET to parse the value, and then use the dynamic
keyword to access the values of that JSON object. Totally doable, but the stringifying gets things a little messy, and this also requires you to jump through a few more hoops to get it working (by the way, this is the route you actually have to take to post a JSON collection of objects from JavaScript to an ASMX web service, but that's another story).
Another option that people on the Internet recommend is creating a Entity object to represent the structure of the collection rather than relying on the fact that you can have IEnumerable<T>
or List<T>
collections of the object. This adds another kludge to the structure.
So what is the best option? Your opinion might differ, but the following solution left the best taste in my mouth, and actually works.
On the JavaScript end, you essentially do a post, but you'll want to stringify the collection. For example, in the below TypeScript code, the data is being accepted as an array of Person
interface objects:
static saveAll(url: string, data: Array<Person>, callback: Function): void {
var self = this;
super.insert(`${url}`, JSON.stringify({ data }), function (result: number) {
callback(result);
});
}
The interface could look like something like this:
interface Person {
firstName: string
,lastName: string
}
In this code, when I call the saveAll()
method, I'm passing the REST URL I need to connect to, an array of Person
objects, and a callback function to execute after the AJAX call successfully returns. The saveAll
method is a method of an object that extends another object, hence the latter call to super
, but we don't need to go over that. All you really need to know is that super.insert()
is a wrapper around the jQuery $.ajax()
function. It's simply there to clean up some default values, such as contentType
, datatype
, etc. All you need to know about the insert()
method here is that it is an AJAX call that sets the method to POST
, and defaults both the content type and the data type to their JSON equivalents, since we are both sending and receiving JSON (actually we're receiving a number back, but that doesn't matter).
Notice that the data is an array of objects, but we simply stringify it and pass that as the data to the AJAX call (This is a simplified version of the stringifying way of accomplishing this mentioned earlier).
The backend code will look like this:
[ResponseType(typeof(int))]
public int PostAllPerson([FromBody]IDictionary<string, object> data)
{
JArray arr;
int result = 0;
try
{
arr = (JArray)data["data"];
result = arr.Count();
foreach (dynamic item in arr)
{
using (DefaultContext db = new DefaultContext())
{
Person.Create(
db
, (string)item.firstName
, (string)item.lastName
);
}
}
return result;
}
finally
{
arr = null;
}
}
This is a sample backend method for a Web API 2 call. The data is received via the [FromBody]
attribute, and the method receives it as an IDictionary
object with a key/value pair as a string/object. The string is "data," while the object is the array of JSON objects. (Don't pay attention to the return value. That's just mock data for the example.)
You can then use Newtonsoft's JSON.NET to cast the data string to a JArray
, and then use the dynamic
keyword while looping through the array to access whatever values you need from the object. In this instance, the data is being passed into a model object's Create()
method to insert new records.