65.9K
CodeProject 正在变化。 阅读更多。
Home

在 BizTalk 中调用 Web 服务(异步)

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (4投票s)

2015 年 12 月 4 日

CPOL

8分钟阅读

viewsIcon

28270

downloadIcon

313

如何在 BizTalk 中异步调用 Web 服务。错误处理和重试,在 Web 服务不可用时发送错误邮件。当服务恢复后,恢复编排以完成。

引言

本文介绍了一种在 BizTalk 中以异步方式调用 Web 服务函数 的简单方法。这可以通过两个编排实现:父编排和子编排,并使用“启动编排”形状。 此外,还解释了在调用的 Web 服务不可用/不可访问时进行错误处理和重试的机制。建议使用计划服务进行重试机制,该服务查找已暂停的编排实例,并在 Web 服务再次启动运行时恢复这些实例。这将确保由于 Web 服务问题而失败的 Web 服务调用在 Web 服务稍后正常运行时能够成功执行。 

背景

了解 BizTalk、.NET 将会有所帮助。

开发我们的项目

我们使用编排以异步方式调用 Web 方法。 有几种方法可以在 BizTalk 中调用 Web 服务,但没有直接的方法以异步模式调用。 为了以异步方式调用 Web 服务方法,我们使用了两个编排。 BizTalk 支持 SOAP、WCF-BasicHTTP、WCF-WSHTTP、WCF-Custom 等协议来调用 Web 方法。 我们使用的是一个 asmx Web 服务,它有一个简单的方 法,该方法接受一个输入字符串,将其与“Hello World”连接,然后返回连接后的字符串作为响应。

我们选择了 WCF-Custom,因为它是在 BizTalk 中最具灵活性的协议配置,它提供了各种选项,并且可以根据未来的需求进行修改。

以下是准备我们的 BizTalk 示例的总体步骤。

1. 要使用 WCF-... 协议调用任何 Web 服务,我们需要做的第一件事是“添加生成项”自 Visual Studio。 选择“使用 WCF 服务”,然后提供我们 Web 服务的 wsdl 位置。

例如:http://<webservice url>/Helloworld.asmx?wsdl

2. 完成向导后,它将生成一个编排,其中包含必需的请求和响应类型、请求/响应的架构以及用于使用 WCF-Custom/WCF-BasicHTTP 协议创建发送端口的 XML 文件。

3. 我们将编排命名为BTWebService.odx,它将是子编排,负责调用 Web 服务并将响应返回给父编排,通过父子共享的公共端口。

4. 在 BTWebService 编排中,我们需要创建一个已配置的端口,该端口使用向导自动生成的现有端口类型。 它应该创建为:发送请求/接收响应类型的端口,以调用 Web 服务方法。

5. 创建另一个编排作为父编排(CallingOrch.odx),该编排将调用 BTWebService 编排。

6. 在父编排中创建一个单向端口类型,用于将响应从子编排发送回父编排。 必须在父编排和子编排中各创建一个端口(我们使用相同的名称 ParentChildCommon),使用此端口类型。 这些端口将是单向端口,必须在子编排中配置为“发送”方向,在父编排中配置为“接收”。

父编排中的公共端口

子编排中的公共端口

7. 在子编排(BTWebService.odx)中,在编排视图中创建 2 个编排参数。

SOAP 请求类型的消息,我们创建的单向端口类型的端口。

8. 在父编排中:创建一个接收端口,一个接收形状,用于从文件文件夹接收请求消息。 接收消息应具有多部分消息类型 HelloWorldSoapIn(由向导创建),它表示请求消息。

9. 添加一个“启动编排”形状来调用BTWebService.odx编排。 父编排将使用 2 个参数调用BTWebService.odx:请求消息和公共端口(由父子共享)。 BTWebService.odx使用我们之前创建的发送端口调用 Web 服务,获取响应,并通过公共端口将其发送回父编排。  

10. 在启动编排之后,在父编排中添加一个接收形状和一个发送形状。 父编排通过公共端口接收来自子编排的响应,然后通过另一个发送端口将响应发送到文件夹。

11. 在管理控制台中部署项目,并进行如下配置。

父编排

子编排

在 BizTalk 管理控制台中的配置

部署包含两个编排的项目后,我们需要在管理控制台中执行以下配置。

1. 为父编排创建接收端口/位置和发送端口,并将它们与逻辑端口关联。

2. 在接收位置,选择 XMLReceive 管道,并将 DocumentSpecNames 属性设置为

TestWebService.BTWebService_tempuri_org+HelloWorld,TestWebService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a530ecc5d7ba9a86

请注意,根节点必须放在架构名称之后,并以“+”号分隔。

导入 BTWebService_Custom.BindingInfo.xml 到应用程序,以便创建两个 WCF-Custom 发送端口 - 一个使用 SOAP 1.1,另一个使用 SOAP 1.2。

3. 我们将编排配置为使用 SOAP 1.2 发送端口。 在 WCF-Custom SOAP 1.2 发送端口中设置 XMLReceive 管道。 设置 DocumentSpecNames 属性为

TestWebService.BTWebService_tempuri_org+HelloWorldResponse,TestWebService,Version=1.0.0.0,Culture=neutral,PublicKeyToken=a530ecc5d7ba9a86

此属性不是由向导自动设置的,我们需要设置此属性才能读取 Web 服务的响应。

5. 启动应用程序。

测试应用程序

1. 生成架构文件 BTWebService_tempuri_org.xsd 的实例,这将生成以下 XML,它是对 Web 服务的请求:

<ns0:HelloWorld xmlns:ns0="http://tempuri.org/">
  <ns0:input>srini</ns0:input> 
  </ns0:HelloWorld>

2. 将 XML 文件放入为父编排配置的接收位置。

3. XML 请求将被传递给子编排,子编排调用 Web 服务。 响应应通过父编排中的公共端口发送回来。

4. 收到的响应将写入输出位置。

处理 Web 服务不可用的场景

我们已经看到了所有正常工作的正常情况。 我们将对BTWebService.odx编排进行一些增强,以处理 Web 服务不可用的情况。 我们将引入一个循环和变量 ErrorCount 和 Result。 为编排添加一个范围,覆盖执行 Web 服务调用和接收响应的形状。 在范围内设置 2 个异常块,按顺序捕获 SOAP 异常和常规异常。 在常规异常块中,增加 ErrorCount。 添加一个决策形状,检查 ErrorCount >=3 && Result==0,在这种情况下,它会分支到错误处理程序块。 如果遇到错误,Web 服务调用将重试 3 次,之后会发送一封错误邮件并暂停编排。

带错误处理的修改后的编排

变量:ErrorCount, Result

范围:WS Call Proc

异常块:SOAP Exception Handler

Base System Exception Handler

如果执行成功,它将使用上面看到的 ParentChildCommon 端口将结果发送回父编排,并且两个编排都将正常终止。

Web 服务可用时恢复执行

在编排被挂起的情况下,假设过一段时间 Web 服务恢复运行,并且我们希望重试调用 Web 服务并完成调用过程。 这可以通过在管理控制台中手动查找被挂起的BTWebService.odx编排实例并执行恢复来实现。 请注意,父编排CallingOrch.odx处于脱机状态,因为它正在等待子编排的响应。

在管理控制台中查看编排

当 Web 服务启动时,我们希望BTWebService.odx重试编排并从暂停的地方开始执行。 暂停形状之后的形状将开始执行。 在这种情况下,在恢复时,我们希望编排进行另外 3 次重试。 因此,在暂停之前,我们使用表达式形状将 ErrorCount 和 Result 重置为 0,这样当编排恢复时,它将从头开始执行循环。 如果我们选择在 Web 服务启动时恢复,它应该触发 Web 服务调用并成功执行,并应接收到响应。 两个编排都应终止。

暂停形状和在暂停前重置变量

使用 SMTP 端口发送错误邮件

如果错误计数超过 3 次,将在暂停编排之前发送一封邮件。

可以通过代码来恢复编排。 我们可以将此代码作为服务运行。 可以配置该服务每 2 或 5 分钟运行一次,以快速恢复错误。 该服务使用编排名称并检查是否有任何被挂起的实例。 如果存在实例,它可以立即恢复这些实例。 这种方法将避免错过任何 Web 服务调用的处理,因为当 Web 服务可用时,服务将自动执行这些调用。 这将使管理员无需手动搜索和恢复。

以下是使用编排名称并查找此特定编排的所有被挂起(可恢复)实例,然后通过代码恢复它们的代码。

        static void Main(string[] args)
        {

            ResumeSvcInstByOrchestrationName("BTWebServiceClient");
        }

        public static void ResumeSvcInstByOrchestrationName(string strOrchestrationName)
        {
            try
            {
                const uint SERVICE_CLASS_ORCHESTRATION = 1;
                const uint SERVICE_STATUS_SUSPENDED_RESUMABLE = 4;
                const uint REGULAR_RESUME_MODE = 1;

                // Query for suspended (resumable) service instances of the specified orchestration
                // Suggestion: Similar queries can be written for Suspend/Terminate operations by using different ServiceStatus value.  See MOF definition for details.
                string strWQL = string.Format(
                    "SELECT * FROM MSBTS_ServiceInstance WHERE ServiceClass = {0} AND ServiceStatus = {1} AND ServiceName = \"{2}\"",
                    SERVICE_CLASS_ORCHESTRATION.ToString(), SERVICE_STATUS_SUSPENDED_RESUMABLE.ToString(), strOrchestrationName);

                ManagementObjectSearcher searcherServiceInstance = new ManagementObjectSearcher(new ManagementScope("root\\MicrosoftBizTalkServer"), new WqlObjectQuery(strWQL), null);
                int nNumSvcInstFound = searcherServiceInstance.Get().Count;

                // If we found any
                if (nNumSvcInstFound > 0)
                {
                    // Construct ID arrays to be passed into ResumeServiceInstancesByID() method
                    string[] InstIdList = new string[nNumSvcInstFound];
                    string[] ClassIdList = new string[nNumSvcInstFound];
                    string[] TypeIdList = new string[nNumSvcInstFound];

                    string strHost = string.Empty;
                    string strReport = string.Empty;
                    int i = 0;
                    foreach (ManagementObject objServiceInstance in searcherServiceInstance.Get())
                    {
                        // It is safe to assume that all service instances belong to a single Host.
                        if (strHost == string.Empty)
                            strHost = objServiceInstance["HostName"].ToString();

                        try
                        {
                            ClassIdList[i] = objServiceInstance["ServiceClassId"].ToString();
                            string test = objServiceInstance["ServiceClass"].ToString();
                            test = objServiceInstance["ServiceName"].ToString();
                            test = objServiceInstance["ServiceStatus"].ToString();
                        }
                        catch (Exception ex)
                        {

                        }
                        TypeIdList[i] = objServiceInstance["ServiceTypeId"].ToString();
                        InstIdList[i] = objServiceInstance["InstanceID"].ToString();

                        strReport += string.Format("  {0}\n", objServiceInstance["InstanceID"].ToString());
                        i++;
                    }

                    // Load the MSBTS_HostQueue with Host name and invoke the "ResumeServiceInstancesByID" method
                    string strHostQueueFullPath = string.Format("root\\MicrosoftBizTalkServer:MSBTS_HostQueue.HostName=\"{0}\"", strHost);
                    ManagementObject objHostQueue = new ManagementObject(strHostQueueFullPath);

                    // Note: The ResumeServiceInstanceByID() method processes at most 2047 service instances with each call.
                    //   If you are dealing with larger number of service instances, this script needs to be modified to break down the
                    //   service instances into multiple batches.
                    objHostQueue.InvokeMethod("ResumeServiceInstancesByID",
                        new object[] { ClassIdList, TypeIdList, InstIdList, REGULAR_RESUME_MODE }
                    );

                    Console.WriteLine(string.Format("Service instances with the following service instance IDs have been resumed:\n{0}", strReport));
                }
                else
                {
                    System.Console.WriteLine(string.Format("There is no suspended (resumable) service instance found for orchestration '{0}'.", strOrchestrationName));
                }
            }
            catch (Exception excep)
            {
                Console.WriteLine("Error: " + excep.Message);
            }
        }
    

 

从文章顶部的链接下载源代码。 

学习要点

希望我已经充分说明了此解决方案的各个方面。 

进行此示例时的一些学习体会

  • 为了读取对应多部分架构中特定根节点的 XML 文件,在 XMLReceive 管道的 DocumentSpecNames 属性中,根节点应放在架构名称之后,并以“+”号分隔,如上所示。
  • 可以使用上述方法通过编排来处理外部服务的故障场景。 这将使我们的业务流程更加健壮,不受外部系统错误或不可用的影响。

历史

 

© . All rights reserved.