Performance in Dynamic 365 Web API using Batch Request.
This is not something which I do as regular dynamic crm coding. In one of my projects there was a need to
implement such thing to get higher performance as the application was badly hit by the performance.
I have referred to this blog
which really help me in developing my code fast and it is well explained.
However, I am writing few additional notes which will
helped me and my team to implement such method by keeping some error away while we code.
public static async Task<List<HttpResponseMessage>> SendBatchRequestAsync(List<HttpMessageContent>
httpContents, HttpClient _httpClient)
{
if (httpContents == null)
{
throw new ArgumentNullException(nameof(httpContents));
}
string batchName = $"batch_{Guid.NewGuid()}";
MultipartContent batchContent = new MultipartContent("mixed", batchName);
string changesetName = $"changeset_{Guid.NewGuid()}";
MultipartContent changesetContent = new MultipartContent("mixed", changesetName);
httpContents.ForEach((c) =>
changesetContent.Add(c));
batchContent.Add(changesetContent);
return await SendBatchRequestAsync(batchContent, _httpClient);
}
public static async Task<List<HttpResponseMessage>> SendBatchRequestAsync(MultipartContent batchContent, HttpClient _httpClient)
{
HttpRequestMessage batchRequest = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(_httpClient.BaseAddress.AbsoluteUri.Trim()
+ "$batch")
};
batchRequest.Content = batchContent;
batchRequest.Headers.Add("OData-MaxVersion", "4.0");
batchRequest.Headers.Add("OData-Version", "4.0");
batchRequest.Headers.Add("Accept", "application/json");
HttpResponseMessage response = await _httpClient.SendAsync(batchRequest);
MultipartMemoryStreamProvider body = await response.Content.ReadAsMultipartAsync();
List<HttpResponseMessage> contents =
await ReadHttpContents(body);
return contents;
}
public static async Task<List<HttpResponseMessage>> Get_BatchRequestAsync(List<HttpMessageContent> httpContents, HttpClient _httpClient)
{
// Requests in
Change Sets only support the HTTP methods 'POST', 'PUT', 'DELETE', and 'PATCH'.
// If you
provide GET while using ChangeSet : An invalid HTTP method 'GET' was detected
for a request in a change set.
if (httpContents == null)
{
throw new
ArgumentNullException(nameof(httpContents));
}
string batchName = $"batch_{Guid.NewGuid()}";
MultipartContent batchContent = new MultipartContent("mixed", batchName);
httpContents.ForEach((c) =>
batchContent.Add(c));
return await GetBatchRequestAsync(batchContent, _httpClient);
}
public static async Task<List<HttpResponseMessage>> GetBatchRequestAsync(MultipartContent batchContent, HttpClient _httpClient)
{
HttpRequestMessage batchRequest = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(_httpClient.BaseAddress.AbsoluteUri.Trim()
+ "$batch")
};
batchRequest.Content = batchContent;
batchRequest.Headers.Add("OData-MaxVersion", "4.0");
batchRequest.Headers.Add("OData-Version", "4.0");
HttpResponseMessage response = await _httpClient.SendAsync(batchRequest);
MultipartMemoryStreamProvider body = await response.Content.ReadAsMultipartAsync();
List<HttpResponseMessage> contents =
await ReadHttpContents(body);
return contents;
}
public static HttpMessageContent CreateHttpMessageContent(HttpClient
_httpClient,HttpMethod httpMethod, string
requestUri, int
contentId = 0, string content
= null)
{
string baseUrl = _httpClient.BaseAddress.ToString();
if (!requestUri.StartsWith(baseUrl))
{
requestUri = baseUrl + requestUri;
}
HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, requestUri);
HttpMessageContent messageContent = new HttpMessageContent(requestMessage);
messageContent.Headers.Remove("Content-Type");
messageContent.Headers.Add("Content-Type", "application/http");
messageContent.Headers.Add("Content-Transfer-Encoding", "binary");
// only GET
request requires Accept header
if (httpMethod == HttpMethod.Get)
{
requestMessage.Headers.Add("Accept", "application/json");
}
else
{
// request
other than GET may have content, which is normally JSON
if (!string.IsNullOrEmpty(content))
{
StringContent stringContent = new StringContent(content);
stringContent.Headers.ContentType =
MediaTypeHeaderValue.Parse("application/json;type=entry");
stringContent.Headers.Add("Prefer", "return=representation");
requestMessage.Content =
stringContent;
}
messageContent.Headers.Add("Content-ID", contentId.ToString());
}
return messageContent;
}
private static async Task<List<HttpResponseMessage>> ReadHttpContents(MultipartMemoryStreamProvider body)
{
List<HttpResponseMessage> results = new List<HttpResponseMessage>();
if (body?.Contents != null)
{
foreach (HttpContent c in body.Contents)
{
if (c.IsMimeMultipartContent())
{
results.AddRange(await ReadHttpContents((await c.ReadAsMultipartAsync())));
}
else if
(c.IsHttpResponseMessageContent())
{
HttpResponseMessage
responseMessage = await c.ReadAsHttpResponseMessageAsync();
if (responseMessage != null)
{
results.Add(responseMessage);
}
}
else
{
HttpResponseMessage
responseMessage = DeserializeToResponse(await c.ReadAsStreamAsync());
if (responseMessage != null)
{
results.Add(responseMessage);
}
}
}
}
return results;
}
private static HttpResponseMessage DeserializeToResponse(Stream stream)
{
HttpResponseMessage response = new HttpResponseMessage();
MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
response.Content = new ByteArrayContent(memoryStream.ToArray());
response.Content.Headers.Add("Content-Type", "application/http;msgtype=response");
return response.Content.ReadAsHttpResponseMessageAsync().Result;
}
Example to create Task in Bulk using Batch
Request
public async Task<string> CreateTasksInBatch()
{
List<HttpMessageContent>
httpContents = new
List();
for (int i = 1; i
<= 5; i++)
{
string taskJson = "{\"subject\":\"Task
" + i.ToString() + "
in
batch\",\"regardingobjectid_account_task@odata.bind\":\"/accounts(12900d66-30a0-ea11-a812-000d3a33f58e)\"}";
httpContents.Add(CreateHttpMessageContent(HttpMethod.Post, "tasks", i, taskJson));
}
List<HttpResponseMessage>
responses = await SendBatchRequestAsync(httpContents);
}
Example to Fetch Multiple Task and Multiple
Account as a part of single Batch Request
public async Task<string> GetTaskAndAccountsInBatch()
{
List<HttpMessageContent>
httpContents = new List<HttpMessageContent>();
httpContents.Add(CreateHttpMessageContent(HttpMethod.Get, "tasks(3c1e7865-6cca-ea11-a812-000d3a33f58e)?$select=subject"));
httpContents.Add(CreateHttpMessageContent(HttpMethod.Get, "tasks(3c1e7865-6cca-ea11-a812-000d3a33f58e)?$select=subject"));
httpContents.Add(CreateHttpMessageContent(HttpMethod.Get, "accounts(12900d66-30a0-ea11-a812-000d3a33f58e)/Account_Tasks?$select=subject"));
List<HttpResponseMessage> responses
= await SendBatchRequestAsync(httpContents);
}