0
votes

I have an asynchronous library that connects to a third party system API. I am trying to use this library within a Dynamics C# plugin to create a new record in the third party system. The code I have written works fine whenever the plugin only runs on one entity at a time. However, if I kick off the plugin on two different entities at the same time I receive the error:

Failed to update Star. Error - Object reference not set to an instance of an object. : System.NullReferenceException : : at COHEN.APIConnector.Connectors.StarConnector.d__2`1.MoveNext()

I'm not quite sure what is causing this error or how to resolve it. It seems to have something to do with the asynchronous nature of the library I have written to connect to the API. What would cause this error and what are some options to resolve it?

Plugin Code

APIResponse<COHEN.APIConnector.Model.Entity.ContractJob> response = new APIResponse<COHEN.APIConnector.Model.Entity.ContractJob>();

Task.WaitAll(Task.Run(async () => response = await starConnector.Create(starJob))); 

Library Code

public async Task<APIResponse<T>> Create<T>(T entity) where T : EntityBase
{
    APIResponse<T> response = new APIResponse<T>();

    try
    {
        using (HttpClient client = new HttpClient())
        {
            client.BaseAddress = new Uri(Helpers.GetSystemUrl(Application.Star));
            client.DefaultRequestHeaders.Clear();
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/xml"));

            response.RequestURL = "Calling ToJSON";

            string json = await entity.ToJSON(Application.Star);

            response.RequestURL = "JSON: " + json;

            response.RequestURL = "RunTask?taskid=" + (int)TaskID.CREATE + "&entity=" +
                await MapSingleton.Instance.GetFieldName(Application.Star, entity.Type, FieldType.EntityName) +
                "&json=" + json;

            using (HttpResponseMessage responseMessage = await client.GetAsync(
                "RunTask?taskid=" + (int)TaskID.CREATE + "&entity=" +
                await MapSingleton.Instance.GetFieldName(Application.Star, entity.Type, FieldType.EntityName) +
                "&json=" + json
            ))
            {
                // Check TaskCentre response
                if (responseMessage.StatusCode == HttpStatusCode.OK)
                {
                    XmlDocument xmlDocument = new XmlDocument();
                    xmlDocument.LoadXml(await responseMessage.Content.ReadAsStringAsync());

                    // Check API Response
                    string responseStatusCode = xmlDocument.GetElementsByTagName("StatusCode").Item(0).InnerText;
                    if (responseStatusCode != "")
                    {
                        StatusCode statusCode = (StatusCode)Convert.ToInt32(responseStatusCode);
                        string statusMessage = xmlDocument.GetElementsByTagName("StatusMessage").Item(0).InnerText;

                        if (statusCode == StatusCode.Created)
                        {
                            XmlDocument xmlData = new XmlDocument();
                            xmlData.LoadXml("<data>" + xmlDocument.InnerText.Substring(0, xmlDocument.InnerText.Length - (xmlDocument.InnerText.Length - xmlDocument.InnerText.LastIndexOf("row") - 4)) + "</data>");
                            JObject data = JObject.Parse(JsonConvert.SerializeXmlNode(xmlData));

                            await response.SetValues(Application.Star, entity.Type, data["data"]["row"], entity.ID);
                        }

                        response.StatusCode = statusCode;
                        response.StatusReason = statusMessage;
                    }
                    else
                    {
                        response.StatusCode = StatusCode.Error;
                        response.StatusReason = "No Status Code Returned - " + response.StatusReason;
                    }
                }
                else
                {
                    response.StatusCode = (StatusCode)responseMessage.StatusCode;
                    response.StatusReason = responseMessage.ReasonPhrase;
                }
            }
        }
    }
    catch (Exception e)
    {
        response.StatusCode = StatusCode.Error;
        response.StatusReason = e.Message + " : " + e.GetType().ToString() + " : " + e.InnerException + " : " + e.StackTrace;
    }

    return response;
}
1

1 Answers

0
votes

According to the Dynamics Developer Guide, you should not use global variables in plugins. I was using a global variable to access my third party library which was causing this issue. Relevant section of linked documentation below

For improved performance, Dynamics 365 for Customer Engagement caches plug-in instances. The plug-in's Execute(IServiceProvider) method should be written to be stateless because the constructor is not called for every invocation of the plug-in. Also, multiple system threads could execute the plug-in at the same time. All per invocation state information is stored in the context, so you should not use global variables or attempt to store any data in member variables for use during the next plug-in invocation unless that data was obtained from the configuration parameter provided to the constructor. Changes to a plug-ins registration will cause the plug-in to be re-initialized.