Here's a solution I implemented for a similar problem: As Hugh suggests I use a helper inheriting from XmlDocument.
The Xml Template class
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Xml;
namespace Acme
{
[Serializable]
public class ResourceXmlDocument : XmlDocument
{
public ResourceXmlDocument(Type assemblyType, string resourceName, QueryValues queryValues)
{
try
{
Assembly callingAssembly = Assembly.GetAssembly(assemblyType);
if (null == callingAssembly)
{
throw new ResourceException("GetExecutingAssembly returned null");
}
Stream resourceStream = callingAssembly.GetManifestResourceStream(resourceName);
Load(resourceStream);
if (null == queryValues)
{
throw new ResourceException("queryValues not initialized");
}
if (queryValues.Keys.Count < 1)
{
throw new ResourceException("queryValues.Keys must have at least one value");
}
foreach (string querycondition in queryValues.Keys)
{
XmlNode conditionNode = this.SelectSingleNode(querycondition);
if (null == conditionNode)
{
throw new ResourceException(string.Format(CultureInfo.InvariantCulture, "Condition: '{0}' did not return a XmlNode", querycondition));
}
XmlAttribute valueAttribute = conditionNode.Attributes["value"];
if (null == valueAttribute)
{
throw new ResourceException(string.Format(CultureInfo.InvariantCulture, "Condition: '{0}' with attribute 'value' did not return an XmlAttribute ", querycondition));
}
valueAttribute.Value = queryValues[querycondition];
}
}
catch (Exception ex)
{
throw new ResourceException(ex.Message);
}
}
}
}
Of course my expample targets a fixed attribute value
to be set so you'll have to adapt this to your needs.
The QueryValues helper class
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Acme
{
[Serializable]
public class QueryValues : Dictionary<string, string>
{
public QueryValues()
{
}
protected QueryValues(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
The Xml Template
Add a Xml doc MyTemplate.xml to your project and change the compile action to Embedded Resource
so ResorceXmlDocument can load it via Reflection.
<?xml version="1.0" encoding="utf-8" ?>
<root>
<SomeOtherNode>some (fixed) value</SomeOtherNode>
<MyNodeName tablename="MyTableName" fieldname="MyFieldName" value="0" />
<YetAnotherNode>
<SubNode>Foo</SubNode>
</YetAnotherNode>
</root>
Orchestration variables and Messages
You'll need to declare
- a variable *queryValues* of type `Acme.QueryValues`
- a variable *resourceXmlDoc* of type `Acme.ResourceXmlDocument`
- a message of type `MySchemaType`
Putting it together inside a Message Assignment Shape
inside a Construct Message Shape creating a Message MyRequest of type MySchemaType
queryValues = new Acme.QueryValues();
queryValues.Add("//MyNodeName[@tablename='MyTableName' and @fieldname='MyFieldName']", "MyValueToSet");
resourceXmlDoc = new Acme.ResourceXmlDocument(typeof(Acme.MySchemaType), "MyTemplate.xml", queryValues);
MyRequest = resourceXmlDoc;
I'm keeping ResourceXmlDocument
and QueryValues
in a util lib and reference it from any BizTalk project I need. The various Xml template docs are embedded into the respective BizTalk assembly.
EDIT by OP: Actually the only way I go this to work is to also implement ISerializable
on ResourceXmlDocument
and persist message using custom serialization of OuterXml. The XmlDocument in the base is simply not serializable on its own. If there is another approach, feel free to edit this.
[Serializable]
public class ResourceXmlDocument : XmlDocument, ISerializable
{
...
protected ResourceXmlDocument(SerializationInfo info, StreamingContext context)
{
if (info == null) throw new System.ArgumentNullException("info");
Load(info.GetString("content"));
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null) throw new System.ArgumentNullException("info");
info.AddValue("content", this.OuterXml);
}