quarta-feira, 30 de agosto de 2017

Dynamics 365 Web API - Batch Operation in C#

Uma dica rápida para quem precisar usar o recurso de Web API - Batch Operation no Dynamics 365, só que usando C#.

A vantagem deste tipo de operação é que podemos, em um único POST HTTP, enviar um LOTE de registros para serem, por exemplo, criados no CRM.

Abaixo segue trecho de código que desenvolvi para inserir dois Contatos (CTT1, CTT2) no Dynamics 365. O exemplo está inserido em um projeto do tipo ASP.NET MVC - WEB API.


        [HttpGet]
        public void CreateDyn365BatchContacts()
        {
            ConnectToCRM(null);
            Task.WaitAll(Task.Run(async () => await RunBatchAsync()));
        }



        public async Task RunBatchAsync()
        {
            await getWebAPIVersion();
            webApiURI = config.ServiceUrl + "api/data/" + getVersionedWebAPIPath() + "$batch";

            var batchId = Guid.NewGuid().ToString();
            var changeSetId = "BBB456";

            var contact1 = new JObject();
            var contact2 = new JObject();
            contact1.Add("firstname", "CTT");
            contact1.Add("lastname", "1");
            contact2.Add("firstname", "CTT");
            contact2.Add("lastname", "2");

            var batchContent = new MultipartContent("mixed", "batch_" + batchId);

            // CTT1
            var changeSetContent = new MultipartContent("mixed", "changeset_" + changeSetId);
            var httpRM1 = new HttpRequestMessage(HttpMethod.Post, config.ServiceUrl + "api/data/" + getVersionedWebAPIPath() + "contacts");
            httpRM1.Content = new StringContent(JsonConvert.SerializeObject(contact1), Encoding.UTF8, "application/json");
            var httpMC1 = new HttpMessageContent(httpRM1);
            httpMC1.Headers.Remove("Content-Type");
            httpMC1.Headers.Add("Content-Type", "application/http");
            httpMC1.Headers.Add("Content-Transfer-Encoding", "binary");
            httpMC1.Headers.Add("Content-ID", "1");
            httpMC1.Headers.Add("OData-MaxVersion", "4.0");
            httpMC1.Headers.Add("OData-Version", "4.0");
            changeSetContent.Add(httpMC1);

            // CTT2
            var httpRM2 = new HttpRequestMessage(HttpMethod.Post, config.ServiceUrl + "api/data/" + getVersionedWebAPIPath() + "contacts");
            httpRM2.Content = new StringContent(JsonConvert.SerializeObject(contact2), Encoding.UTF8, "application/json");
            var httpMC2 = new HttpMessageContent(httpRM2);
            httpMC2.Headers.Remove("Content-Type");
            httpMC2.Headers.Add("Content-Type", "application/http");
            httpMC2.Headers.Add("Content-Transfer-Encoding", "binary");
            httpMC2.Headers.Add("Content-ID", "2");
            httpMC2.Headers.Add("OData-MaxVersion", "4.0");
            httpMC2.Headers.Add("OData-Version", "4.0");
            changeSetContent.Add(httpMC2);

            batchContent.Add(changeSetContent);

            var response = SendCrmRequestAsync(HttpMethod.Post, webApiURI, batchContent).Result;

            var text = await response.Content.ReadAsStringAsync();
        }



        private async Task<HttpResponseMessage> SendCrmRequestAsync(HttpMethod method, string query, MultipartContent mpContent = null, Boolean formatted = false, int maxPageSize = 10)
        {
            var request = new HttpRequestMessage();
            request.Method = method;
            request.RequestUri = new Uri(query);

            request.Headers.Add("Prefer", "odata.maxpagesize=" + maxPageSize.ToString());
            if (formatted)
                request.Headers.Add("Prefer", "odata.include-annotations=OData.Community.Display.V1.FormattedValue");

            if (mpContent != null) {
                request.Content = mpContent;
            }

            return await httpClient.SendAsync(request);
        }


O código fonte completo está disponnível no zip CrmWebApi_JavaScript_CSharp.rar

23 comentários:

  1. I am implementing the same batch request in console app for dynamics CRM 2016, I am getting error 'This operation is not supported for a relative URI', Can you please help me to resolved the issue asap.

    ResponderExcluir
  2. Hi Jasdeep,

    Were you able to run my sample code attached?

    In general, it appears that the full URI of the Web API endpoint may be missing from your HTTPRequestMessage.
    It should be something like this:

    [Organization URI]/api/data/v8.2/contacts

    ResponderExcluir
  3. Actually I am trying to implement batch request in dynamics CRM 2016 on-premise and same request worked for dynamics 365 online when we register application in azure portal to get authenticate token but how we can implement same for on-premise, Is there any limitation or minimum version required to execute the request ?

    ResponderExcluir
    Respostas
    1. In your scenario, i think you must rely on ADFS server, in order to get the token.

      Take a look on this:

      https://stackoverflow.com/questions/45467317/authorize-webapp-to-adfs-in-order-to-access-dynamics-crm-web-api

      Excluir
  4. I keep getting this error:

    The 'Content-Type' header is missing. The 'Content-Type' header must be specified for each MIME part of a batch message.","ExceptionMessage":"The 'Content-Type' header is missing. The 'Content-Type' header must be specified for each MIME part of a batch message."

    Any help will be greatly appriciated.

    ResponderExcluir
    Respostas
    1. Hi,
      Can you tell me more about your issue.
      Are you working with Dynamics 365 ONLINE? ONPREMISSE?
      Are you sure you have the environment configured properly?

      Excluir
  5. I am getting the below errors while executing the same batch request in dynamics CRM 2016 (on-premise):
    An error occurred while sending the request.
    The underlying connection was closed:
    An unexpected error occurred on a receive.
    Unable to read data from the transport connection:
    An existing connection was forcibly closed by the remote host

    Any help on the above issue?

    ResponderExcluir
    Respostas
    1. Hi Jasdeep,
      In your case, I would try to check:
      - if the system has the latest Rollup
      - more logs, by enabling the Trace Logs
      - if can open a Call to MS

      Let me know if you could solve the issues.

      Excluir
  6. Hi,
    I am using the Dynamics CRM 2016 On-Premise (8.1.0.359) (DB 8.1.0.359) version.
    I have implemented the code in console app, so don't have more logs info.

    Thanks

    ResponderExcluir
    Respostas
    1. Hi,
      What about the 3 items I mentioned earlier, could you check?

      Excluir
  7. I am trying to do same operation but for update, I am getting error "Bad request". Can you please let me know if there is any change required for the update? I have also downloaded your code but don't know how to use it because of Azure AD, so can not verify if your code is working with my env. I am using CRM 9.0 and api version 9.1. your help is highly appreciated.

    ResponderExcluir
    Respostas
    1. I get below error
      My Request
      {Method: POST, RequestUri: 'https://**.dynamics.com/api/data/v9.1/$batch', Version: 2.0, Content: System.Net.Http.MultipartContent, Headers:
      {
      Prefer: odata.maxpagesize=10
      Content-Type: multipart/mixed; boundary="batch_40b52419-3847-4588-87d3-6498df275de2"
      Content-Length: 784
      }}

      Response
      [09/08/2019 14:27:52] System.Private.CoreLib: Exception while executing function: PersonUpdatedEventListner. MDC.Subscriber: Error In Updating CRM. Status Code : BadRequest,
      Details:
      {"Message":"The 'Content-Type' header value 'application/http; msgtype=request' is invalid. When this is the start of the change set,
      the value must be 'multipart/mixed'; otherwise it must be 'application/http'.",
      "ExceptionMessage":"The 'Content-Type' header value 'application/http; msgtype=request' is invalid. When this is the start of the change set,
      the value must be 'multipart/mixed'; otherwise it must be 'application/http'.",
      "ExceptionType":"Microsoft.OData.ODataException","StackTrace":"
      at Microsoft.OData.MultipartMixed.ODataMultipartMixedBatchReaderStream.ValidatePartHeaders(ODataBatchOperationHeaders headers, Boolean& isChangeSetPart)\r\n
      at Microsoft.OData.MultipartMixed.ODataMultipartMixedBatchReaderStream.ProcessPartHeader(String& contentId)\r\n
      at Microsoft.OData.MultipartMixed.ODataMultipartMixedBatchReader.SkipToNextPartAndReadHeaders()\r\n
      at Microsoft.OData.ODataBatchReader.ReadImplementation()\r\n
      at Microsoft.OData.ODataBatchReader.InterceptException[T](Func`1 action)\r\n
      at System.Web.OData.Batch.ODataBatchReaderExtensions.d__0.MoveNext()\r\n
      --- End of stack trace from previous location where exception was thrown ---\r\n
      at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
      at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n
      at Microsoft.Crm.Extensibility.OData.CrmODataBatchHandler.d__16.MoveNext()\r\n
      --- End of stack trace from previous location where exception was thrown ---\r\n

      Excluir
    2. Hi Hiren,
      To help you if, could you send me your update batch code?

      As additional information, take a look on this samples:

      [see the section - Bulk Update]
      https://community.dynamics.com/crm/b/scaleablesolutionsblog/posts/web-api-bulk-operations

      Excluir
    3. Also, according the error message, [msgtype=request] is invalid.

      The [Content-Type] for the PATCH must have only [application/http]

      Excluir
    4. I have sent you code, But I am not able to understand from where it takes msgtype=request

      Excluir
    5. The code I have sent you is not giving this error.
      [12/08/2019 08:26:03] Update of Person failed, please verify json object. Exception message: Error In Updating CRM. Status Code : BadRequest,
      Details:
      {"Message":"The HTTP version 'HTTP/2.0' used in a batch operation request or response is not valid. The value must be 'HTTP/1.1'.",
      "ExceptionMessage":"The HTTP version 'HTTP/2.0' used in a batch operation request or response is not valid. The value must be 'HTTP/1.1'.",
      "ExceptionType":"Microsoft.OData.ODataException",
      "StackTrace":" at Microsoft.OData.MultipartMixed.ODataMultipartMixedBatchReader.ParseRequestLine(String requestLine, String& httpMethod, Uri& requestUri)\r\n
      at Microsoft.OData.MultipartMixed.ODataMultipartMixedBatchReader.CreateOperationRequestMessageImplementation()\r\n
      at Microsoft.OData.ODataBatchReader.InterceptException[T](Func`1 action)\r\n
      at Microsoft.OData.ODataBatchReader.CreateOperationRequestMessage()\r\n
      at System.Web.OData.Batch.ODataBatchReaderExtensions.d__9.MoveNext()\r\n---
      End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n
      at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n
      at System.Web.OData.Batch.ODataBatchReaderExtensions.d__0.MoveNext()\r\n
      --- End of stack trace from previous location where exception was thrown ---\r\n

      Excluir
    6. Kindly Ignore my message, the problem is resolved now.

      Excluir
    7. Nice to hear that.
      How did you figured out?

      Excluir
    8. I had missed to add code to remove and add content type from request. And then the batch is not supported in http 2.0 so while creating request I had to change version of request to 1.1 and that worked well. its httpversion.

      Excluir
  8. Este comentário foi removido pelo autor.

    ResponderExcluir
  9. I have one more question now. I want to update address of customer without fetch because that will be part of batch request. So I want to update address when addressnumber = 1 and Contact.alternatekey = "abc"

    how to make this type of update request?

    ResponderExcluir
    Respostas
    1. Or else is there any way that, In batch request, I take output of first request and use it in second request?

      Excluir
    2. As for as I know I think this is not possible, because you cannot use the batch code as a [sql stored procedure] that has this ability.

      Excluir

<< Ao enviar um comentário, favor clicar na opção [Enviar por e-mail comentários de acompanhamento para gtezini@gmail.com] >>