Thursday, October 29, 2009

BizTalk Orchestration Designer Crashes Visual Studio

This is just a quick post to note in passing an issue that I encountered recently. In my current project I am again working as a BizTalk architect, and we are using BizTalk Server 2006 R2, and hence the designer is hosted in Visual Studio 2005. After taking over some enormous orchestrations from another member of the team recently I started to find that Visual Studio started crashing often and unpredictably. So much so, in fact, that I had to press ctrl+S after just about every shape change, as I was losing work so often.

Now, I had worked on a project a couple of years ago where we suffered this a lot. I think that one was BizTalk Server 2006 R1. After having done some stints with BizTalk 209 / VS 2008 it came as a nasty surprise to be getting this issue again.

As you might expect, I decided to search to see who else had had the same issue. I got a couple of relevant hits. This one, http://continuouslyintegrating.blogspot.com/2008/01/orchestration-designer-crashes-visual.html, was interesting as it implied that there was something in your profile that was causing the issue. I didn't really want to have to rebuild a profile in the middle of a critical phase in the project so I kept searching.

I then came across this one, http://www.sabratech.co.uk/blogs/yossidahan/labels/visual%20studio.html, and more things to try. The thing that seemed to be intuitively right was that the size of the orchestration might be an issue. I had only started experiencing issues once I started to work on huge orchestrations. Before that there had been no problem. When working on modest orchestrations there was no problem.

I therefore took the suggestion that had the least amount of pain, to decrease the colour depth in my display settings from 32-bit down to 16-bit. I was hoping that this was going to do the trick. I then started working on the offending orchestrations and I could at least get started, but I did experience further crashes.

I then thought of a further step I might take, and it seemed to follow on from reducing the graphics load. I zoomed out. Never had another crash after that!

I think part of the problem is that the orchestration designer renders the orchestration as an image, or rather as a series of overlapping images. Then, depending on where you scroll to, a certain portion of the image is displayed. Therefore, no matter how small your viewing area in the designer the orchestration designer is still rendering a pretty big image. However, if you scroll out you reduce the overall size f the image that needs to be rendered. And, as mentioned, if you decrease the colour depth you reduce the size of it still further.

Conclusion

Huge orchestrations, from a design point of view, are bad. Huge orchestrations, from the Visual Studio Orchestration Designer point of view, are bad. If you must have them (as in my case where I was handed them and had to make them work), reducing the graphics load on your machine is a quick way to prevent Visual Studio from crashing under the load.

Tuesday, October 20, 2009

Use of interfaces within BizTalk Orchestrations and XLANG/s

Abstract

This blog post discusses the way in which interfaces are handled in BizTalk. In particular, the reason why variables defined by interfaces cannot be saved to orchestration state. This causes a compiler error [a non-serializable object type '{type name} {variable name}' can only be declared within an atomic scope or service ].

Introduction

I have recently been working back on BizTalk, and I have come across a strange issue with the way that interfaces are handled by the BizTalk compiler. As usual, this is of interest not only because there are people who may encounter this issue, but also because of the implications it has for understanding how the BizTalk orchestration engine works.

Let's consider a scenario: You want to encapsulate a common sub-process within a single orchestration. You need to invoke some business logic within this sub-process and this may change depending on which orchestration has invoked the sub-process.

In my case I decided to use a factory pattern, so I created an interface for the business logic and then the orchestration invoked the factory to receive the correct business logic instance. I have created a simplified example project to demonstrate the issue.

Example Solution

First, I created an interface that defines how the business logic is to be called:

public interface IOrchestrationHelper
{
void DoStuff();
}


I then created a base class (more on this later):

[Serializable]
public class OrchestrationHelperBase : IOrchestrationHelper
{
#region IOrchestrationHelper Members

public virtual void DoStuff()
{
throw new Exception("The method or operation is not implemented.");
}

#endregion
}

And then I created 2 implementations of the class:

public class OrchestrationHelperA : OrchestrationHelperBase
{
public override void DoStuff()
{
Debug.WriteLine("This is OrchestrationHelperA");
}
}

[Serializable]
public class OrchestrationHelperB : OrchestrationHelperBase
{
public override void DoStuff()
{
Debug.WriteLine("This is OrchestrationHelperB");
}
}

And the factory to instantiate the classes:

public static class OrchestrationHelperFactory
{
public static IOrchestrationHelper CreateHelper(string helperName)
{
switch (helperName)
{
case "A":
return new OrchestrationHelperA();
case "B":
return new OrchestrationHelperB();
default:
throw new Exception("Could not match a helper to the input specification.");
}
}
}

OK, so far so good. Simple stuff, we do this sort of thing every day don't we? This needed to be hooked into the BizTalk processes, so I incorporated the calls to the factory and the business logic into an orchestration, as follows:


If you look at the orchestration, I have a parameter called helperSpecification of type System.String that is passed in by the caller, which defines the piece of business logic to invoke (in practice this would possibly be an enum, but this is just to demonstrate). There is also an orchestration parameter called orchestrationHelper of type IOrchestrationHelper that contains the instance of the business logic component.

In the first expression shape I create the orchestration helper:

orchestrationHelper = Andrew.InterfacesXLANGs.Components.OrchestrationHelperFactory.CreateHelper(helperSpecification);

And in the next expression shape I call the business logic:

orchestrationHelper.DoStuff();

Again, this is almost as simple an orchestration as it is possible to get. However, when I try to compile it I get the following error:

Error 1 a non-serializable object type 'Andrew.InterfacesXLANGs.Components.IOrchestrationHelper orchestrationHelper' can only be declared within an atomic scope or service C:\Documents and Settings\v-anriv\My Documents\Visual Studio 2005\Projects\InterfacesXLANGs\InterfacesXLANGs\SubProcess.odx 46 66

Now, if you look into the cause of this error it is quite simple. BizTalk is a reliable messaging and orchestration server; the mechanism for achieving this reliability is that the state of messages and orchestrations is persisted to the Message Box database at runtime, either at persistence points (send ports, timeouts, exiting atomic scopes) or when the service decides to save the state to manage load. This is where the issue lies. In order to save the state of a variable it must be marked as serializable. When an orchestration hits a persistence point it serializes all of its variables and saves the data into the database. When the orchestration is "rehydrated", the state is deserialized and the processing can continue.

I mentioned scopes just above. Atomic scopes are a special case in BizTalk. These are the scopes in which an atomic (MSDTC) transaction is running. Obviously, in order to marshal the resources for such a transaction the orchestration must remain in memory during the processing of an atomic scope. This means that the scope must complete, or if it fails half way through BizTalk will assume that the work in the atomic scope has not been done, and will attempt to re-run it when the orchestration is started.

A side-effect of atomic scopes is that variables that are defined in an atomic scope will never be persisted to the database as they will always be in memory until the scope is complete. Because of this, it is possible to define a variable that is a non-serializable class.

As you can imagine, when a BizTalk host is running an orchestration t is just like any process executing code. However, when a persistence point is reached there is a double-hit on the performance as the state is serialized and is then saved into the message box database as a recoverable state point. This increased the latency of the orchestration considerably, and if throughput performance is an issue then you should minimise the number of persistence points. If you are more concerned with reliability then it's not such a bad thing.

If you look at the classes I defined they were all marked as serializable, so they could all happily exist in BizTalk orchestrations. However, because the variable was defined to use the interface, the compiler did not know if the class that would implement the interface would also be marked as serializable, therefore it generated an error.

The Solution, and some words of warning

In order to allow the BizTalk compiler to be fooled into accepting the interface, you need to declare the variable as the base class, and mark the base class as serializable. But be careful, if you do anything in your subclasses to make them non-serializable then there will be some unfortunate unintended consequences when the object is instantiated by the factory and loaded into the orchestration state.

Summary

I have used an example where an interface is used in a declaration in BizTalk to illustrate how BizTalk manages orchestration state through serialization of the orchestration variables. If you think about why and where orchestrations serialize and persist / dehydrate / rehydrate you will also start to get a grip on the factors that affect orchestration performance, and you can alter your designs to suit.


Wednesday, October 14, 2009

Just read this somewhere....

"A computer without COBOL or FORTRAN is like a chocolate cake without ketchup or mustard"

Thursday, October 08, 2009

Reminder : Fusion Log Viewer

Just a quick note - been having some errors loading assemblies lately and have called on the little-known tool in the SDK called Fusion Log Viewer. You can load it from the Visual Studio Command Prompt using the command fuslogvw or load it from here:

C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\ FUSLOGVW.exe

You'll get a view of all assembly load failures, which is really useful with systems that dynamically load stuff from the GAC at runtime such as BizTalk.



Wednesday, October 07, 2009

BizTalk Error: Failed while delivering a message to a service instance. Message details follow.

I had a strange error in BizTalk this week. I was integrating some changes by the database team on my project, and I got a strange error condition that I had not seen before. The symptoms were as follows:

  • (Normal functionality): Orchestration extracts an entity from the data access layer, and uses a Start Orchestration shape to launch another orchestration, passing the entity as a parameter.
  • (Error functionality): The second orchestration shows as started in the BizTalk Admin Console, it can be viewed in Orchestration Debugger, but it never starts processing even its first shape. (The event log blurb will be at the end of the post, because it's a bit bloaty and will detract from the commentary).
So, in my usual troubleshooting-cum-why-is-it-always-me-to-sort-this-out kind of way, I then had to start looking at why this was happening. The strange thing is that nothing much had changed on these orchestrations for about 2 months. It was a stable and well-tested piece of functionality.

What had changed, however, was the structure of the entity that I was getting back. An additional property had been added to the top-level entity that was itself a class containing 3 properties, two strings and a byte array. Nothing controversial here, I thought. I checked out this new class to see if it was serializable (a common thing for non-BizTalk devs to miss off) and it was OK (in fact if it is not serializable then you get a compile rather than a runtime error).

I then put a temporary hack into the data access code to set the new property to null, to see if the creation of this new object was causing the issue and instantly the orchestration started to work as normal. I was starting to get somewhere.

If you look at the error and examine the stack trace you see that there is an index out of bounds error on an array. Now, you may recall that the new object has a byte array, so I thought that this must be the candidate. I then looked at the values being passed through to the byte array and I found that although the byte array was being instantiated it had zero length.

In the end the error was caused by BizTalk not being able to binary serialize / deserialize the state of the object into the orchestration. This only happens if I have a zero-length array. Just to prove it I put in the following line as a temporary measure:

entity.MyProperty.MyByteArray = new byte[] { 0x00 };

This went through the message box OK. I then put in the following instead:

entity.MyProperty.MyByteArray = null;

This also went through. Therefore there is just an issue with a byte array of zero length. I eventually settled on the following:

if (entity.MyProperty.MyByteArray.Length == 0)
{
entity.MyProperty.MyByteArray = null;
}

In Summary

This error was caused by an exception very low down in the BizTalk engine, and lay in the failure to serialize or deserialize the object into the orchestration state. This was caused by the byte array being of zero length (which is legitimate from a .Net point of view), so this may be a fault oin the BizTalk engine.

As a work around, setting the byte array to null when it is empty allowed the orchestration to function.

Exception / Stack Trace

Event Type: Error
Event Source: XLANG/s
Event Category: None
Event ID: 10001
Date: 06/10/2009
Time: 10:14:17
User: N/A
Computer: <>
Description:
Failed while delivering a message to a service instance. Message details follow.
Message ID: 24abb7a4-060d-480b-abdd-c22f70118c11
Service Instance ID: c58613fa-3716-48df-9fc9-edd76cae2f13
Service Type ID: afa69d35-1f0f-7665-4264-b4b78f9abfef
Subscription ID: d7676de9-1e27-4dba-ae5d-223e77c64b50
Body part name:
Service type: <>, <>, Version=<>, Culture=neutral, PublicKeyToken=<>
Exception type: BTXMessageDeliveryException
The following is a stack trace that identifies the location where the exception occured

at Microsoft.BizTalk.XLANGs.BTXEngine.BTXSession._receiveOneMessage(Guid& instanceId, Guid& serviceId, IBTMessage currentMsg)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXSession.ReceiveMessages(IBTMessage[] messages, Int32 firstIdx, Int32 count)
at Microsoft.BizTalk.XLANGs.BTXEngine.AppDomains.AppDomainRoot.Microsoft.XLANGs.BizTalk.ProcessInterface.IAppDomainStub.ReceiveMessages(Object objMsg)
at Microsoft.XLANGs.BizTalk.CrossProcess.AppDomainStubProxy.Microsoft.XLANGs.BizTalk.ProcessInterface.IAppDomainStub.ReceiveMessages(Object msgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)
at System.Runtime.Remoting.Messaging.ServerObjectTerminatorSink.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Messaging.ServerContextTerminatorSink.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Channels.CrossContextChannel.SyncProcessMessageCallback(Object[] args)
at System.Threading.Thread.CompleteCrossContextCallback(InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Threading.Thread.InternalCrossContextCallback(Context ctx, IntPtr ctxID, Int32 appDomainID, InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Threading.Thread.InternalCrossContextCallback(Context ctx, InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Runtime.Remoting.Channels.CrossContextChannel.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Channels.ChannelServices.SyncDispatchMessage(IMessage msg)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] args)
at System.Threading.Thread.CompleteCrossContextCallback(InternalCrossContextDelegate ftnToCall, Object[] args)

Additional error information:

Failed while delivering a message to a service instance. Message details follow.
Message ID: 24abb7a4-060d-480b-abdd-c22f70118c11
Service Instance ID: c58613fa-3716-48df-9fc9-edd76cae2f13
Service Type ID: afa69d35-1f0f-7665-4264-b4b78f9abfef
Subscription ID: d7676de9-1e27-4dba-ae5d-223e77c64b50
Body part name:
Service type: <>, <>, Version=<>, Culture=neutral, PublicKeyToken=<>
Exception type: BTXMessageDeliveryException
Source: Microsoft.XLANGs.BizTalk.Engine
Target Site: Void DeliverMessage(System.Guid, Microsoft.BizTalk.Agent.Interop.IBTMessage, Boolean ByRef)
The following is a stack trace that identifies the location where the exception occured

at Microsoft.BizTalk.XLANGs.BTXEngine.BTXSession._tryReceiveOneMessage(Boolean& loggedError, Guid& instanceId, IBTMessage currMsg)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXSession._receiveOneMessage(Guid& instanceId, Guid& serviceId, IBTMessage currentMsg)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXSession.ReceiveMessages(IBTMessage[] messages, Int32 firstIdx, Int32 count)
at Microsoft.BizTalk.XLANGs.BTXEngine.AppDomains.AppDomainRoot.Microsoft.XLANGs.BizTalk.ProcessInterface.IAppDomainStub.ReceiveMessages(Object objMsg)
at Microsoft.XLANGs.BizTalk.CrossProcess.AppDomainStubProxy.Microsoft.XLANGs.BizTalk.ProcessInterface.IAppDomainStub.ReceiveMessages(Object msgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.PrivateProcessMessage(RuntimeMethodHandle md, Object[] args, Object server, Int32 methodPtr, Boolean fExecuteInContext, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg, Int32 methodPtr, Boolean fExecuteInContext)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)
at System.Runtime.Remoting.Messaging.ServerObjectTerminatorSink.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Messaging.ServerContextTerminatorSink.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Channels.CrossContextChannel.SyncProcessMessageCallback(Object[] args)
at System.Threading.Thread.CompleteCrossContextCallback(InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Threading.Thread.InternalCrossContextCallback(Context ctx, IntPtr ctxID, Int32 appDomainID, InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Threading.Thread.InternalCrossContextCallback(Context ctx, InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Runtime.Remoting.Channels.CrossContextChannel.SyncProcessMessage(IMessage reqMsg)
at System.Runtime.Remoting.Channels.ChannelServices.SyncDispatchMessage(IMessage msg)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] args)
at System.Threading.Thread.CompleteCrossContextCallback(InternalCrossContextDelegate ftnToCall, Object[] args)

Additional error information:

Index was outside the bounds of the array.
Exception type: IndexOutOfRangeException
Source: Microsoft.BizTalk.Pipeline
Target Site: Int32 Read(Byte[], Int32, Int32)
The following is a stack trace that identifies the location where the exception occured

at Microsoft.BizTalk.Message.Interop.StreamViewOfIStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.BinaryReader.ReadBytes(Int32 count)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadArrayAsBytes(ParseRecord pr)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadArray(BinaryHeaderEnum binaryHeaderEnum)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at Microsoft.XLANGs.Core.CustomFormattedPart.ProtectedUnpersist(Stream stm)
at Microsoft.XLANGs.Core.CustomFormattedPart.Unpersist(UnderlyingPart ulPart)
at Microsoft.XLANGs.Core.Part._slowProtectedRegisterWithValueTable()
at Microsoft.XLANGs.Core.Part.ProtectedRegisterWithValueTable()
at Microsoft.XLANGs.Core.Part.RetrieveAs(Type t)
at Microsoft.XLANGs.Core.DotNetPart.get_Object()
at Microsoft.BizTalk.XLANGs.BTXEngine.ExecMessage.GetParam(Int32 i)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXService.ArgsFromExecEnvelope(IBTMessage msg)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXService.DeliverMessageImpl2(Guid subscriptionId, IBTMessage msg, Boolean& receiveCompleted)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXService.DeliverMessageImpl(Guid subscriptionId, IBTMessage msg, Boolean& receiveCompleted)
at Microsoft.BizTalk.XLANGs.BTXEngine.BTXService.DeliverMessage(Guid subscriptionId, IBTMessage msg, Boolean& receiveCompleted)


For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Saturday, October 03, 2009

BizTalk Multi-Part Messages and Serialization

I've been back working on BizTalk lately so the next few posts are going to be about BizTalk issues that have arisen from some things that have come up lately.

This post is going to cover a defect that I had to resolve lately, and the insight into using BizTalk that comes from the cause / solution.

I am currently working on a banking system that needs to reliably flow payment transactions to people's accounts. The functionality of the system I was working on can be described as follows, without any danger of giving away anything commercially sensitive:

  • The system batch-processes BACS payments onto a ledger. Once per day input files come from a mainframe system and the payments need to be loaded into the ledger.
  • An SSIS process decodes the files into a staging database location.
  • The payments are extracted from the database for transmission to the ledger. This is done using BizTalk.
  • The payments are split into batches and transmitted to the ledger.
  • The ledger responds asynchronously with a message that describes the success / failure of the payment transaction.
  • Payments that ultimately fail get written into a SQL table.
  • Once per day, when processing of the payments is complete, the failed payments are extracted into a BACS file using an SSIS process.
Now, given this, there was a bug raised which stated that the amount value for the payments that are in the failed payment output file were being written as zero. Here is a description of the basic fault finding process:

  • Bug was raised against the SSIS developer who wrote the output package. He unit tested the extraction process and verified that the value was being written correctly.
  • The bug was then assigned to the developer who wrote the data access layer that writes the failed payments into the output staging table. He then verified that the amount values get written correctly into the database.
  • The bug was then assigned to the BizTalk team and that meant that me, being one of the BizTalk architects with an overview of the entire process, was called in to look at the issue.
  • The first thing I did was to attach a debugger onto the BizTalk hosts, so that I could look at the actual values being passed through the system. First, I debugged the orchestration that writes out the failed payments. I verified that the amount being written by BizTalk was zero - thus confirming that there was no issue with the data access code.
  • I then debugged the orchestration that receives the failed payments and verified that the payment amount was non-zero. This meant that somewhere between receiving the payment and writing the payment out the value was being set to zero - but how?
The answer to this lay in the way that BizTalk handles messages. Using good practice, my payment information was held in a multi-part message type (see previous post). Because of the required throughput of the system and the need to access the payment object model, the payment data is held in BizTalk as a .Net object rather than an XML message. Now, this is OK - I can assign a .Net class to a message part as well as an XML schema - as long as the classes are XML serializable. This is because the multi-part message, when sent to the message box, gets XML serialized.

Now, we're getting somewhere. In the process I was looking at the payments are received (as mentioned) and then context information is promoted on the multi-message and it is written into the message box. Different orchestrations then subscribe to the multi-part message by filtering on the context and performing payment-specific business processing. Through further debugging I narrowed the fault down - before we write to the message the value is non-zero and after the message box, in the subscribing orchestration, the value is zero. Baffling.

Now, the answer to this lay in the serialization, as you could probably guess from the title of this post. The payments were originally defined using an XML schema and then the .Net classes were generated using XSD.exe.

OK, so let's strip this back to the essence of the problem. Let's say that I have a payment schema:


And I then create a serializable class for this using XSD.exe:

//------------------------------------------------------------------------------
//
// This code was generated by a tool.
// Runtime Version:2.0.50727.3074
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
//------------------------------------------------------------------------------

//
// This source code was auto-generated by xsd, Version=2.0.50727.3038.
//
namespace Andrew.Blog.MultiPart.Entities {
using System.Xml.Serialization;
///
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://Andrew.Blog.MultiPart.Schemas.Payment")]
[System.Xml.Serialization.XmlRootAttribute(Namespace="http://Andrew.Blog.MultiPart.Schemas.Payment", IsNullable=false)]
public partial class Payment : object, System.ComponentModel.INotifyPropertyChanged {
private decimal paymentValueField;
private bool paymentValueFieldSpecified;
///
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public decimal PaymentValue {
get {
return this.paymentValueField;
}
set {
this.paymentValueField = value;
this.RaisePropertyChanged("PaymentValue");
}
}
///
[System.Xml.Serialization.XmlIgnoreAttribute()]
public bool PaymentValueSpecified {
get {
return this.paymentValueFieldSpecified;
}
set {
this.paymentValueFieldSpecified = value;
this.RaisePropertyChanged("PaymentValueSpecified");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null)) {
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}

And you will see that the serialized entity not only has a field for the decimal, but it expects the "PaymentValueSpecified" property to be set to indicate that the decimal has a value. This is because the field is marked as an optional field in the XML schema, to handle the cases where the field is nullable or unassigned. Unfortunately, in the code the flag to indicate that the payment value was set had not been changed and was still false. [It would be a good idea to set this field in the property set.] Therefore, the XML serializer was still thinking that there was no value and hence the payment value element was not present in the XML when the object was serialized (as it is written to the message box). When the message is deserialized the XML element for payment value is not present and so the decimal field defaults in value to zero. Hence the bug.

In Summary

  • .Net objects can be used as part of multi-part message types; this improves the maintainability of your orchestrations and also allows context information to be written onto .Net objects.
  • .Net objects, when assigned to a message part, are XML serialized when they are written to the message box. For this purpose, they must be XML serializable. It is easiest to generate these classes from an XML schema.
  • Be careful that your .Net object serializes as you expect, because if it doesn't you can get unexpected and unexplained issues in your solution.


I would point out that I inherited the schemas and entities here but somehow I was the one who had to sort out this issue. as usual ;)