Need help using 3rd party XSD

Topics: General Discussion Forum, Service Factory Modeling Edition Forum
Oct 28, 2009 at 11:25 AM

Hi I am fairly new to WSSF so please bear with me!  This is my scenario...

- I need to write a service which already has the contract defined in the form of a 3rd party XSD over which I have no control

I would expect this to be a fairly common thing to do so I am sure I am missing something obvious, but this is what's happening...

This is the bit of the XSD I have (I've cut it down and removed namespaces for simplicity):

<xs:schema>
  <xs:element name="TheRoot" type="TheRootType" />
  <xs:complexType name="TheRootType">
    <xs:sequence>
      <xs:element name="Value" type="xs:boolean"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

So I've created my service contract model with an XSD Message.  When I hit the ellipses to browse and select the Element for the XSD message, I am able to select "TheRootType".  Note that "TheRoot" is not available to select.

So I validate, all OK, and generate the code.  All appears well.

Now, when I generate my actual WCF service it all seems happy.  However, when I browse to the WCF generated WSDL I noticed that the XSD it has generated is slightly different from the source used to create the service.  In particular I have this:

<xs:schema>
  <xs:element name="TheRootType" type="TheRootType" />
  <xs:complexType name="TheRootType">
    <xs:sequence>
      <xs:element name="Value" type="xs:boolean"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Note that the name of the root element has changed (as underlined).

If my service publishes this in it's WSDL it's exposing a different contract, which is incorrect.  So rather than use the on-the-fly generated WSDL I modified my service config file to reference a static WSDL file which referenced the correct 3rd party XSD (ie. the first xml snippet above).

However, when I fire a test message at the service the property inside my Message object which comes into the service is null.  After a bit of fiddling around I realised that this is because the XML name of the property in the MessageContract is not explicitly defined in the generated code, so it takes the default which is the property name itself (in this case "TheRootType".   But this doesn't match what the client has sent...they have sent "TheRoot".

I'm able to fix the issue by modifying the generated Message Contract.  This is what gets generated by the service factory:

[WCF::MessageContract(WrapperName = "MyRequestMessage", WrapperNamespace = "http://www.me.com")]
[WCF::XmlSerializerFormat] 
public partial class MyRequestMessage
{
	private MyNamespace.MyRootType myRootType;
		
	[WCF::MessageBodyMember(Namespace="http://www.me.com", Order=0)]
	[System.Xml.Serialization.XmlElementAttribute] 
	public MyNamespace.MyRootType MyRootType
	{
		get { return myRootType; }
		set { myRootType= value; }
	}

...

}

If I modify this generated code slightly as follows:

[WCF::MessageContract(WrapperName = "MyRequestMessage", WrapperNamespace = "http://www.me.com")]
[WCF::XmlSerializerFormat] 
public partial class MyRequestMessage
{
	private MyNamespace.MyRootType myRootType;
		
	[WCF::MessageBodyMember(Namespace="http://www.me.com", Order=0)]
	[System.Xml.Serialization.XmlElementAttribute("MyRoot")] 
	public MyNamespace.MyRootType MyRootType
	{
		get { return myRootType; }
		set { myRootType= value; }
	}

...

}

The modification is underlined....I've changed the generated MessageContract class to specifically name the XmlElementAttribute as "MyRoot" rather than taking on the default name of "MyRootType" (the property name).  When I manually applied this change to the gen'd code the problem is fixed and the data is serialized into MyRequestMessage no problem.

The issue I have is having to tinker with generated code.  Is there a way around this??????

I have a lot of services to generate like this and it's a potential maintenance nightmare waiting to happen if I have to manually change the code that's generated by the service factory.

Thanks in advance!

Paul

Oct 28, 2009 at 2:35 PM

Hi Paul,

In order to get that kind of customization, you may update the text template file (.tt) that drives the code generation for the Message Contracts and set the XmlElementAttribute with the required value.

You can try this suggestion and see if it works (I did not try this but it should work).

Steps:

  1. Open the "XsdMessageContract.tt" file located in a path like this "...\Service Contract DSL\Dsl\TextTemplates\WCF\CS"
  2. Go to line 45 and update it to this: [WCF::MessageBodyMember(Namespace="<#= CurrentElement.Namespace #>", Order=0)]<#=ResolveXmlElementAttribute(CurrentElement)#>
  3. Add this code at the bottom of that file:

<#+ 
	private string ResolveXmlElementAttribute(MessageBase message)
	{
		if (HasXmlSerializer())
		{
			return "\r\n\t\t[System.Xml.Serialization.XmlElementAttribute(\"" + message.Name + "\")]";
		}
		return "";	
	}
#>
<font size="2"></font>

  1. Re open VS and try regenerate code. No need to rebuild WSSF.

Notice that we are using message.Name as the attribute name value. This means that you may get [System.Xml.Serialization.XmlElementAttribute("MyRootType")]

In order to get "MyRoot" you need to remove the "Type" postfix (the easy way) using some simple string manipulation or (hard way) adding an additional property to the WCFXsdMessageContract class inside Microsoft.Practices.ServiceFactory.Extenders.ServiceContract.Wcf project and extract that property using a couple lines of code. If your requirements fit well with the "easy way" I suggest that solution where you don;t need to rebuild because is ony updating a tt file, a good advantage over the latter solution.

Hope this help.

Regards,

Hernan 

 

Oct 28, 2009 at 2:58 PM

Hi Hernan,

Firstly thank you so much for your quick reply!

I have tried the "easy"option you suggested.   It doesn't quite work because I get the message.Name, which is "MyRequestMessage".  It's this message that contains the element named "MyRoot" of type "MyRootType".

I'm using the WSSF binaries at this stage so don't have the source;  it sounds like a big job but is the "hard" option my only way forward?

Thanks,

Paul

Oct 28, 2009 at 4:12 PM

My fault, please try this code to get the actual type name:

<#+ 
	private string ResolveXmlElementAttribute(MessageBase message)
	{
		if (HasXmlSerializer())
		{
			WCFXsdMessageContract wfcXsdMc = GetObjectExtender<WCFXsdMessageContract>(message);
			if (wfcXsdMc == null)
			{
				return "\r\n\t\t[System.Xml.Serialization.XmlElementAttribute]";
			}
			XmlSchemaElementMoniker uri = new XmlSchemaElementMoniker(((XsdMessage)message).Element);
			return "\r\n\t\t[System.Xml.Serialization.XmlElementAttribute(\"" + uri.ElementName + "\")]";
		}
		return "";	
	}
#>
Now you should get the type name with uri.ElementName like "MyRootType" and then you may manipulate the string at will. This will still be part of the "easy" solution.
Regards,
Hernan
Oct 28, 2009 at 4:24 PM

Hi Hernan,

Thanks, that works great!

(for this one sample)

However....I tried this with another of the XSDs I have to work with and they haven't been consistent with their naming.  So the Type is called "MyRootType" but the element is called "myRoot".  Or in some cases "Root" or "Foo".  Ie. the name of the element can't always be derived from the name of it's type.

So using the easy method with a simple bit of string processing won't work.

Are there any plans for the future to allow an element to be selected as the target of an XSD message rather than a complex type?  It seems this is the underlying issue I am facing.

Do you have any other suggestions?  Getting the source and modifying WSSF itself sounds like a lot of work!

Thanks,

Paul

Oct 28, 2009 at 6:51 PM

Ok so at this point we are close to the "limits" of the "easy" solution. With this I mean that we can add more work on the tt side at the expense of complexity but we still follow the tt approach avoiding source code builds and related pain.

Let's keep on "experimenting" and this time we can add some code to get access directly to the type schema and with that in hand, exploring anything under that scope.

Now let's have a look at this code:

<#+ 
	private string ResolveXmlElementAttribute(MessageBase message)
	{
		if (HasXmlSerializer())
		{
			WCFXsdMessageContract wfcXsdMc = GetObjectExtender<WCFXsdMessageContract>(message);
			if (wfcXsdMc == null)
			{
				return "\r\n\t\t[System.Xml.Serialization.XmlElementAttribute]";
			}
			XmlSchemaElementMoniker uri = new XmlSchemaElementMoniker(((XsdMessage)message).Element);
			
			string xsdMoniker = Utility.GetData<string>(wfcXsdMc.XsdMessageContractElementArtifactLink, "Element");
			string xmlSchemaSource = XmlSchemaUtility.GetXmlSchemaSource(xsdMoniker, wfcXsdMc.XsdMessageContractElementArtifactLink);
			XmlSchemaTypeGenerator generator = new XmlSchemaTypeGenerator(true);
			XmlSchemaSet schemaSet = generator.ReadSchemas(xmlSchemaSource);
			
			// You can traverse the schemaSet and look for the Element name that matchs the type name in uri.ElementName.
			string elementName = "[the actual value here]";
			
			return "\r\n\t\t[System.Xml.Serialization.XmlElementAttribute(\"" + elementName + "\")]";
		}
		return "";	
	}
#>
You also need to add an import for the XmlSchema namespace like this (at the top in the imposrt section):
<#@ Import Namespace="System.Xml.Schema" #>
I'm not sure if you need any other namespace but if that's the case you can add the appropiate import and eventually an <#@ Assembly ..> directive.
 
You can also notice that you need to add the traversing logic on the schemaset to find out the Element name for the known type name pointed by uri.ElementName.
I know that might require some effort but you can experiment with that code in an external "test like" project (console) for the shcema straversing part and add it to the tt once you got it working.
 
Best of luck,
Hernan 
Oct 29, 2009 at 10:05 AM

GREAT STUFF!

Many thanks Hernan, works perfectly.

You have saved me a great deal of effort!!!!

Paul