Batch Support

Applies To:yes OData AspNet WebApi V7yes OData AspNet WebApi V6

Batch requests allow grouping multiple operations into a single HTTP request payload and the service will return a single HTTP response with the response to all operations in the requests. This way, the client can optimize calls to the server and improve the scalability of its service.

Please refer to OData Protocol for more detail about batch, and Batch in ODL for batch in ODL client.

Enable Batch in Web API OData Service

It is very easy to enable batch in an OData service which is built by Web API OData.

Add Batch Handler

public static void Register(HttpConfiguration config)
{
    var odataBatchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
    config.MapODataServiceRoute("odata", "odata", GetModel(), odataBatchHandler);
}

As above, we only need to create a new batch handler and pass it when mapping routing for OData service. Batch will be enabled.

For testing, we can POST a request with batch body to the baseurl/$batch:

    POST https://localhost:14409/odata/$batch HTTP/1.1
    User-Agent: Fiddler
    Host: localhost:14409
    Content-Length: 1244
    Content-Type: multipart/mixed;boundary=batch_d3bcb804-ee77-4921-9a45-761f98d32029
    
    --batch_d3bcb804-ee77-4921-9a45-761f98d32029
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    
    GET https://localhost:14409/odata/Products(0)  HTTP/1.1
    OData-Version: 4.0
    OData-MaxVersion: 4.0
    Accept: application/json;odata.metadata=minimal
    Accept-Charset: UTF-8
    User-Agent: Microsoft ADO.NET Data Services
    
    --batch_d3bcb804-ee77-4921-9a45-761f98d32029
    Content-Type: multipart/mixed;boundary=changeset_77162fcd-b8da-41ac-a9f8-9357efbbd
    
    --changeset_77162fcd-b8da-41ac-a9f8-9357efbbd 
    Content-Type: application/http 
    Content-Transfer-Encoding: binary 
    Content-ID: 1
    
    DELETE https://localhost:14409/odata/Products(0) HTTP/1.1
    OData-Version: 4.0
    OData-MaxVersion: 4.0
    Accept: application/json;odata.metadata=minimal
    Accept-Charset: UTF-8
    User-Agent: Microsoft ADO.NET Data Services
    
    --changeset_77162fcd-b8da-41ac-a9f8-9357efbbd--
    --batch_d3bcb804-ee77-4921-9a45-761f98d32029
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    
    GET https://localhost:14409/odata/Products  HTTP/1.1
    OData-Version: 4.0
    OData-MaxVersion: 4.0
    Accept: application/json;odata.metadata=minimal
    Accept-Charset: UTF-8
    User-Agent: Microsoft ADO.NET Data Services
    
    --batch_d3bcb804-ee77-4921-9a45-761f98d32029--

And the response should be:

    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: multipart/mixed; boundary=batchresponse_5667121d-ca2f-458d-9bae-172f04cdd411
    Expires: -1
    Server: Microsoft-IIS/8.0
    OData-Version: 4.0
    X-AspNet-Version: 4.0.30319
    X-SourceFiles: =?UTF-8?B?YzpcdXNlcnNcbGlhbndcZG9jdW1lbnRzXHZpc3VhbCBzdHVkaW8gMjAxM1xQcm9qZWN0c1xUZXN0V2ViQVBJUmVsZWFzZVxUZXN0V2ViQVBJUmVsZWFzZVxvZGF0YVwkYmF0Y2g=?=
    X-Powered-By: ASP.NET
    Date: Wed, 06 May 2015 07:34:29 GMT
    Content-Length: 1449
    
    --batchresponse_5667121d-ca2f-458d-9bae-172f04cdd411
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    
    HTTP/1.1 200 OK
    Content-Type: application/json; odata.metadata=minimal; charset=utf-8
    OData-Version: 4.0
    
    {
      "@odata.context":"https://localhost:14409/odata/$metadata#Products/$entity","ID":0,"Name":"0Name"
    }
    --batchresponse_5667121d-ca2f-458d-9bae-172f04cdd411
    Content-Type: multipart/mixed; boundary=changesetresponse_e2f20275-a425-404a-8f01-c9818aa63610
    
    --changesetresponse_e2f20275-a425-404a-8f01-c9818aa63610
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    Content-ID: 1
    
    HTTP/1.1 204 No Content
    
    
    --changesetresponse_e2f20275-a425-404a-8f01-c9818aa63610--
    --batchresponse_5667121d-ca2f-458d-9bae-172f04cdd411
    Content-Type: application/http
    Content-Transfer-Encoding: binary
    
    HTTP/1.1 200 OK
    Content-Type: application/json; odata.metadata=minimal; charset=utf-8
    OData-Version: 4.0
    
    {
      "@odata.context":"https://localhost:14409/odata/$metadata#Products","value":[
        {
          "ID":1,"Name":"1Name"
        },{
          "ID":2,"Name":"2Name"
        },{
          "ID":3,"Name":"3Name"
        },{
          "ID":4,"Name":"4Name"
        },{
          "ID":5,"Name":"5Name"
        },{
          "ID":6,"Name":"6Name"
        },{
          "ID":7,"Name":"7Name"
        },{
          "ID":8,"Name":"8Name"
        },{
          "ID":9,"Name":"9Name"
        }
      ]
    }
    --batchresponse_5667121d-ca2f-458d-9bae-172f04cdd411--

Setting Batch Quotas

DefaultODataBatchHandler contains some configuration, which can be set by customers, to customize the handler. For example, the following code will only allow a maximum of 8 requests per batch and 5 operations per ChangeSet.

var odataBatchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
odataBatchHandler.MessageQuotas.MaxPartsPerBatch = 8;
odataBatchHandler.MessageQuotas.MaxOperationsPerChangeset = 5;

Enable/Disable continue-on-error in Batch Request

We can handle the behavior upon encountering a request within the batch that returns an error by preference odata.continue-on-error, which is specified by OData V4 spec.

Enable Preference odata.continue-on-error

Preference odata.continue-on-error makes no sense by default, and service returns the error for that request and continue processing additional requests within the batch as default behavior.

To enable odata.continue-on-error, please refer to section 4.20 Prefer odata.continue-on-error for details.

Request Without Preference odata.continue-on-error

For testing, we can POST a batch request without Preference odata.continue-on-error:


	POST https://localhost:9001/DefaultBatch/$batch HTTP/1.1
	Accept: multipart/mixed
	Content-Type: multipart/mixed; boundary=batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Host: localhost:9001
	Content-Length: 633
	Expect: 100-continue
	Connection: Keep-Alive

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomer(0) HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomerfoo HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomer(1) HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0--

The response should be:

	HTTP/1.1 200 OK
	Content-Length: 820
	Content-Type: multipart/mixed; boundary=batchresponse_b49114d7-62f7-450a-8064-e27ef9562eda
	Server: Microsoft-HTTPAPI/2.0
	OData-Version: 4.0
	Date: Wed, 12 Aug 2015 02:23:10 GMT

	--batchresponse_b49114d7-62f7-450a-8064-e27ef9562eda
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	HTTP/1.1 200 OK
	Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
	OData-Version: 4.0

	{
	  "@odata.context":"https://localhost:9001/DefaultBatch/$metadata#DefaultBatchCustomer/$entity","Id":0,"Name":"Name 0"
	}
	--batchresponse_b49114d7-62f7-450a-8064-e27ef9562eda
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	HTTP/1.1 404 Not Found
	Content-Type: application/json; charset=utf-8

	{"Message":"No HTTP resource was found that matches the request URI 'https://localhost:9001/DefaultBatch/DefaultBatchCustomerfoo'.","MessageDetail":"No route data was found for this request."}
	--batchresponse_b49114d7-62f7-450a-8064-e27ef9562eda--

Service returned error and stop processing.

Request With Preference odata.continue-on-error

Now POST a batch request with Preference odata.continue-on-error:


	POST https://localhost:9001/DefaultBatch/$batch HTTP/1.1
	Accept: multipart/mixed
	prefer: odata.continue-on-error
	Content-Type: multipart/mixed; boundary=batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Host: localhost:9001
	Content-Length: 633
	Expect: 100-continue
	Connection: Keep-Alive

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomer(0) HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomerfoo HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	GET https://localhost:9001/DefaultBatch/DefaultBatchCustomer(1) HTTP/1.1

	--batch_abbe2e6f-e45b-4458-9555-5fc70e3aebe0--

Service returns the error for that request and continue processing additional requests within the batch:

	HTTP/1.1 200 OK
	Content-Length: 1190
	Content-Type: multipart/mixed; boundary=batchresponse_60fec4c2-3ce7-4900-a05a-93f180629a11
	Server: Microsoft-HTTPAPI/2.0
	OData-Version: 4.0
	Date: Wed, 12 Aug 2015 02:27:45 GMT

	--batchresponse_60fec4c2-3ce7-4900-a05a-93f180629a11
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	HTTP/1.1 200 OK
	Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
	OData-Version: 4.0

	{
	  "@odata.context":"https://localhost:9001/DefaultBatch/$metadata#DefaultBatchCustomer/$entity","Id":0,"Name":"Name 0"
	}
	--batchresponse_60fec4c2-3ce7-4900-a05a-93f180629a11
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	HTTP/1.1 404 Not Found
	Content-Type: application/json; charset=utf-8

	{"Message":"No HTTP resource was found that matches the request URI 'https://localhost:9001/DefaultBatch/DefaultBatchCustomerfoo'.","MessageDetail":"No route data was found for this request."}
	--batchresponse_60fec4c2-3ce7-4900-a05a-93f180629a11
	Content-Type: application/http
	Content-Transfer-Encoding: binary

	HTTP/1.1 200 OK
	Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
	OData-Version: 4.0

	{
	  "@odata.context":"https://localhost:9001/DefaultBatch/$metadata#DefaultBatchCustomer/$entity","Id":1,"Name":"Name 1"
	}
	--batchresponse_60fec4c2-3ce7-4900-a05a-93f180629a11--