简体   繁体   English

在Dynamics CRM插件中:如何强制代码不等待异步响应

[英]In Dynamics CRM Plugin: How to force the code not to wait for async response

So here is my scenario. 所以这是我的情况。 I have to call an Azure Function through Dynamics CRM plugin code (C#) asynchronously, that is fine. 我必须通过Dynamics CRM插件代码(C#)异步调用Azure函数,这很好。 But I don't want the code to wait for Azure Function's response . 但是我不希望代码等待Azure Function的响应 I just want to complete the code execution and exit. 我只想完成代码执行并退出。

The Azure Function will take care of the updates back in the CRM if necessary. 如有必要,Azure功能将负责更新CRM中的更新。

The reason why I don't want to wait is there is a 2 minutes time limit for plugin execution to complete in CRM Online. 我不想等待的原因是,在CRM Online中完成插件执行有2分钟的时间限制。 However, Azure Function could take several minutes to complete the process. 但是,Azure Function可能需要几分钟才能完成该过程。

Here is my plugin class code that is making a synchronous call to Azure Function. 这是我的插件类代码,正在对Azure Function进行同步调用。 (I can convert the call to async following this document, but following that approach my code will still be waiting for the response). (我可以按照文档将调用转换为异步,但是按照这种方法,我的代码仍将等待响应)。

public class CallAzureFunc : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        // Extract the tracing service for use in debugging sandboxed plug-ins.  
        ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

        // Obtain the execution context from the service provider.  
        IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext));

        // The InputParameters collection contains all the data passed in the message request.  
        if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
        {
            // Obtain the target entity from the input parameters.  
            Entity entity = (Entity)context.InputParameters["Target"];

            // Verify that the target entity represents an entity type you are expecting.   
            if (entity.LogicalName != "account")
                return;

            // Obtain the organization service reference which you will need web service calls.  
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            try
            {
                // Plug-in business logic goes here.  

                Data data = new Data
                {
                    name = entity.Attributes["name"].ToString()
                };

                string result = CallFunction(tracer, data);
                tracer.Trace($@"result: {result}");
            }

            catch (FaultException<OrganizationServiceFault> ex)
            {
                throw new InvalidPluginExecutionException("An error occurred in MyPlug-in.", ex);
            }

            catch (Exception ex)
            {
                tracer.Trace("MyPlugin: {0}", ex.ToString());
                throw;
            }
        }
    }

    private string CallFunction(ITracingService tracer, Data data)
    {
        string json = JsonConvert.SerializeObject(data);

        string apiUrl = "https://<AzureFunctionName>.azurewebsites.net/api/";
        string token = "<token>";
        string content = null;
        string apiMethod = "CreateContactFromLead";
        string inputJson = json;
        string result = ApiHelper.ExecuteApiRequest(apiUrl, token, content, apiMethod, inputJson, tracer);
        return result;
    }
}

And here are the helper methods to make an API call. 这里是进行API调用的辅助方法。

    internal static string ExecuteApiRequest(string apiUrl, string token, string content, string apiMethod, string inputJson, ITracingService tracer)
    {
        try
        {
            var data = Encoding.ASCII.GetBytes(inputJson);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(String.Format(apiUrl + apiMethod));
            request.Method = "POST";
            request.ContentLength = inputJson.Length;
            request.ContentType = "application/json";
            request.ContentLength = data.Length;
            request.Headers.Add("x-functions-key", token);
            request.Accept = "application/json";

            // Send the data
            Stream newStream = request.GetRequestStream();
            newStream.Write(data, 0, data.Length);
            newStream.Close();

            // Get the resposne
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            if (response != null)
            {
                tracer.Trace("ApiHelper > ExecuteApiRequest > response.StatusCode: " + response.StatusCode);
                tracer.Trace("ApiHelper > ExecuteApiRequest > response.StatusDescription: " + response.StatusDescription);
            }

            if (response.StatusCode == HttpStatusCode.OK || response.StatusDescription == "OK" || response.StatusDescription == "200")
            {
                content = ReadStream(response, tracer);
            }
            else if (response.StatusCode == HttpStatusCode.NoContent || response.StatusDescription == "No Content" || response.StatusDescription == "204")
            {
                content = null;
            }
            else
            {
                if (response != null)
                {
                    throw new Exception(String.Format("Status Code: {0}, Status Description: {1}",
                        response.StatusCode,
                        response.StatusDescription));
                }
            }

            return content;
        }
        catch (Exception ex)
        {
            tracer.Trace("ApiHelper > ExecuteApiRequest > error: " + ex.Message);
            throw;
        }
    }

    private static string ReadStream(HttpWebResponse response, ITracingService tracer)
    {
        try
        {
            var responseJson = string.Empty;
            if (response != null)
            {
                Stream dataStream = response.GetResponseStream();
                if (dataStream != null)
                {
                    using (StreamReader reader = new StreamReader(dataStream))
                    {
                        while (!reader.EndOfStream)
                        {
                            responseJson = reader.ReadToEnd();
                        }
                    }
                }
            }
            return responseJson;
        }
        catch (Exception ex)
        {
            tracer.Trace("ApiHelper > ReadStream > error: " + ex.Message);
            throw ex;
        }
    }

You need two functions. 您需要两个功能。

Function #1 will be called by your plugin (essentially what you are doing now.) It will validate the inputs. 函数#1将由您的插件调用(实际上是您现在正在做的事情。)它将验证输入。 If the inputs are successful it will place a message (presumably that includes the relative data from the caller) in an Azure Service Bus Queue . 如果输入成功,它将在Azure Service Bus Queue中放置一条消息(可能包括来自调用方的相对数据)。 After placing a message on the service bus queue it will terminate and return a success message to the caller (ie, the plugin code.) 将消息放置在服务总线队列上后,它将终止并向呼叫者返回成功消息(即,插件代码)。

Function # 2 will be triggered by an Azure Service Bus Queue message. 功能#2将由Azure服务总线队列消息触发。 This function will handle the long-running code based on the message content (from Function # 1.) 此函数将根据消息内容处理长时间运行的代码(来自功能1)。

Example of Azure Service triggered Azure Function : Azure服务触发的Azure功能示例

[FunctionName("ServiceBusQueueTriggerCSharp")]                    
public static void Run(
    [ServiceBusTrigger("myqueue", AccessRights.Manage, Connection = "ServiceBusConnection")] 
    string myQueueItem,
    Int32 deliveryCount,
    DateTime enqueuedTimeUtc,
    string messageId,
    TraceWriter log)
{
    log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
    log.Info($"EnqueuedTimeUtc={enqueuedTimeUtc}");
    log.Info($"DeliveryCount={deliveryCount}");
    log.Info($"MessageId={messageId}");
}

This pattern is commonly used because it provides transaction execution safety. 通常使用此模式,因为它提供了事务执行安全性。 If you only had the one function, as described above, and the Function failed the call would be lost since there was no listener for completion. 如果您只有一个函数(如上所述),并且函数失败,则由于没有侦听器完成呼叫,因此该调用将丢失。

Using two functions we have safety. 使用两个功能,我们很安全。 If Function # 1 fails (either validation or placing a message on the queue) it will fail to the caller, and your plugin code can handle as appropriate. 如果功能1失败(验证或将消息放入队列中),它将对调用方失败,并且您的插件代码可以适当处理。 If Function # 2 fails it will fail back to the Service Bus and be queued for retry (by default it is retried up to 5 times and then written to a poison queue.) 如果功能2失败,它将失败回到服务总线,并排队等待重试(默认情况下,它最多重试5次,然后写入有毒队列)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM