c# - 在 Dynamics CRM 插件中:如何强制代码不等待异步响应
问题描述
所以这是我的场景。我必须通过 Dynamics CRM 插件代码 (C#) 异步调用 Azure 函数,这很好。但我不希望代码等待 Azure Function 的响应。我只想完成代码执行并退出。
如有必要,Azure 函数将处理 CRM 中的更新。
我不想等待的原因是在 CRM Online 中完成插件执行有 2 分钟的时间限制。但是,Azure Function 可能需要几分钟才能完成该过程。
这是我对 Azure Function 进行同步调用的插件类代码。(我可以在本文档之后将调用转换为异步,但按照这种方法,我的代码仍将等待响应)。
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 将由您的插件调用(本质上是您现在正在执行的操作。)它将验证输入。如果输入成功,它将在Azure Service Bus Queue中放置一条消息(可能包括来自调用者的相关数据) 。将消息放入服务总线队列后,它将终止并向调用者返回成功消息(即插件代码。)
函数 #2 将由 Azure 服务总线队列消息触发。此函数将根据消息内容处理长时间运行的代码(来自函数 #1。)
[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}");
}
这种模式很常用,因为它提供了事务执行的安全性。如果您只有一个函数,如上所述,并且函数失败,则调用将丢失,因为没有用于完成的侦听器。
使用两个函数我们有安全性。如果 Function #1 失败(验证或将消息放入队列)它将失败给调用者,并且您的插件代码可以适当地处理。如果功能 #2 失败,它将故障返回到服务总线并排队等待重试(默认情况下,它最多重试 5 次,然后写入毒队列。)
推荐阅读
- list - 如何在 Haskell 中对列表进行回文化?
- python - 直接从列表推导中获取列表(没有嵌套列表)
- c# - Unity 中的闪光效果
- shopify - Shopify 多种产品选项
- c++ - 类构造函数问题中的简单可变参数模板
- python - Celery .delay() 同步工作,不延迟
- android - 从 ListView 中删除项目而不更改相关数据库
- javascript - 如何遍历这个对象并以逗号分隔的方式显示它们?
- javascript - 如何使用 Web Crypto API 的 SubtleCrypto 验证签名的 JWT?
- html - 如何使用 CSS 操作 HTML 中表格标签的边框?