В Dynamics CRM Plugin: Как заставить код не ждать асинхронного ответа - PullRequest
0 голосов
/ 06 сентября 2018

Так вот мой сценарий. Я должен вызывать функцию Azure через код плагина Dynamics CRM (C #) асинхронно, это нормально. Но Я не хочу, чтобы код ожидал ответа функции Azure . Я просто хочу завершить выполнение кода и выйти.

Функция Azure позаботится об обновлениях обратно в CRM, если это необходимо.

Причина, по которой я не хочу ждать, состоит в том, что для запуска плагина в CRM Online существует ограничение в 2 минуты. Однако функция Azure может занять несколько минут для завершения процесса.

Вот мой код класса плагина, который выполняет синхронный вызов функции Azure. (я могу преобразовать вызов в асинхронный после этого документа, но после этого подхода мой код все еще будет ждать ответа).

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;
    }
}

А вот вспомогательные методы для вызова 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;
        }
    }

1 Ответ

0 голосов
/ 07 сентября 2018

Вам нужно две функции.

Функция # 1 будет вызываться вашим плагином (в основном то, что вы делаете сейчас). Он проверит входные данные. Если входные данные будут успешными, он поместит сообщение (предположительно, которое включает относительные данные от вызывающего абонента) в очередь служебной шины Azure . После помещения сообщения в очередь служебной шины оно завершается и возвращает вызывающему абоненту сообщение об успешном завершении (т. Е. Код плагина).

Функция # 2 будет вызвана сообщением очереди служебной шины Azure. Эта функция будет обрабатывать долгосрочный код на основе содержимого сообщения (из функции # 1.)

Пример запускаемой службы 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}");
}

Этот шаблон обычно используется, поскольку он обеспечивает безопасность выполнения транзакций. Если бы у вас была только одна функция, как описано выше, и функция не удалась, вызов был бы потерян, так как не было прослушивателя для завершения.

Используя две функции, мы обеспечиваем безопасность. Если функция # 1 завершается с ошибкой (либо проверка, либо размещение сообщения в очереди), она не будет выполнена вызывающей стороне, и ваш код плагина может обработать ее соответствующим образом. В случае сбоя функции № 2 она вернется к служебной шине и будет поставлена ​​в очередь для повторной попытки (по умолчанию она повторяется до 5 раз, а затем записывается в очередь отравления.)

...