Skip to content

Welcome to the OpenTravel Forum! more...

.NET Nested Attributes

Posts related to schema binding.

Moderator: sangel

.NET Nested Attributes

by SchemaBasics » Fri Dec 18, 2009 5:42 pm

I am implementing an OpenTravel service with .NET and I am having problems with .NET not supporting nested attributes. Is there some way to get around this problem?
SchemaBasics
 
Posts: 33
Joined: Mon Dec 14, 2009 3:41 pm

Re: .NET Nested Attributes

by SchemaBasics » Fri Dec 18, 2009 6:18 pm

Handling the lack of support for nested attribute groups in .NET is quite easy.

There are few issues that must be resolved to deal with this. The first is getting everything collected into one schema file so it can be processed by an XSLT script. Then there is the need to be able to make the substitutions of the attributes where each attribute group is referenced from within another attribute group. And finally you have
to deal with the recursive nature of the nested attribute groups in the OTA schema files.

Collecting every thing together is trivial. Simply write up small schema file that includes all the messages you are interested in. Be sure to include the schema files that are included in the message schemas you are interested in. For example, since I do cars, I use:
Code: Select all
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.opentravel.org/OTA/2003/05"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://www.opentravel.org/OTA/2003/05"
           elementFormDefault="qualified" version="2.007"
           id="OTA2003A2008B">
    <xs:include schemaLocation="OTA_SimpleTypes.xsd"/>
    <xs:include schemaLocation="OTA_CommonTypes.xsd"/>
    <xs:include schemaLocation="OTA_CommonPrefs.xsd"/>
    <xs:include schemaLocation="OTA_VehicleCommonTypes.xsd"/>
    <xs:include schemaLocation="OTA_PingRQ.xsd"/>
    <xs:include schemaLocation="OTA_PingRS.xsd"/>
    <xs:include schemaLocation="OTA_VehAvailRateRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehAvailRateRS.xsd"/>
    <xs:include schemaLocation="OTA_VehCancelRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehCancelRS.xsd"/>
    <xs:include schemaLocation="OTA_VehCheckInRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehCheckInRS.xsd"/>
    <xs:include schemaLocation="OTA_VehCheckOutRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehCheckOutRS.xsd"/>
    <xs:include schemaLocation="OTA_VehExchangeRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehExchangeRS.xsd"/>
    <xs:include schemaLocation="OTA_VehLocDetailRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehLocDetailRS.xsd"/>
    <xs:include schemaLocation="OTA_VehLocDetailsNotifRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehLocDetailsNotifRS.xsd"/>
    <xs:include schemaLocation="OTA_VehLocSearchRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehLocSearchRS.xsd"/>
    <xs:include schemaLocation="OTA_VehModifyRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehModifyRS.xsd"/>
    <xs:include schemaLocation="OTA_VehRateNotifRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehRateNotifRS.xsd"/>
    <xs:include schemaLocation="OTA_VehRateRuleNotifRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehRateRuleNotifRS.xsd"/>
    <xs:include schemaLocation="OTA_VehRateRuleRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehRateRuleRS.xsd"/>
    <xs:include schemaLocation="OTA_VehResRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehResRS.xsd"/>
    <xs:include schemaLocation="OTA_VehRetResRQ.xsd"/>
    <xs:include schemaLocation="OTA_VehRetResRS.xsd"/>
</xs:schema>

This file in itself could be used as direct input to XSD.EXE. But before we do that we need to perform some transformations on the assembled schema.

The first step is to use this input to generate an intermediate file that contains all of the definitions referenced in the input file. This can be accomplished with this XSLT script:
Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="xs:annotation"/>
    <xsl:template match="xs:include">
        <xsl:variable name="schemaLocation" select="@schemaLocation"/>
        <xsl:apply-templates select="document($schemaLocation)/node()" mode="inner">
            <xsl:with-param name="schemaLocation">
                <xsl:value-of select="@schemaLocation"/>
            </xsl:with-param>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="@* | node()" mode="inner">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" mode="inner"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="xs:schema" mode="inner">
        <xsl:param name="schemaLocation"/>
        <xsl:comment>Begin <xsl:value-of select="($schemaLocation)"/>Schema</xsl:comment>
        <xsl:apply-templates select="node()" mode="inner"/>
        <xsl:comment>End <xsl:value-of select="($schemaLocation)"/> Schema</xsl:comment>
    </xsl:template>
    <xsl:template match="xs:annotation" mode="inner"/>
    <xsl:template match="xs:include" mode="inner"/>
</xsl:stylesheet>

This transform with read all the input files and generate one big output file with everthing in it.

Next we pass the output through another XSLT script to perform substitutions:
Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:output method="xml" indent="yes"/>
    <xsl:key name="Groups" match="xs:attributeGroup[@name]" use="@name"/>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="xs:attributeGroup/xs:attributeGroup[@ref]">
        <xsl:comment>
            Begin <xsl:value-of select="@ref"/> AttributeGroup
        </xsl:comment>
        <xsl:variable name="Temp" select="key('Groups', @ref)"/>
        <xsl:copy-of select="$Temp/xs:attribute|$Temp/xs:attributeGroup"/>
        <xsl:comment>
            End <xsl:value-of select="@ref"/> AttributeGroup
        </xsl:comment>
    </xsl:template>
</xsl:stylesheet>

The one problem with this script is that it only makes a one level deep substitution and the OTA schemas are nested several levels deep. However, simply running the script several times will eventually lead to an output file where no attribute group references another attribute group. In the car environment, I run it 4 times.

When the final result of these transforms is passed to XSD.EXE you should get a class file that doesn't suffer from limitations of just using XSD.EXE directly.

I have used this for some time now and seems to address the problem for the vehicle vertical. I have heard from others that it works for the hotel vertical also. I would be very interested to hear from people how it works for them.

Joe Schafer
SchemaBasics
 
Posts: 33
Joined: Mon Dec 14, 2009 3:41 pm

Re: .NET Nested Attributes

by ITemplate » Mon Jan 11, 2010 9:00 am

Hi Joe,

We have successfully used your scripts (including the 'choice' script) on the Hotel messages of 2006A. Worked the first time - thanks for sharing!

--
Werner
ITemplate
 
Posts: 9
Joined: Wed Dec 23, 2009 8:59 am

Re: .NET Nested Attributes

by thansen » Mon Aug 02, 2010 7:34 am

Hey Joe
I have successfully used your xslt scripts on some selected hotel messages, but I do have a problem with the XmlSerializer.

When I try to create a new object "OTA.Transactions.OTA_HotelAvailRS" for instance.

Dim xs As New XmlSerializer (getType (OTA.Transactions.OTA_HotelAvailRS))

Then I get a runtime error, why and what can I do about it ?

Error:
Unable to generate a temporary class (result = 1). error CS0030: Can convert type 'OTA.Transactions.VendorMessageType []' to 'OTA.Transactions.VendorMessageType' error CS0030: Can convert type 'OTA.Transactions.VendorMessageType []' to 'OTA.Transactions.VendorMessageType' error CS0029: Can implicitly convert types 'OTA.Transactions.VendorMessageType's two' OTA.Transactions.VendorMessageType [] 'error CS0029: Can not implicitly convert types' OTA.Transactions.VendorMessageType's two 'OTA.Transactions.VendorMessageType []'

Thomas
thansen
 
Posts: 2
Joined: Thu Jul 29, 2010 4:14 am

Re: .NET Nested Attributes

by thansen » Mon Aug 02, 2010 7:38 am

Hey Joe
I have successfully used your xslt scripts on some selected hotel messages, but I do have a problem with XmlSerializer.

When I try to create a new object "OTA.Transactions.OTA_HotelAvailRS" for instance.

Dim xs As New XmlSerializer (getType (OTA.Transactions.OTA_HotelAvailRS))

Then I get a runtime error, why and what can I do about it ?

Unable to generate a temporary class (result = 1). error CS0030: Can convert type 'OTA.Transactions.VendorMessageType []' to 'OTA.Transactions.VendorMessageType' error CS0030: Can convert type 'OTA.Transactions.VendorMessageType []' to 'OTA.Transactions.VendorMessageType' error CS0029: Can implicitly convert types 'OTA.Transactions.VendorMessageType's two' OTA.Transactions.VendorMessageType [] 'error CS0029: Can not implicitly convert types' OTA.Transactions.VendorMessageType's two 'OTA.Transactions.VendorMessageType []'

Thomas
thansen
 
Posts: 2
Joined: Thu Jul 29, 2010 4:14 am

Re: .NET Nested Attributes

by jschafer » Tue Aug 10, 2010 11:16 pm

Thomas,

The problem has to do with how MS and some other tool producers generate class code using XSD schema files. The issue has to do with how you would go about generating class code in the situation where an element is defined with a maxOccurs that is not 1 and that element only contains 1 child element (and no attributes).

For example the VendorMessages element is defined as maxOccurs="unbounded" and it only contains a single child element named VendorMessage which itself is defined as maxOccurs="99" and it also only contains a single child element.

.NET's generator produces array definitions when the maxOccurs is greater than 1. However since the VendorMessages element only contains a single child element (and no attributes), it makes little sense to generate a an array of VendorMessages classes which each contain a single property typed as an array of VendorMessage classes.

While creating the VendorMessages class might make philosophical sense, it is not very practical considering that in most cases there would be no actual benefit to having a separate VendorMessages class. It would simply be another class that would have to be allocated from the heap and would provide no actual benefit other that making the generated class structure more closely follow the XSD definition.

In the specific case at hand, the fact that there are multiple occurances of the VendorMessages element that contain multiple occurances of the VendorMessage element is the root of the problem. .NET would have to generate an array of arrays of VendorMessage classes. This creates an abiguity problem which makes it difficult for the .NET parser to handle. Hence the error message that you received.

From the 10,000 foot level, it makes little sense that OpenTravel has defined the structure so that both a single VendorMessages element could contain 100 VendorMessage elements AND also allow 100 VendorMessages elements to each contain a single VendorMessage element. Without some attribute on the VendorMessages elements to differentiate them, it's just two ambiguous ways of encoding the same information.

This all leads to the two possible ways of getting around this problem. One is to modify the VendorMessages definition to be optional and not repeat. The other is to add a dummy optional attribute to the VendorMessages definition to force it into it's own separate class.

Both work fine, but I am hoping in the OpenTravel 2.0 definition we will eliminate this kind of problem once and for all.

BTW, there is a similar problem with FeeType element.

Joe Schafer
jschafer
 
Posts: 54
Joined: Tue Dec 15, 2009 2:47 pm

Re: .NET Nested Attributes

by emil » Sun Oct 24, 2010 2:51 pm

Hello Joe,

by implementing your advise, actually at the end we have only 1 big xsd right? that means we will have 1 big serializable class by using xsd.exe? If thats true, do you know the way to generate class for each?

thanks,

Emil
emil
 
Posts: 28
Joined: Fri Feb 26, 2010 11:13 am

Re: .NET Nested Attributes

by jschafer » Mon Oct 25, 2010 2:00 pm

Emil,

You are correct. After the preprocessing there will be one big .xsd file to submit to XSD.EXE. In addition, it will produce one big .vb or .cs file that contains all of the .NET classes that represent the messages. This is the nature of XSD.EXE.

However, splitting the classes out into separate files is not all that easy. XSD.EXE is a thin command line wrapper some over standard .NET functionality. So in theory it should be possible to create your own version of XSD.EXE that splits out the files. Unfortunately, MS does a pretty poor job of documenting the necessary functionality so the task is difficult at best.

If you want to attempt this, I suggest you get a copy of .NET Reflector. It allows you to crack open a .NET dll or exe file and decompile it to vb or c# source code. It's a wonderful way to get tons of hints of how the undocumented features of .NET work.

I looked at something like this as a more direct way of handling the .NET Attribute problems before I chose the XSLT approach. But I found it to be a very frustrating trial and error process. In the long run I took the direction in this thread as a way to maintain sanity. :D

Given that the generated code is rather uninteresting, I’m not sure splitting the classes out into separate files really buys you much. With a little experience with what XSD.EXE generates, I find that the big .xsd file is a better resource for tracking down what is expected where in the OpenTravel messages.

Joe Schafer
jschafer
 
Posts: 54
Joined: Tue Dec 15, 2009 2:47 pm

Re: .NET Nested Attributes

by emil » Thu Oct 28, 2010 3:48 pm

one question; you say you ran second xslt 4 times.
Is it because of 5 nested xsds like shown below?
OTA_VehResRQ ->OTA_VehicleCommonTypes.xsd >> OTA_CommonPrefs.xsd >> OTA_CommonTypes.xsd >> OTA_SimpleTypes.xsd

On the other hand, how about this tool http://xsd2code.codeplex.com/
I havent used it yet but I will try and write my experiences here. It has a new WCF support written there, that feature makes me wonder.

best regards,

Emil
emil
 
Posts: 28
Joined: Fri Feb 26, 2010 11:13 am

Re: .NET Nested Attributes

by jschafer » Thu Oct 28, 2010 4:25 pm

Emil,

I run the xslt 4 times because the Attribute Groups are nested. By adding all the nested xsd's includes in the initial input file, the nested inclusions are handled without needing to rerun the files through the xslt transformation.

The xslt process only makes the substitution of the attribute group reference with the members of the attribute group definition one level at a time. To handle cases where attribute groups are defined as containing other attribute groups, the xslt needs to be passed over the xsd several times.

In the vehicle messages, I am only aware of 3 levels of attribute group nesting. However, since running the transform when no attribute groups are present simply copies the input to output, it doesn't hurt to run it extra times. If you notice a place where the messages have attribute groups nested more than 4 levels please let me know.

I have not looked at that tool. I would be interested to hear what you think of it. I don't know if WCF support really adds anything. The OpenTravel standards are defined as messages so things like the names of operations on a web service are not specified in the standards.

Joe Schafer
jschafer
 
Posts: 54
Joined: Tue Dec 15, 2009 2:47 pm

Next

Return to Schema Documentation, Implementation & Binding

Go to Tnooz

Get Tnooz RSS Feeds

cron