BizTalk ESB 异常处理 - 消费 WCF 服务 - 第三部分





5.00/5 (1投票)
通过 BizTalk ESB Toolkit 消费 WCF 服务时进行异常管理 - 第三部分。
引言
这是我关于 ESB 工具包异常处理的 3 部分系列文章的第三部分。在这最后一篇文章中,我将解释如何在服务失败时创建自己的错误消息。也就是说,当您自己的组件之一发生错误时……您如何处理这种情况?
我不是第一个研究这个问题的人,我借鉴了 Paolo Salvatori 在 此处 博客中发布的一些想法。
问题 - 捕获异常
以下是我在发现问题时正在处理的场景。您可以看到我有一个 BizTalk 服务。我已将其公开为 WCF 服务。
在测试此服务期间,我想检查它在我的行程中发生错误时是否提供了期望的响应。我发现我收到的响应包含我不希望我的消费者接收的信息。例如,当映射失败时,我的消费者可以获得完整的失败原因。这基本上是标准的 BizTalk 管道行为。但是,我想做的是向我的消费者隐藏错误的详细信息。
行程 101
在继续之前,我想在此重申,行程在 BizTalk 管道中执行。因此,当您的行程中发生错误时,无法捕获异常,您也无法对其进行任何操作。要克服这个问题,我们需要配置一个挂钩到适配器中以获取异常。
解决方案 – WCF 错误处理程序
我采纳了 Paolo 的建议,创建了我自己的 IErrorHandler。我将任何错误转换为 SOAP 故障。我一直在寻找通用的解决方案,并认为这是最好的。然后,我扩展了 Paolo 的想法,并添加了在错误处理程序中执行映射的功能。以下是我的实现方式。
第 1 步 – 创建错误处理程序
我仍然在使用 BizTalk 2009、Visual Studio 2008 和 ESB 工具包 2.0,但我确信您可以使用 2010 和 2.1 分别实现这一点。
所以,我有一个包含以下类型的普通程序集。
整个解决方案都始于 ErrorHandlerBehaviorExtensionElement 类。这是一个我们将要在设置接收端口时引用的配置元素。我为此类添加了一个自定义属性,以便我们可以配置一个可选的映射,在异常被转换为 SOAP 故障后执行。
Extension Element 提供了一个 ErrorHandler 类的实例,该类同时实现了 IServiceBehavior 和 IErrorHandler。错误处理程序将使用 ErrorBodyWriter 将异常转换为 SOAP 故障。如果配置了映射,它还将执行映射。
提供执行映射的能力是因为错误处理程序在 WCF 适配器的底层执行。它不再位于 BizTalk 中,甚至不在管道中,因此,我们失去了通过行程或编排处理消息的任何能力。
IErrorHandler 的实现是解决方案的核心所在。这是 IErrorHandler 的接口。
对于 HandlError 方法,我仅出于调试目的跟踪错误。在 ProvideFault 方法中,我们创建自定义响应消息。以下是代码
public bool HandleError(Exception error)
{
//Tracing goes here...
//WCF must NOT abort the session
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
try
{
//Create our custom fault message
fault = CreateErrorMessage(fault, "An unexpected error occurred", error);
}
catch (Exception exception)
{
//Create our custom fault message
fault = CreateErrorMessage(fault, "An unexpected error occured", error);
}
}
我使用一个名为 CreateErrorMessage 的私有方法来为我们创建响应消息。在此方法中,我手动执行自定义 BodyWriter 来构建我的故障。然后,如果需要,我将 BodyWriter 的结果传递给映射。
private Message CreateErrorMessage(Message message, string errorMessage, Exception exception)
{
string action = null;
if (message != null &&
message.Headers != null)
action = message.Headers.Action;
string messageType = "http://schemas.xmlsoap.org/soap/envelope/#Fault";
//Prepare to use our body writer to create a message
StringBuilder stringBuilder = new StringBuilder();
//Create the SOAP Fault body
using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
{
using (XmlDictionaryWriter xmlDictionaryWriter =
XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter))
{
//Create an instance of our body writer
BodyWriter bodyWriter = new ErrorBodyWriter("soap:Receiver",
exception.Message,
action,
errorMessage,
exception);
//Execute the body writer
bodyWriter.WriteBodyContents(xmlDictionaryWriter);
//Flush the stream
xmlDictionaryWriter.Flush();
}
}
if (!string.IsNullOrEmpty(Map))
{
//A map has been configured so let's execute it.
// Note: if executing the map fails in any way, we return the
// SOAP Fault created above
using (MemoryStream streamIn =
new MemoryStream(Encoding.Unicode.GetBytes(stringBuilder.ToString())))
{
//Perform the transform
Stream streamOut = TransformStream(streamIn, Map, ref messageType);
using (StreamReader streamReader = new StreamReader(streamOut))
{
//Get the contents of the transform stream
stringBuilder = new StringBuilder(streamReader.ReadToEnd());
}
}
}
TextReader textReader = new StringReader(stringBuilder.ToString());
//Convert the raw message (stringBuilder) to a Message
Message replyMessage = Message.CreateMessage(MessageVersion.Default,
messageType,
XmlReader.Create(textReader));
//Return the message
return replyMessage;
}
如果我们有映射的详细信息,那么错误处理程序会尝试执行它。执行映射时发生的任何错误都会被忽略。我们使用 BizTalk 提供的一些运行时类型来执行映射。这是执行此操作的方法。
private Stream TransformStream(Stream streamIn, string mapName, ref string messageType)
{
try
{
//Get the data type of the map
Type type = Type.GetType(mapName);
if (type == null)
throw new Exception(string.Format("The type for {0} was not a valid map type.", mapName));
//Load the map
TransformMetaData transformMetaData = TransformMetaData.For(type);
//Get the schema information of the source message from the map
SchemaMetadata schemaMetadataSource = transformMetaData.SourceSchemas[0];
//Get the schema type of the soruce message in the map
string schemaName = schemaMetadataSource.SchemaName;
//Check that the message type of the input stream matches the message type of the
// source schema in the map
if (!string.Equals(messageType, schemaName, StringComparison.InvariantCultureIgnoreCase))
{
//The message type is not the same as the map source schema so don't
// run the map
return streamIn;
}
//Get the schema information of the target message from the map
SchemaMetadata schemaMetadataTarget = transformMetaData.TargetSchemas[0];
//Get the message type of the target of the map
messageType = schemaMetadataTarget.SchemaName;
//Prepare the input stream
XPathDocument input = new XPathDocument(streamIn);
XslTransform transform = transformMetaData.Transform;
//Create the output stream
Stream streamOut = new MemoryStream();
//Perform the transform
transform.Transform(input, transformMetaData.ArgumentList, streamOut);
//Finalise the output stream
streamOut.Flush();
streamOut.Seek(0L, SeekOrigin.Begin);
return streamOut;
}
catch (Exception exception)
{
return streamIn;
}
}
我需要指出的是,我们的 BodyWriter 不会像本系列第一篇文章中的 BodyWriter 那样生成完整的 SOAP 封装。这是因为错误处理程序只需要构建消息体,而不是像第一部分中那样需要构建整个消息封装。
这是我们即将创建的 SOAP 故障的一个示例
<Fault SOAP:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode>soap:Receiver</faultcode>
<faultstring>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml,Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher"
Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</faultstring>
<detail>
<ErrorMessage>An unexpected error occured</ErrorMessage>
<Exception>
<Message>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml, Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher" Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</Message>
<Type>Microsoft.BizTalk.Message.Interop.BTSException</Type>
</Exception>
</detail>
</Fault>
第 2 步 – 部署错误处理程序
要进行部署,只需编译程序集并将其添加到 GAC。然后通过将其添加到 machine.config 文件中,使新行为可供您的服务使用,如下所示。
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!-- other extensions here -->
<add name="ESBErrorHandler"
type="ESB.ExceptionHandling.ServiceModel.Configuration.ErrorHandlerBehaviorExtensionElement,
ESB.ExceptionHandling.ServiceModel, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=a496c9267b296339" />
<!-- other extensions here -->
</behaviorExtensions>
</extensions>
</system.serviceModel>
仅将此添加到 BizTalk 配置文件意味着 IIS 或 BizTalk 控制台将无法访问它。
如果您运行的是 64 位操作系统,请记住将其添加到 32 位和 64 位 machine.config 文件中。
另外,请记住重新启动 IIS 和所有 BizTalk 主机以加载配置文件更改。
第 3 步 – 创建您的 BizTalk 应用程序
下一步是创建一个名为 ESB.ExceptionHandling 的 BizTalk 应用程序。
我从本系列第一部分获取了架构,并创建了一个我确切知道会失败的示例映射。我还使用了在第一部分中创建的 SOAP to Response 映射。架构和映射已部署到 BizTalk,并将由我的行程执行。
我的端口配置包括一个名为 TestReceivePort 的 WCF 接收端口和一个名为 TestReceiveLocation 的接收位置。接收位置使用 ItinerarySelectReceiveXml 管道,设置如下
属性名称 |
值 |
ItineraryFactKey |
Resolver.Itinerary |
ResolverConnectionString |
ITINERARY:\\name=ESB.ExceptionHandling.Test |
为了使用自定义错误处理程序,我为接收位置类型使用了 WCF-CustomIsolated。已将其配置为使用 wsHttpBinding,并将安全设置为 None。
然后,我在行为选项卡上添加自定义行为扩展。
我通过输入映射的完整类型来配置错误处理程序以执行我在第一部分中创建的 SOAPFault_to_Response 映射。
ESB.ExceptionHandling.Maps.SOAPFault_to_Response, ESB.ExceptionHandling, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a496c9267b296339
最后,我确保错误处理包含故障详细信息。
我使用 WCF 发布向导将接收位置发布到 IIS,现在可以使用 soapUI 来消费我的服务了。
其余的基本上都是标准的 ESB 服务配置。
第 4 步 – 构建您的行程
现在我们可以构建行程了。这是我的行程的样子。它基本上是一个直通服务,在出口前有一个转换。
转换将失败,因为我们将使用我知道会失败的映射。出口永远不会被到达,所以我不会详细介绍其配置。
一旦有了行程,您就可以像往常一样部署它,然后就可以进行测试了。
第 5 步 – 测试
和以前一样,我使用 soapUI 来测试我的服务。当我调用我的服务时获得的结果符合我的响应模式,如下所示。映射生成了一个错误,该错误被转换为 SOAP 故障,然后,
使用我在第一部分中创建的映射将其映射到响应。
<ns0:Response xmlns:ns0="http://ESB.ExceptionHandling.ResponseMessage">
<ResponseElement1>An unexpected error occured</ResponseElement1>
<ResponseElement2>There was a failure executing the receive pipeline: "Microsoft.Practices.ESB.Itinerary.Pipelines.ItinerarySelectReceiveXml, Microsoft.Practices.ESB.Itinerary.Pipelines, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "ESB Dispatcher" Receive Port: "TestReceivePort" URI: "/ESB.ExceptionHandling/ExceptionHandling.svc" Reason: Function 'ScriptNS0:GetCommonValue()' has failed.</ResponseElement2>
<ResponseElement3/>
</ns0:Response></span>
最后
这 3 部分系列文章为您提供了管理和控制服务在消费和发布 WCF 服务时如何响应和创建异常所需的所有工具。
尽管我写这些文章时以 ESB 工具包为重点,但我提出的某些技术也可以用于构建非 ESB 方式的服务。
历史
版本 1.0