{"id":1054,"date":"2013-11-01T18:05:03","date_gmt":"2013-11-02T01:05:03","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/webdev\/2013\/11\/01\/introducing-batch-support-in-web-api-and-web-api-odata\/"},"modified":"2022-08-09T03:17:59","modified_gmt":"2022-08-09T10:17:59","slug":"introducing-batch-support-in-web-api-and-web-api-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/introducing-batch-support-in-web-api-and-web-api-odata\/","title":{"rendered":"Introducing batch support in Web API and Web API OData"},"content":{"rendered":"<p>With the release of Microsoft ASP.NET Web API 2 OData, we have introduced support for batching requests. Batching is a web API feature that allows a customer to pack several API requests and send them to the web API service in one HTTP request and receive a single HTTP response with the response to all their requests. This way, the client can optimize calls to the server and improve the scalability of its service.<\/p>\n<p>For a more in depth look at the batch support, you can take a look at the <a href=\"http:\/\/aspnetwebstack.codeplex.com\/wikipage?title=Web%20API%20Request%20Batching\">specification<\/a>.<\/p>\n<h3>Batch in Web API<\/h3>\n<p>In order to start using batch in Web API, the only requirement is to register a route with a batch handler. Let\u2019s start by creating the server. For this sample we will be using the brand new OWIN host for Web API. The code below creates the server and configures a web API route and a batch route.<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">static<\/span> <span class=\"kwrd\">void<\/span> Main(<span class=\"kwrd\">string<\/span>[] args)\r\n{\r\n    <span class=\"kwrd\">string<\/span> serviceUrl = <span class=\"str\">&quot;http:\/\/localhost:12345&quot;<\/span>;\r\n    <span class=\"kwrd\">using<\/span> (WebApp.Start(serviceUrl, Configuration))\r\n    {\r\n\r\n        Console.WriteLine(<span class=\"str\">&quot;Service listening at {0}&quot;<\/span>, serviceUrl);\r\n        Console.WriteLine(<span class=\"str\">&quot;Press any key to stop the service and exit the application&quot;<\/span>);\r\n        Console.ReadKey();\r\n    }\r\n}\r\n\r\n<span class=\"kwrd\">private<\/span> <span class=\"kwrd\">static<\/span> <span class=\"kwrd\">void<\/span> Configuration(IAppBuilder builder)\r\n{\r\n    HttpConfiguration configuration = <span class=\"kwrd\">new<\/span> HttpConfiguration();\r\n    HttpServer server = <span class=\"kwrd\">new<\/span> HttpServer(configuration);\r\n    configuration.Routes.MapHttpBatchRoute(\r\n        routeName:<span class=\"str\">&quot;batch&quot;<\/span>,\r\n        routeTemplate:<span class=\"str\">&quot;api\/batch&quot;<\/span>,\r\n        batchHandler:<span class=\"kwrd\">new<\/span> DefaultHttpBatchHandler(server));\r\n    configuration.Routes.MapHttpRoute(<span class=\"str\">&quot;api&quot;<\/span>, <span class=\"str\">&quot;api\/{controller}\/{id}&quot;<\/span>, <span class=\"kwrd\">new<\/span> { id = RouteParameter.Optional });\r\n    builder.UseWebApi(server);\r\n}\r\n<\/pre>\n<p>If we look in detail to the code in the Configuration method, we can see the following method call is the only thing required to enable batching in your Web API service:<\/p>\n<pre class=\"csharpcode\">configuration.Routes.MapHttpBatchRoute(\r\n        routeName:<span class=\"str\">&quot;batch&quot;<\/span>,\r\n        routeTemplate:<span class=\"str\">&quot;api\/batch&quot;<\/span>,\r\n        batchHandler:<span class=\"kwrd\">new<\/span> DefaultHttpBatchHandler(server));\r\n<\/pre>\n<p>The important things to notice are the following:<\/p>\n<ul>\n<li>The DefaultHttpBatchHandler requires an instance of an HttpServer to work. We can create this instance ourselves in the self-host scenario or we can get it by accessing it from GlobalConfiguration.DefaultServer.<\/li>\n<li>The DefaultHttpBatchHandler executes each request sequentially, but in case you don\u2019t have any dependency between requests, you can set a property to make execution non-sequential.<\/li>\n<li>The MapHttpBatchRoute route template can contain parameters. So, in case you create your own batch handler, you can pass in parameters to it.<\/li>\n<\/ul>\n<p>That\u2019s all that is required to enable batch in your service. Let\u2019s see how to send batch requests and read batch responses using HttpClient. In order to do that, we are going to need a model, a web API controller and a route to dispatch the incoming requests. We will be using Entity Framework as the backend and AutoFixture to generate some sample data for the service. Here is the code for all of it:<\/p>\n<p>Route:<\/p>\n<pre class=\"csharpcode\">configuration.Routes.MapHttpRoute(\r\n    name: <span class=\"str\">&quot;api&quot;<\/span>,\r\n    routeTemplate: <span class=\"str\">&quot;api\/{controller}\/{id}&quot;<\/span>,\r\n    defaults: <span class=\"kwrd\">new<\/span> { id = RouteParameter.Optional });\r\n<\/pre>\n<p>Model:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> Customer\r\n{\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">int<\/span> Id { get; set; }\r\n    <span class=\"kwrd\">public<\/span> <span class=\"kwrd\">string<\/span> Name { get; set; }\r\n}\r\n<\/pre>\n<p>Controller:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> WebCustomersController : ApiController\r\n{\r\n    CustomersContext context = <span class=\"kwrd\">new<\/span> CustomersContext();\r\n    [Queryable(PageSize = 10, MaxExpansionDepth = 2)]\r\n    <span class=\"kwrd\">public<\/span> IHttpActionResult Get()\r\n    {\r\n        <span class=\"kwrd\">return<\/span> Ok(context.Customers);\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Post([FromBody] Customer entity)\r\n    {\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(ModelState);\r\n        }\r\n        context.Customers.Add(entity);\r\n        await context.SaveChangesAsync();\r\n        <span class=\"kwrd\">return<\/span> CreatedAtRoute(<span class=\"str\">&quot;api&quot;<\/span>, <span class=\"kwrd\">new<\/span> { controller = <span class=\"str\">&quot;ApiCustomers&quot;<\/span> }, entity);\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Put(<span class=\"kwrd\">int<\/span> id, [FromBody] Customer entity)\r\n    {\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(ModelState);\r\n        }\r\n        <span class=\"kwrd\">else<\/span> <span class=\"kwrd\">if<\/span> (id != entity.Id)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(<span class=\"str\">&quot;The key from the url must match the key of the entity in the body&quot;<\/span>);\r\n        }\r\n        var originalCustomer = await context.Customers.FindAsync(id);\r\n        <span class=\"kwrd\">if<\/span> (originalCustomer == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> NotFound();\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {\r\n            context.Entry(originalCustomer).CurrentValues.SetValues(entity);\r\n            await context.SaveChangesAsync();\r\n        }\r\n        <span class=\"kwrd\">return<\/span> Content(HttpStatusCode.OK, entity);\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Delete(<span class=\"kwrd\">int<\/span> id)\r\n    {\r\n        Customer entity = await context.Customers.FindAsync(id);\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> NotFound();\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {\r\n            context.Customers.Remove(entity);\r\n            await context.SaveChangesAsync();\r\n            <span class=\"kwrd\">return<\/span> StatusCode(HttpStatusCode.NoContent);\r\n        }\r\n    }\r\n}<\/pre>\n<p>Entity framework context:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> CustomersContext : DbContext\r\n{\r\n    <span class=\"kwrd\">static<\/span> CustomersContext()\r\n    {\r\n        Database.SetInitializer&lt;CustomersContext&gt;(<span class=\"kwrd\">new<\/span> CustomersContextInitializer());\r\n    }\r\n\r\n    <span class=\"kwrd\">protected<\/span> <span class=\"kwrd\">override<\/span> <span class=\"kwrd\">void<\/span> OnModelCreating(DbModelBuilder modelBuilder)\r\n    {\r\n        <span class=\"kwrd\">base<\/span>.OnModelCreating(modelBuilder);\r\n    }\r\n\r\n    <span class=\"kwrd\">private<\/span> <span class=\"kwrd\">class<\/span> CustomersContextInitializer : DropCreateDatabaseAlways&lt;CustomersContext&gt;\r\n    {\r\n        <span class=\"kwrd\">protected<\/span> <span class=\"kwrd\">override<\/span> <span class=\"kwrd\">void<\/span> Seed(CustomersContext context)\r\n        {\r\n            Fixture fixture = <span class=\"kwrd\">new<\/span> Fixture();\r\n            IEnumerable&lt;Customer&gt; customers = fixture.CreateMany&lt;Customer&gt;(20).ToList();\r\n            context.Customers.AddRange(customers);\r\n        }\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> DbSet&lt;Customer&gt; Customers { get; set; }\r\n}\r\n<\/pre>\n<p>Now that we have a running service with some data on it, and we have enabled batching support, we can move on to actually perform batch requests. The default Web API batch implementation is based on the mime\/multipart content type. It accepts mime\/multipart requests containing multiple HTTP Requests and processes all the requests sending a mime\/multipart response containing the individual responses. Let\u2019s start by creating some requests:<\/p>\n<pre class=\"csharpcode\">Fixture fixture = <span class=\"kwrd\">new<\/span> Fixture();\r\nHttpClient client = <span class=\"kwrd\">new<\/span> HttpClient();\r\ndynamic listOfCustomers = JToken.Parse(await client.GetStringAsync(<span class=\"str\">&quot;http:\/\/localhost:12345\/api\/WebCustomers&quot;<\/span>));\r\ndynamic firstCustomer = listOfCustomers[0];\r\nfirstCustomer.Name = <span class=\"str\">&quot;Peter&quot;<\/span>;\r\ndynamic secondCustomer = listOfCustomers[1];\r\nJsonMediaTypeFormatter formatter = <span class=\"kwrd\">new<\/span> JsonMediaTypeFormatter();\r\n\r\n<span class=\"rem\">\/\/Create a request to query for customers<\/span>\r\nHttpRequestMessage queryCustomers = <span class=\"kwrd\">new<\/span> HttpRequestMessage(HttpMethod.Get, <span class=\"str\">&quot;http:\/\/localhost:13245\/api\/WebCustomers&quot;<\/span>);\r\n<span class=\"rem\">\/\/Create a message to add a customer<\/span>\r\nHttpRequestMessage addCustomer = <span class=\"kwrd\">new<\/span> HttpRequestMessage(HttpMethod.Post, <span class=\"str\">&quot;http:\/\/localhost:13245\/api\/WebCustomers&quot;<\/span>);\r\naddCustomer.Content = <span class=\"kwrd\">new<\/span> ObjectContent&lt;Customer&gt;(fixture.Create&lt;Customer&gt;(), formatter);\r\n<span class=\"rem\">\/\/Create a message to update a customer<\/span>\r\nHttpRequestMessage updateCustomer = <span class=\"kwrd\">new<\/span> HttpRequestMessage(HttpMethod.Put, <span class=\"kwrd\">string<\/span>.Format(<span class=\"str\">&quot;http:\/\/localhost:13245\/api\/WebCustomers\/{0}&quot;<\/span>, firstCustomer.Id));\r\nupdateCustomer.Content = <span class=\"kwrd\">new<\/span> ObjectContent&lt;dynamic&gt;(firstCustomer, formatter);\r\n<span class=\"rem\">\/\/Create a message to remove a customer.<\/span>\r\nHttpRequestMessage removeCustomer = <span class=\"kwrd\">new<\/span> HttpRequestMessage(HttpMethod.Delete, <span class=\"kwrd\">string<\/span>.Format(<span class=\"str\">&quot;http:\/\/localhost:13245\/api\/WebCustomers\/{0}&quot;<\/span>, secondCustomer.Id));<\/pre>\n<p>The first block before the blank line only performs a query to get some customers that we can update and delete in a request later. At this point, we could just send four requests using HttpClient, and our service would just process the requests and send individual responses back.<\/p>\n<p>In order to batch those requests together into a single HTTP request, we need to encapsulate them into HttpMessageContent instances and add those instances to a MultipartContent instance. Here is the code to do that:<\/p>\n<pre class=\"csharpcode\"><span class=\"rem\">\/\/Create the different parts of the multipart content<\/span>\r\nHttpMessageContent queryContent = <span class=\"kwrd\">new<\/span> HttpMessageContent(queryCustomers);\r\nHttpMessageContent addCustomerContent = <span class=\"kwrd\">new<\/span> HttpMessageContent(addCustomer);\r\nHttpMessageContent updateCustomerContent = <span class=\"kwrd\">new<\/span> HttpMessageContent(updateCustomer);\r\nHttpMessageContent removeCustomerContent = <span class=\"kwrd\">new<\/span> HttpMessageContent(removeCustomer);\r\n\r\n<span class=\"rem\">\/\/Create the multipart\/mixed message content<\/span>\r\nMultipartContent content = <span class=\"kwrd\">new<\/span> MultipartContent(<span class=\"str\">&quot;mixed&quot;<\/span>, <span class=\"str\">&quot;batch_&quot;<\/span> + Guid.NewGuid().ToString());\r\ncontent.Add(queryContent);\r\ncontent.Add(addCustomerContent);\r\ncontent.Add(updateCustomerContent);\r\ncontent.Add(removeCustomerContent);<\/pre>\n<p>If you look at the code above, the multipart content needs to have a subtype of mixed, and a boundary which is a unique identifier that determines the separation between the different parts of the content.<\/p>\n<p>Now that we have created the multipart\/mixed content, the only last thing that we need to do in order to have a valid batch request is create the HTTP request and associate the content to it. We can do that as we see in the following fragment:<\/p>\n<pre class=\"csharpcode\"><span class=\"rem\">\/\/Create the request to the batch service<\/span>\r\nHttpRequestMessage batchRequest = <span class=\"kwrd\">new<\/span> HttpRequestMessage(HttpMethod.Post, <span class=\"str\">&quot;http:\/\/localhost:12345\/api\/batch&quot;<\/span>);\r\n<span class=\"rem\">\/\/Associate the content with the message<\/span>\r\nbatchRequest.Content = content;<\/pre>\n<p>With this last part, we are ready to send a batch request to the server and get a response. To do that, we just use an HttpClient instance and call SendAsync passing the request message to it and capture the associated response. If we just do that, the following request gets send and the following response comes back:<\/p>\n<p>Request:<\/p>\n<pre class=\"csharpcode\">POST http:\/\/localhost:12345\/api\/batch HTTP\/1.1\r\nContent-Type: multipart\/mixed; boundary=&quot;batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e&quot;\r\nHost: localhost:12345\r\nContent-Length: 857\r\nExpect: 100-continue\r\n\r\n--batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\r\nContent-Type: application\/http; msgtype=request\r\n\r\nGET \/api\/WebCustomers HTTP\/1.1\r\nHost: localhost:13245\r\n\r\n\r\n--batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\r\nContent-Type: application\/http; msgtype=request\r\n\r\nPOST \/api\/WebCustomers HTTP\/1.1\r\nHost: localhost:13245\r\nContent-Type: application\/json; charset=utf-8\r\n\r\n{&quot;Id&quot;:129,&quot;Name&quot;:&quot;Name4752cbf0-e365-43c3-aa8d-1bbc8429dbf8&quot;}\r\n--batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\r\nContent-Type: application\/http; msgtype=request\r\n\r\nPUT \/api\/WebCustomers\/1 HTTP\/1.1\r\nHost: localhost:13245\r\nContent-Type: application\/json; charset=utf-8\r\n\r\n{&quot;Id&quot;:1,&quot;Name&quot;:&quot;Peter&quot;}\r\n--batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\r\nContent-Type: application\/http; msgtype=request\r\n\r\nDELETE \/api\/WebCustomers\/2 HTTP\/1.1\r\nHost: localhost:13245\r\n\r\n\r\n--batch_357647d1-a6b5-4e6a-aa73-edfc88d8866e--\r\n<\/pre>\n<p>If we look at the request above, we can see, as we have said before, that all the messages are separated with a boundary, which in this case is \u201cbatch_357647d1-a6b5-4e6a-aa73-edfc88d8866e\u201d. Also, we can see that every request message has a Content-Type of application\/http. This header is introduced by the HttpContent where we wrapped all our requests. If we wanted to, we could have added extra headers that we could read and use for handling the processing of the message in a custom batch handler.<\/p>\n<p>If we look at the response below we can clearly see that it follows the same pattern, with a multipart\/mixed content type, a boundary to separate the different parts of the multipart and a collection of responses encapsulated in HttpContent parts.<\/p>\n<p>Response:<\/p>\n<pre class=\"csharpcode\">HTTP\/1.1 200 OK\r\nContent-Length: 1373\r\nContent-Type: multipart\/mixed; boundary=<span class=\"str\">&quot;61cfbe41-7ea6-4771-b1c5-b43564208ee5&quot;<\/span>\r\nServer: Microsoft-HTTPAPI\/2.0\r\nDate: Fri, 25 Oct 2013 06:30:14 GMT\r\n\r\n--61cfbe41-7ea6-4771-b1c5-b43564208ee5\r\nContent-Type: application\/http; msgtype=response\r\n\r\nHTTP\/1.1 200 OK\r\nContent-Type: application\/json; charset=utf-8\r\n\r\n[{<span class=\"str\">&quot;Id&quot;<\/span>:1,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Namefc4b8794-943b-487a-9049-a8559232b9dd&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:2,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name244bbada-3e83-43c8-82f7-5b2c4d72f2ed&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:3,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Nameec11d080-7f2d-47df-a483-7ff251cdda7a&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:4,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name14ff5a3d-ad92-41f6-b4f6-9b94622f4968&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:5,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name00f9e4cc-673e-4139-ba30-bfc273844678&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:6,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name01f6660c-d1de-4c05-8567-8ae2759c4117&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:7,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name60030a17-6316-427c-a744-b2fff6d9fe11&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:8,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Namefa61eb4c-9f9e-47a2-8dc5-15d8afe33f2d&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:9,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name9b680c10-1727-43f5-83cf-c8eda3a63790&quot;<\/span>},{<span class=\"str\">&quot;Id&quot;<\/span>:10,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name9e66d797-d3a9-44ec-814d-aecde8040ced&quot;<\/span>}]\r\n--61cfbe41-7ea6-4771-b1c5-b43564208ee5\r\nContent-Type: application\/http; msgtype=response\r\n\r\nHTTP\/1.1 201 Created\r\nLocation: http:<span class=\"rem\">\/\/localhost:13245\/api\/ApiCustomers<\/span>\r\nContent-Type: application\/json; charset=utf-8\r\n\r\n{<span class=\"str\">&quot;Id&quot;<\/span>:21,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Name4752cbf0-e365-43c3-aa8d-1bbc8429dbf8&quot;<\/span>}\r\n--61cfbe41-7ea6-4771-b1c5-b43564208ee5\r\nContent-Type: application\/http; msgtype=response\r\n\r\nHTTP\/1.1 200 OK\r\nContent-Type: application\/json; charset=utf-8\r\n\r\n{<span class=\"str\">&quot;Id&quot;<\/span>:1,<span class=\"str\">&quot;Name&quot;<\/span>:<span class=\"str\">&quot;Peter&quot;<\/span>}\r\n--61cfbe41-7ea6-4771-b1c5-b43564208ee5\r\nContent-Type: application\/http; msgtype=response\r\n\r\nHTTP\/1.1 204 No Content\r\n\r\n\r\n--61cfbe41-7ea6-4771-b1c5-b43564208ee5--<\/pre>\n<p>Finally, now that we have sent and received a response we need to read it and extract all the individual responses from the content. For accomplishing this task, we can use the ReadAsMimeMultipartAsync and ReadAsHttpResponseMessageAsync methods on the Content property of the response.<\/p>\n<pre class=\"csharpcode\">HttpResponseMessage response = await client.SendAsync(batchRequest);\r\n<span class=\"rem\">\/\/Reads the individual parts in the content and loads them in memory<\/span>\r\nMultipartMemoryStreamProvider responseContents = await response.Content.ReadAsMultipartAsync();\r\n<span class=\"rem\">\/\/Extracts each of the individual Http responses<\/span>\r\nHttpResponseMessage queryResponse = await responseContents.Contents[0].ReadAsHttpResponseMessageAsync();\r\nHttpResponseMessage addResponse = await responseContents.Contents[1].ReadAsHttpResponseMessageAsync();\r\nHttpResponseMessage updateResponse = await responseContents.Contents[2].ReadAsHttpResponseMessageAsync();\r\nHttpResponseMessage removeResponse = await responseContents.Contents[3].ReadAsHttpResponseMessageAsync();\r\n<\/pre>\n<p>And with this, we have successfully sent and read a batch request to Web API. Let\u2019s look at how to do the same thing with Web API OData.<\/p>\n<h3>Batch in OData<\/h3>\n<p>In order to use batch with OData services, the flow is very similar to the flow in Web API. We will be reusing the model and the backend from the above sample, and the only things we\u2019ll need are an OData model a route that defines the OData endpoint and a controller to handle the incoming requests. All of which can be shown below:<\/p>\n<p>Route:<\/p>\n<pre class=\"csharpcode\">configuration.Routes.MapODataRoute(<span class=\"str\">&quot;odata&quot;<\/span>, <span class=\"str\">&quot;odata&quot;<\/span>, GetModel(), <span class=\"kwrd\">new<\/span> DefaultODataBatchHandler(server));<\/pre>\n<p>As you can see from the fragment above, MapODataRoute accepts an ODataBatchHandler as a parameter. In this case, we are using the DefaultODataBatchHandler, but we could also use the UnbufferedODataBatchHandler.<\/p>\n<p>OData model:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">private<\/span> <span class=\"kwrd\">static<\/span> IEdmModel GetModel()\r\n{\r\n    ODataModelBuilder builder = <span class=\"kwrd\">new<\/span> ODataConventionModelBuilder();\r\n    builder.ContainerName = <span class=\"str\">&quot;CustomersContext&quot;<\/span>;\r\n    EntitySetConfiguration&lt;Customer&gt; customers = builder.EntitySet&lt;Customer&gt;(<span class=\"str\">&quot;Customers&quot;<\/span>);\r\n    <span class=\"kwrd\">return<\/span> builder.GetEdmModel();\r\n}\r\n<\/pre>\n<p>Controller:<\/p>\n<pre class=\"csharpcode\"><span class=\"kwrd\">public<\/span> <span class=\"kwrd\">class<\/span> CustomersController : ODataController\r\n{\r\n    CustomersContext context = <span class=\"kwrd\">new<\/span> CustomersContext();\r\n    [Queryable(PageSize = 10, MaxExpansionDepth = 2)]\r\n    <span class=\"kwrd\">public<\/span> IHttpActionResult Get()\r\n    {\r\n        <span class=\"kwrd\">return<\/span> Ok(context.Customers);\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Post([FromBody] Customer entity)\r\n    {\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(ModelState);\r\n        }\r\n        context.Customers.Add(entity);\r\n        await context.SaveChangesAsync();\r\n        <span class=\"kwrd\">return<\/span> Created(entity);\r\n    }\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Put([FromODataUri] <span class=\"kwrd\">int<\/span> key, [FromBody] Customer entity)\r\n    {\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(ModelState);\r\n        }\r\n        <span class=\"kwrd\">else<\/span> <span class=\"kwrd\">if<\/span> (key != entity.Id)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(<span class=\"str\">&quot;The key from the url must match the key of the entity in the body&quot;<\/span>);\r\n        }\r\n        var originalCustomer = await context.Customers.FindAsync(key);\r\n        <span class=\"kwrd\">if<\/span> (originalCustomer == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> NotFound();\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {\r\n            context.Entry(originalCustomer).CurrentValues.SetValues(entity);\r\n            await context.SaveChangesAsync();\r\n        }\r\n        <span class=\"kwrd\">return<\/span> Updated(entity);\r\n    }\r\n\r\n    [AcceptVerbs(<span class=\"str\">&quot;PATCH&quot;<\/span>, <span class=\"str\">&quot;MERGE&quot;<\/span>)]\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Patch([FromODataUri] <span class=\"kwrd\">int<\/span> key, Delta&lt;Customer&gt; patch)\r\n    {\r\n        <span class=\"kwrd\">object<\/span> id;\r\n        <span class=\"kwrd\">if<\/span> (patch == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(<span class=\"str\">&quot;The entity is malformed&quot;<\/span>);\r\n        }\r\n        <span class=\"kwrd\">else<\/span> <span class=\"kwrd\">if<\/span> (patch.TryGetPropertyValue(<span class=\"str\">&quot;Id&quot;<\/span>, <span class=\"kwrd\">out<\/span> id) &amp;&amp; (<span class=\"kwrd\">int<\/span>)id != key)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> BadRequest(<span class=\"str\">&quot;The key from the url must match the key of the entity in the body&quot;<\/span>);\r\n        }\r\n        Customer originalEntity = await context.Customers.FindAsync(key);\r\n        <span class=\"kwrd\">if<\/span> (originalEntity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> NotFound();\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {\r\n            patch.Patch(originalEntity);\r\n            await context.SaveChangesAsync();\r\n        }\r\n        <span class=\"kwrd\">return<\/span> Updated(originalEntity);\r\n    }\r\n\r\n\r\n    <span class=\"kwrd\">public<\/span> async Task&lt;IHttpActionResult&gt; Delete([FromODataUri]<span class=\"kwrd\">int<\/span> key)\r\n    {\r\n        Customer entity = await context.Customers.FindAsync(key);\r\n        <span class=\"kwrd\">if<\/span> (entity == <span class=\"kwrd\">null<\/span>)\r\n        {\r\n            <span class=\"kwrd\">return<\/span> NotFound();\r\n        }\r\n        <span class=\"kwrd\">else<\/span>\r\n        {\r\n            context.Customers.Remove(entity);\r\n            await context.SaveChangesAsync();\r\n            <span class=\"kwrd\">return<\/span> StatusCode(HttpStatusCode.NoContent);\r\n        }\r\n    }\r\n}<\/pre>\n<p>We could use HttpClient to send batch requests to our OData service, but it\u2019s much easier if we make use of the generated client built in through the Add Service Reference dialog in Visual Studio 2013.<\/p>\n<p>To do that, we can just start the service by pressing Ctrl+F5, then we only need to go to the References icon of the project Right Click, Add Service Reference and give it the metadata url of our service which in this case is <a href=\"http:\/\/localhost:12345\/odata\/$metadata\">http:\/\/localhost:12345\/odata\/$metadata<\/a>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/11\/7242.clip_image002_thumb_1BFDD532.jpg\"><img decoding=\"async\" title=\"clip_image002\" border=\"0\" alt=\"clip_image002\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2013\/11\/7242.clip_image002_thumb_1BFDD532.jpg\" width=\"703\" height=\"298\" \/><\/a><\/p>\n<p>Now that we have a generated client, we can just start using it to work with the OData endpoint and batch requests. The generated client supports two flows. It can batch a set of queries and return a batch response with the responses to the individual queries, or it can batch a change set of requests that perform modifications on the server. <\/p>\n<p>In order to batch and read a set of queries, we can do the following:<\/p>\n<pre class=\"csharpcode\">CustomersContext context = <span class=\"kwrd\">new<\/span> CustomersContext(<span class=\"kwrd\">new<\/span> Uri(<span class=\"str\">&quot;http:\/\/localhost:12345\/odata\/&quot;<\/span>));\r\ncontext.Format.UseJson();\r\n<span class=\"rem\">\/\/Create the queries<\/span>\r\nDataServiceRequest&lt;Customer&gt; firstTwoCustomers = <span class=\"kwrd\">new<\/span> DataServiceRequest&lt;Customer&gt;(<span class=\"kwrd\">new<\/span> Uri(<span class=\"str\">&quot;http:\/\/localhost:12345\/odata\/Customers?$top=2&amp;$orderby=Id&quot;<\/span>));\r\nDataServiceRequest&lt;Customer&gt; nextTwoCustomers = <span class=\"kwrd\">new<\/span> DataServiceRequest&lt;Customer&gt;(<span class=\"kwrd\">new<\/span> Uri(<span class=\"str\">&quot;http:\/\/localhost:12345\/odata\/Customers?$skip=2&amp;$top=2&amp;$orderby=Id&quot;<\/span>));\r\n<span class=\"rem\">\/\/Send the queries<\/span>\r\nDataServiceResponse batchResponse = context.ExecuteBatch(firstTwoCustomers, nextTwoCustomers);\r\n<span class=\"kwrd\">foreach<\/span> (QueryOperationResponse response <span class=\"kwrd\">in<\/span> batchResponse)\r\n{\r\n    <span class=\"kwrd\">foreach<\/span>(Customer c <span class=\"kwrd\">in<\/span> response.Cast&lt;Customer&gt;())\r\n    {\r\n        <span class=\"rem\">\/\/Do something<\/span>\r\n    }\r\n}<\/pre>\n<p>In order to batch a set of changes to the server, we can do the following:<\/p>\n<pre class=\"csharpcode\">Fixture fixture = <span class=\"kwrd\">new<\/span> Fixture();\r\nCustomersContext context = <span class=\"kwrd\">new<\/span> CustomersContext(<span class=\"kwrd\">new<\/span> Uri(<span class=\"str\">&quot;http:\/\/localhost:12345\/odata\/&quot;<\/span>));\r\ncontext.Format.UseJson();\r\nIList&lt;Customer&gt; customers = context.Customers.ToList();\r\nCustomer customerToAdd = fixture.Create&lt;Customer&gt;();\r\nCustomer customerToUpdate = customers.Skip(1).First();\r\nCustomer customerToDelete = customers.Skip(2).First();\r\ncontext.AddToCustomers(customerToAdd);\r\ncustomerToUpdate.Name = <span class=\"str\">&quot;Peter&quot;<\/span>;\r\ncontext.UpdateObject(customerToUpdate);\r\ncontext.DeleteObject(customerToDelete);\r\nDataServiceResponse response = context.SaveChanges(SaveChangesOptions.Batch | SaveChangesOptions.ReplaceOnUpdate);<\/pre>\n<p>Finally, the wire format for OData batch requests is the same as the format for Web API batch requests but with a few minor differences. In OData, requests in a batch are divided in two categories. query operations and change sets. query operations, as it name implies, don\u2019t perform any modification on the server, in contrast with change sets, that basically group a set of state changing operations as a unit.<\/p>\n<p>These concepts get reflected on the format of the request and of the response. Basically change sets get encoded as nested mime\/multipart parts of the external mime\/multipart content that is the batch request. The response follows the same structure, as we can see in the response below:<\/p>\n<pre class=\"csharpcode\">HTTP\/1.1 202 Accepted\r\nContent-Length: 1088\r\nContent-Type: multipart\/mixed; boundary=batchresponse_34d95fb9-d930-4443-8b8b-774b467ba1af\r\nServer: Microsoft-HTTPAPI\/2.0\r\nDataServiceVersion: 3.0\r\nDate: Fri, 25 Oct 2013 08:01:17 GMT\r\n\r\n--batchresponse_34d95fb9-d930-4443-8b8b-774b467ba1af\r\nContent-Type: multipart\/mixed; boundary=changesetresponse_7b32b21f-547b-4eb5-a1ca-cd7b28753fec\r\n\r\n--changesetresponse_7b32b21f-547b-4eb5-a1ca-cd7b28753fec\r\nContent-Type: application\/http\r\nContent-Transfer-Encoding: binary\r\n\r\nHTTP\/1.1 201 Created\r\nLocation: http:\/\/localhost:12345\/odata\/Customers(21)\r\nContent-ID: 11\r\nContent-Type: application\/json; odata=minimalmetadata; charset=utf-8\r\nDataServiceVersion: 3.0\r\n\r\n{\r\n  &quot;odata.metadata&quot;:&quot;http:\/\/localhost:12345\/odata\/$metadata#Customers\/@Element&quot;,&quot;Id&quot;:21,&quot;Name&quot;:&quot;Name7a88d78d-61e0-4951-8852-6b05be5e913b&quot;\r\n}\r\n--changesetresponse_7b32b21f-547b-4eb5-a1ca-cd7b28753fec\r\nContent-Type: application\/http\r\nContent-Transfer-Encoding: binary\r\n\r\nHTTP\/1.1 204 No Content\r\nContent-ID: 12\r\n\r\n\r\n--changesetresponse_7b32b21f-547b-4eb5-a1ca-cd7b28753fec\r\nContent-Type: application\/http\r\nContent-Transfer-Encoding: binary\r\n\r\nHTTP\/1.1 204 No Content\r\nContent-ID: 13\r\n\r\n\r\n--changesetresponse_7b32b21f-547b-4eb5-a1ca-cd7b28753fec--\r\n--batchresponse_34d95fb9-d930-4443-8b8b-774b467ba1af--\r\n<\/pre>\n<p>With this, I conclude our blog post on the support for batch in Web API and Web API OData. I hope you enjoy it and happy coding :).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With the release of Microsoft ASP.NET Web API 2 OData, we have introduced support for batching requests. Batching is a web API feature that allows a customer to pack several API requests and send them to the web API service in one HTTP request and receive a single HTTP response with the response to all [&hellip;]<\/p>\n","protected":false},"author":1866,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197],"tags":[7431,7268,7230,7432],"class_list":["post-1054","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet","tag-odata","tag-web","tag-web-api","tag-webapi"],"acf":[],"blog_post_summary":"<p>With the release of Microsoft ASP.NET Web API 2 OData, we have introduced support for batching requests. Batching is a web API feature that allows a customer to pack several API requests and send them to the web API service in one HTTP request and receive a single HTTP response with the response to all [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1054","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1866"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=1054"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1054\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=1054"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=1054"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=1054"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}