Writing Schemas Implicitly


You now know that the System.Xml serializer you use to serialize and deserialize messages can only do so much—it can translate (sometimes erroneously) the intention of your schema into an equivalent it can handle.

Back in Chapter 2, and again in this chapter, you’ve seen that we can tag members of our Web service class to influence how the class is serialized and the shape of the schema generated for the service. In this section, we’ll look at exactly what these tags do. They work in the same way regardless of whether you are working with an existing class and writing a schema implicitly through tagging or have started with a schema and are trying to get the .NET serializer to be a little truer to your vision.

To demonstrate what each tag does, we’ll start by building a Web service with a deliberately obfuscated set of classes and methods that mirror the AlbumSubmission service you’ve already seen, and we’ll use our tags to give them meaning. In the sample code, you’ll find the classes we’ll use in the file BareServiceClass.cs. For example, the albumSubmission class now looks like this:

public class requestClass  {     public string[] ReqElement1;     public subclass[] ReqElement2;     public string ReqAttribute1; }

If you copy the classes and method into a new Web service project in Visual Studio .NET (you’ll find such a project called TagService in the sample code) and open the WSDL file in your browser, you’ll see that the service works fine, albeit unintelligibly. Actually, at this stage the serializer is more clever than you might imagine. Earlier you saw how xsd.exe turned our AlbumID GUID element into a plain string. By writing the class first, we can state that we want to use a System.Guid in our response class. The serializer notes this in the service’s schema not as a plain string but as a string with a pattern matching a GUID!

<s:simpleType name="guid">   <s:restriction base="s:string">     <s:pattern value="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-Π      [0-9a-fA-F]{4}-[0-9a-fA-F]{12}" />    </s:restriction> </s:simpleType>

If a class involved in your Web service contains something that the serializer can’t handle, this will be obvious the first time you try to browse to it. An ASP.NET error page will appear, telling you what class caused the problem. In general, numerical and string- based classes are fine, as are those that inherit from ICollection and IEnumerable. For those that don’t, you can experiment with the IXmlSerializable interface, which we’ll cover later.

Working with Document/Literal Services

Unless told otherwise, .NET assumes that every Web service and method is of the document/literal variety. However, if you want to make this explicit, you have two options:

  • Add a [SoapDocumentService] attribute to your Web service class. Any Web method in the class will be serialized as document/literal by default.

  • Add a [SoapDocumentMethod] attribute to your class method in addition to the [WebMethod] attribute.

Both attributes can be found in the System.Web.Services.Protocols namespace, so don’t forget to reference it if you use them. We’ll look at [SoapDocumentMethod] and [WebMethod] in more detail when we cover tagging methods, but first let’s look at the other defaults that [SoapDocumentService] affords us.

Setting Service Defaults with [SoapDocumentService]

The [SoapDocumentService] attribute has three parameters that set defaults across all the Web methods defined in the class it has tagged. These are listed in Table 6-5.

Table 6-5: Properties of the [SoapDocumentService] Attribute

Property

Description

ParameterStyle

Sets whether the parameters and return values for the methods in the service will be contained within a single element (which is itself a child of the SOAP <body> element, the default) when serialized or whether they will each be written separately as children of <body>.

RoutingStyle

Sets whether the method called should be specified in the SOAPAction header (the default) or by the first element in the request message.

Use

Sets whether the serializer encodes the messages using XSD types (the default) or Section 5 types.

RoutingStyle and Use are self-explanatory, but ParameterStyle could do with a bit more explanation. We’ll take our SubmitAlbum method as an example. By default, the System.Xml serializer creates a schema that specifies a request message with this structure:

<soap:body>   <SubmitAlbum>      <albumSubmission>       <artist>Tool</artist>       <album>Lateralus</album>     </albumSubmission>   </SubmitAlbum> </soap:body>

As you can see, the serializer creates a wrapper element identified by the method’s name unless we tell it otherwise. This setting can be written explicitly as follows:

[SoapDocumentService(ParameterStyle=SoapParameterStyle.Wrapped)]

The alternative is to set ParameterStyle to SoapParameterStyle.Bare, which removes the <SubmitAlbum> wrapper element:

<soap:body>   <albumSubmission>     <artist>Tool</artist>     <album>Lateralus</album>   </albumSubmission> </soap:body>

ParameterStyle is also a property of the [SoapDocumentMethod] attribute; it allows you to override the service default on a per-method basis.

One other default to mention here is that .NET sets the elementFormDefault attribute for all its autogenerated service schemas to qualified. You cannot change this—which is good—but you can override it per element or attribute, as you’ll soon see.

Identifying the Service with [WebService]

We used the [WebService] attribute as far back as Chapter 1, but what does it actually do behind the scenes? Recall that it has three attributes:

  • Namespace Sets the targetNamespace for our service’s schema to something other than http://tempuri.org. It is important to set this to our own namespace.

  • Name Sets the <service> name attribute in your service’s WSDL document.

  • Description Sets the <service>/<description> element in your service’s WSDL document.

Name and Description are less important to the serialization of messages than Namespace. Remember, however, that the Namespace attribute is important to the outward appearance of your service’s documentation page.

Taking what we’ve learned, let’s return to our TagService project and add the following to our service class declaration:

[WebService(Name="Album Submission Service",      Namespace="http://www.notashop.com/wscr",      Description="Submit an album to the database")] [SoapDocumentService(ParameterStyle=SoapParameterStyle.Wrapped)] public class Service1 : System.Web.Services.WebService {       }

With respect to the schema and serialization, we’ve set the namespace for the service and decided on the outer element for request and response messages.

Important

From here on, we’ll look only at attributes that work with document/literal services. If you tag a service with [SoapRpcService] or [SoapDocumentService use="encoded"] or tag a method with [SoapRpcMethod] or [SoapDocumentMethod use="encoded"], none of the subsequent [Xml…] attributes will be taken into consideration by the serializer.

Setting XML Elements with [XmlElement]

The System.Xml serializer treats all public fields and properties of classes involved in Web methods in exactly the same way: it turns them into elements in your request and response messages and gives each element the same name as the field or property. In our TagService project, the serializer takes our request class

public class requestClass  {     public string[] ReqElement1;     public subclass[] ReqElement2;     public string ReqAttribute1; }

and presents us with the following schema:

<s:complexType name="requestClass">   <s:sequence>     <s:element minOccurs="0" maxOccurs="1"                 name="ReqElement1" type="s0:ArrayOfString" />      <s:element minOccurs="0" maxOccurs="1"                 name="ReqElement2" type="s0:ArrayOfSubclass" />      <s:element minOccurs="0" maxOccurs="1"                 name="ReqAttribute1" type="s:string" />    </s:sequence> </s:complexType>

We’ll soon look at how to alter this so the serializer can recognize fields and properties as element attributes and text fields. In the meantime, we can tag those fields and properties that are meant to be elements with the [XmlElement] attribute to alter how they are represented in the service’s schema:

[XmlElement(ElementName="Artist")] public string[] ReqElement1;      [XmlElement(ElementName="Album")] public subclass[] ReqElement2;

We can also use [XmlElement] to set how the parameters of a Web method are represented in the schema:

[ return: XmlElement("SubmissionReceipt")] public responseClass RunThisMethod(     [XmlElement("AlbumSubmission")] requestClass rC)

This approach is especially useful if the method takes parameters of a primitive type because it gives us a way to control their representation. Note, however, that [XmlRoot] and [SoapDocumentMethod] also give you the same control here, as you’ll see later. If for no other reason than clarity, it’s easier to use these other attributes than a gaggle of [XmlElement] tags around the method signature.

The XmlElement has six properties, as shown in Table 6-6.

Table 6-6: Properties of the [XmlElement] Attribute

Property

Description

DataType

Sets the XSD type for the element

ElementName

Sets the element’s name

Form

Sets the element’s form attribute in the schema to qualified (the default) or unqualified

IsNullable

Sets the element’s nillable attribute in the schema to true or false (the default) and thus sets whether the tagged item can be set to null

Namespace

Sets the namespace for the element

Type

Identifies the .NET type for a specific element

The first five of these attributes are easy to understand. If we tag one of the requestClass fields as shown here

[XmlElement(ElementName="Artist", DataType="normalizedString",      Form=XmlSchemaForm.Unqualified, IsNullable=true,      Namespace="http://www.notashop.com/wscr")] public string[] ReqElement1;

the serializer produces the following entry for it in the service’s schema, pretty much as we’d expect:

<s:schema elementFormDefault="qualified"            targetNamespace="http://www.notashop.com/wscr">      <s:element minOccurs="0" maxOccurs="unbounded" form="unqualified"               name="Artist" nillable="true" type="s:normalizedString" />    </s:schema>

If the namespace given in the namespace attribute differs from the targetNamespace of the schema, the serializer will create a second schema in the service’s WSDL file for that namespace. This second schema will contain the definition for the element and will be imported into the original schema for the service.

The Type attribute needs explanation but is easier to demonstrate. Suppose you have a field of type System.Object, which you expect will store a value of some derived type, let’s say a string. You can use the Type attribute to indicate the type that the service will expect or deliver in this field. So the following

[XmlElement(Type=typeof(string))] public System.Object obj;

will appear in the corresponding schema as you see here:

<s:element minOccurs="0" maxOccurs="1" name="obj" type="s:string" />

Note that you can’t use Type against a field or property that is itself a primitive type. Also, Type must specify a derivation of the class member’s original type.

Setting XML Attributes with [XmlAttribute]

If you’d prefer that a class member (a field or a property) be represented as an attribute of the element representing the class, you can inform the serializer by tagging the member with an [XmlAttribute] attribute. For example, in our TagService example the requestClass class has a field named reqAttribute1 that is being turned into an element by the serializer and that we’d prefer to be an attribute. If we tag the class like so

public class requestClass  {     [XmlElement(ElementName="Artist")]     public string[] ReqElement1;     [XmlElement(ElementName="Album")]     public subclass[] ReqElement2;          [XmlAttribute(AttributeName="Sender")]     public string ReqAttribute1; }

the serializer reshapes the service’s schema to accommodate our wishes:

<s:complexType name="requestClass">   <s:sequence>     <s:element minOccurs="0" maxOccurs="unbounded"                 name="Artist" type="s:string" />      <s:element minOccurs="0" maxOccurs="unbounded"                 name="Album" type="s0:subclass" />    </s:sequence>   <s:attribute name="Sender" type="s:string" />  </s:complexType>

The [XmlAttribute] attribute has four properties, as shown in Table 6-7.

Table 6-7: Properties of the [XmlAttribute] Attribute

Property

Description

AttributeName

Sets the attribute’s name

DataType

Sets the XSD type of the attribute’s value

Form

Sets the element’s form attribute in the schema to qualified (the default) or unqualified

Namespace

Sets the attribute’s namespace

Each property works the same as the corresponding properties for the [XmlElement] attribute. Tagging reqAttribute1 like so

[XmlAttribute(AttributeName="Sender", DataType="token",      Form=XmlSchemaForm.Unqualified,      Namespace="http://www.notashop.com/wscr")] public string ReqAttribute1;

produces the following entry in the service schema:

<s:attribute form="unqualified" name="Sender" type="s:token" />

Note that an error will occur if you use [XmlAttribute] to set an attribute’s form to unqualified and its namespace to a URI other than the value of targetNamespace.

Caution

Because of a bug in the documentation page, setting an attribute’s form to qualified and its namespace to a URI that isn’t the targetNamespace causes the attribute to disappear from the sample request or response message that it appears in.

Last but not least, there’s no reliable way to use these serialization attributes to indicate that an attribute is required in a message (that is, to set an <attribute> element’s use attribute to required in the service schema). It appears, however, that the serializer adds use=required to attribute declarations at random, so be careful.

Setting Text Nodes with [XmlText]

You saw earlier that schemas can define complex types containing elements, attributes, and text values. You know how to identify which class members should be serialized as elements and which should be serialized as attributes, so we’re left with how to identify which class member represents the type’s text node. We do this by tagging it with the [XmlText] attribute.

In our TagService example, the subclass class has a field member named subTextValue1 that we’d like to associate with the text value of the type that will represent it. If we tag the class like so

public class subclass  {     [XmlAttribute(AttributeName="noOfTracks", DataType="unsignedByte")]     public System.Byte subAttribute1;     [XmlAttribute(AttributeName="RunningTime")]     public string subAttribute2;     [XmlText(DataType="normalizedString")]     public string subTextValue1; }

the definition of the subclass complex type will change from a sequence of three elements to the following:

<s:complexType name="subclass">   <s:simpleContent>     <s:extension base="s:normalizedString">       <s:attribute name="noOfTracks"                     type="s:unsignedByte" use="required" />        <s:attribute name="RunningTime" type="s:string" />      </s:extension>   </s:simpleContent> </s:complexType>

Curiously, the documentation page’s sample request message does not show that elements of this type can contain text, but the schema does, and that’s what matters. Table 6-8 shows the properties you can set in the [XmlText] attribute. You’ve seen how they both work before.

Table 6-8: Properties of the [XmlText] Attribute

Property

Description

DataType

Sets the XSD type of the text or the base type being extended by the new <complexType> definition

Type

Identifies the .NET type for a specific element

Don’t forget that a type can have only one text node, so you can tag only one class member with [XmlText]; otherwise, you get an error.

Setting WildCards with [XmlAnyElement] and [XmlAnyAttribute]

The <any> and <anyAttribute> wildcards in XML Schema offer a fair amount of control over what XML can replace them inside a complex type, but their equivalent .NET attributes, [XmlAnyElement] and [XmlAnyAttribute], are somewhat less flexible. Adding the following to our service classes

[XmlAnyAttribute] public XmlAttribute[] WildcardAttributes; [XmlAnyElement] public XmlElement[] WildcardElements;

produces the corresponding wildcards in the service schema:

<s:any minOccurs="0" maxOccurs="unbounded" /> <s:anyAttribute />

There is no way to influence the namespace or process-Contents attributes of either element. Each of the elements and attributes sent in messages in place of the wildcards is stored in the arrays of XmlAttribute and XmlElement objects declared in the class. This is the only way to set up a wildcard. How you process the contents of those arrays is up to you. [XmlAnyAttribute] has no additional properties, but [XmlAnyElement] has two, as detailed in Table 6-9.

Table 6-9: Properties of the [XmlAnyElement] Attribute

Property

Description

Name

Sets the name of a wrapper element for the elements templated by the wildcard. If the property is not specified, the array captures any elements not covered by the schema.

Namespace

Sets the namespace for the wildcard wrapper element. The Name property for [XmlAnyElement] must be also be set if this property is used; otherwise, an error will occur.

Applying a name to a wildcard element has a notable effect on the service schema. Let’s say we add (Name="IceCream") to the [XmlAnyElement] attribute. The wildcard entry changes from <s:any minOccurs="0" maxOccurs="unbounded" /> to this:

<s:element minOccurs="0" maxOccurs="unbounded" name="IceCream">   <s:complexType mixed="true">     <s:sequence>       <s:any maxOccurs="unbounded" />      </s:sequence>     <s:anyAttribute />    </s:complexType> </s:element>

Because of a bug in .NET, [XmlAnyAttribute] clashes with the [XmlText] attribute. If a class contains two members, each tagged with one of these attributes, the resulting mixed-content complex type definition in the service schema will not contain an <anyAttribute /> wildcard element.

Bypassing Class Members with [XmlIgnore]

We’ve looked at tagging class members that will be elements, attributes, and text, but what if we don’t want a class member to be part of the message at all? We use the [XmlIgnore] attribute. Tagging a class member with [XmlIgnore] tells the serializer not to include it in the service schema, as in this example:

[XmlIgnore] public string thisWontBeSerialized;

[XmlIgnore] has no properties.

Finalizing Message Class Definitions with [XmlRoot] and [XmlType]

When the serializer creates the schema for a method’s request and response messages, the root element for each is defined by the method declaration in the service class. For example, in our TagService project, we have one method, which is declared as follows:

[WebMethod] public responseClass RunThisMethod(requestClass rC) {      }

In the service schema, the request class parameter becomes the root of the request message and the response class becomes the root of the response:

<s:element name="rC" type="s0:requestClass" /> <s:element name="RunThisMethodResult" nillable="true"    type="s0:responseClass" />

Note that if the method were to have more than one parameter, each parameter would become a “root” element and you might want to use the wrapper element you can elicit from the [SoapDocumentMethod] or [SoapDocumentService] attributes as the root of your message. To do so, you set the ParameterStyle attribute of either attribute to SoapParameterStyle.Wrapped. This also applies to the situation in which the method has a return class and out parameters.

You can modify the schema definition for each root type by attaching the [XmlRoot] attribute to its class definition. For example:

[XmlRoot(ElementName="AlbumSubmission")] public class requestClass  {      } [XmlRoot(ElementName="SubmissionReceipt")] public class responseClass  {      }

Adding these tags changes the schema definitions for the message roots to the following:

<s:element name="AlbumSubmission"             nillable="true" type="s0:requestClass" />  <s:element name="SubmissionReceipt"             nillable="true" type="s0:responseClass" />

Table 6-10 lists the properties you can set with the [XmlRoot] attribute.

Table 6-10: Properties of the [XmlRoot] Attribute

Property

Description

DataType

Sets the XSD type of the root element

ElementName

Sets the name of the root element

IsNullable

Set’s the root element’s nillable attribute in the schema to true (the default) or false and thus whether the tagged item can be set to null.

Namespace

Sets the namespace of the root element.

Tagging a subclass with the [XmlRoot] attribute has no effect on the service schema. Instead of [XmlRoot], you must use [XmlType] attribute. It has three properties, which are detailed in Table 6-11.

Table 6-11: Properties of the [XmlType] Attribute

Property

Description

IncludeInSchema

Indicates whether the type definition should be included in the schema for the service

Namespace

Sets the namespace for the type definition

TypeName

Sets the name for the type definition

Customizing Enumerations with [XmlEnum]

We noted earlier that the serializer can generate schema types for numerical .NET classes, string-based classes, and classes that inherit from ICollection and IEnumerable. Let’s consider basic enumerations first. In our TagService example, we declare a field in the subclass class to take values from an enumeration we’ve defined called enumTypes:

[XmlElement(ElementName="AlbumFormat")] public enumTypes subEnum1;  public enum enumTypes {     CD, Vinyl, MD }

By default, the enumeration and the field are translated into the following schema definitions:

<s:element minOccurs="1" maxOccurs="1"             name="AlbumFormat" type="s0:enumTypes" />   <s:simpleType name="enumTypes"> <s:restriction base="s:string">   <s:enumeration value="CD" />    <s:enumeration value="Vinyl" />    <s:enumeration value="MD" />  </s:restriction> </s:simpleType>

As noted earlier, the schema <enumeration> element does indeed translate well to enumerations in .NET. The [XmlElement] works as usual except that you can’t specify a DataType for it because the class member you’re tagging isn’t of a primitive type.

The [XmlEnum] attribute tags a value of the enumeration type. It has one property, Name, which you use to give the enumeration value a different name in the schema. For example

public enum enumTypes {     [XmlEnum(Name="Compact Disc")] CD,      [XmlEnum(Name="Vinyl LP Record")] Vinyl,      [XmlEnum(Name="Minidisc")] MD }

With this attribute included, the enumeration’s simple type in the schema changes to the following:

<s:simpleType name="enumTypes">   <s:restriction base="s:string">     <s:enumeration value="Compact Disc" />      <s:enumeration value="Vinyl LP Record" />      <s:enumeration value="Minidisc" />    </s:restriction> </s:simpleType>

The serializer ensures that one value is serialized or deserialized into its corresponding value as needed.

Customizing Arrays with [XmlArray] and [XmlArrayItem]

The array is the last .NET type we’ll look at. It corresponds to a unique entry in a schema. By default, a class member that is an array is represented in a service schema by an element that can occur any number of times. In our TagService example, our responseClass class has the string array member, respElement1:

public string[] respElement1;

The corresponding schema entry looks like this:

<s:element minOccurs="0" maxOccurs="unbounded"             name="respElement1" type="s:string" />

Arrays are the only .NET constructs for which the serializer generates minOccurs and maxOccurs attributes in a schema. You cannot change them, but you can alter the structure of the schema element that corresponds to the array by using the [XmlArray] and [XmlArrayItem] attributes.

The [XmlArray] attribute creates a wrapper around the elements representing the items in the array. If we tag respElement1 with [XmlArray], its schema entry changes from the single element declaration you saw earlier to the following:

<s:element minOccurs="0" maxOccurs="1" name="respElement1"    type="s0:ArrayOfString" />   <s:complexType name="ArrayOfString">   <s:sequence>     <s:element minOccurs="0" maxOccurs="unbounded" name="string"                 nillable="true" type="s:string" />    </s:sequence> </s:complexType>

The [XmlArray] attribute has four properties that affect the respElement1 wrapper element, as shown in Table 6-12.

Table 6-12: Properties of the [XmlArray] Attribute

Property

Description

ElementName

Sets the name of the wrapper element

Form

Sets the wrapper element’s form attribute in the schema to qualified (the default) or unqualified

IsNullable

Sets the wrapper element’s nillable attribute in the schema to true (the default) or false, and thus whether the array as a whole can be set to null

Namespace

Sets the namespace attribute for the wrapper element

If we tag respElement1 with an [XmlArrayItem] attribute in addition to [XmlArray], we also get control over the elements representing the items in our array:

[XmlArray] [XmlArrayItem(ElementName="Message")] public string[] respElement1;

The inclusion of the second attribute changes the complex type definition representing the array to the following:

<s:complexType name="ArrayOfString">   <s:sequence>     <s:element minOccurs="0" maxOccurs="unbounded"                 name="Message" nillable="true" type="s:string" />    </s:sequence> </s:complexType>

The [XmlArrayItem] attribute has seven properties, as detailed in Table 6-13.

Table 6-13: Properties of the [XmlArrayItem] Attribute

Property

Description

DataType

Sets the XSD type of the element representing an array item

ElementName

Sets the name of the element representing an array item

Form

Sets the form attribute for the element representing an array item to qualified (the default) or unqualified

IsNullable

Sets the nillable attribute for the wrapper element to true (the default) or false, and thus whether the array as a whole can be set to null

Namespace

Sets the namespace for the element representing an array item

NestingLevel

For multidimensional arrays, indicates the nesting level for each dimension

Type

Identifies the .NET type of the item in the array

The NestingLevel attribute indicates to the serializer which dimensions of a multidimensional array should be nested in which. For example:

[XmlArray] [XmlArrayItem(ElementName = "breadth", NestingLevel = 0)] [XmlArrayItem(ElementName = "width", NestingLevel = 1)] [XmlArrayItem(ElementName = "height", NestingLevel = 2)] public int[][][] BoxDimensions;

In this case, the BoxDimensions field is serialized into the following format:

<BoxDimensions>   <breadth>     <width>       <height>int</height>       <height>int</height>     </width>     <width>       <height>int</height>       <height>int</height>     </width>   </breadth>   <breadth>        </breadth> </BoxDimensions>

The default value for NestingLevel is 0, so there is no need to apply this attribute to single dimensional arrays.

Marker Interfaces for [XmlArray] and [XmlArrayItem]

Although their names suggest that they are only for tagging arrays, the [XmlArray] and [XmlArrayItem] attributes can be used to tag most classes that implement the IEnumerable interface. By default, then, this statement can be stretched to include most classes that implement ICollection because the former must be implemented for the latter to work. You’ll find a bare-bones example that shows this called ISerializeService in the chapter’s sample code.

Note, however, that this statement cannot be stretched to include all classes that implement ICollection. The attributes and, indeed, the serializer will produce errors if the class implements certain other interfaces. For example, tagging a hash table will produce an error because it implements IDictionary in addition to ICollection and IEnumerable.

Creating Choices with [XmlElement] and [XmlChoiceIdentifier]

As you saw when we discussed writing schemas explicitly, complex types with complex content can be derived by using <sequence>, <choice>, or <all> elements. Thus far, you’ve seen the serializer produce only sequences of elements. It is possible to create a <choice>, which we’ll see now, but there is no way to imply <all>.

In our TagService example, suppose we want to distinguish between soloists and bands. Rather than create an extra service for each scenario, we can just change the name of the element in the request message from <Artist> to <SoloSinger> or to <Band>, with the contents remaining the same. In an XML schema, this is represented by a <choice>; we can write this implicitly in our class by tagging multiple [XmlElement] attributes to the same class member. For example:

[XmlElement(ElementName="SoloSinger")] [XmlElement(ElementName="Band")] public string ReqElement1;

The serializer now creates a <choice>-based complex type definition but has no way to maintain a record of what choice has been made. Thus it produces an error unless the class member is also tagged with an [XmlChoiceIdentifier] attribute that identifies a helper variable to maintain that record:

[XmlElement(ElementName="SoloSinger")] [XmlElement(ElementName="Band")] [XmlChoiceIdentifier("performerType")] public string ReqElement1;

That helper variable must represent an enumeration containing one value for each choice of element:

[XmlIgnore] public PerformerTypes performerType; [XmlType(IncludeInSchema=false)] public enum PerformerTypes {     SoloSinger, Band }

With this helper variable added to our service but not included in the service schema itself, the serializer indeed creates a <choice> of our two elements in the service schema:

<s:complexType name="requestClass">   <s:sequence>      <s:choice minOccurs="1" maxOccurs="1">         <s:element minOccurs="0" maxOccurs="1"                     name="SoloSinger" type="s:string" />          <s:element minOccurs="0" maxOccurs="1"                     name="Band" type="s:string" />        </s:choice>       <s:element minOccurs="0" maxOccurs="unbounded"                   name="Album" type="s0:subclass" />    </s:sequence>   <s:attribute name="Sender" type="s:string" />  </s:complexType>

If a request containing a choice is deserialized into classes, the serializer sets the value of the helper variable. If you want to serialize a class containing a choice into XML, you must set the helper variable yourself before starting the serialization.

Wrapping It All Up with [SoapDocumentMethod]

Last but not least, we come to the Web methods themselves and how they influence the service schema. As you’ll recall from our look at [XmlRoot], the serializer uses a class method’s signature to derive a “wrapper element” for request and response messages that encapsulates elements representing the method’s parameters and return values, respectively. So, by default, the request message takes this shape:

<soap:envelope>   <soap:body>     <methodname> ... </methodname>   </soap:body> </soap:envelope>

and the response to that has this shape by default:

<soap:envelope>   <soap:body>     <methodnameResponse> ... </methodnameResponse>   </soap:body> </soap:envelope>

Tagging a class method with [SoapDocumentMethod] affects only these wrapper elements. However, it first specifies that the request and response messages for this element be defined by a schema in the service’s WSDL file. The [SoapDocumentMethod] attribute has nine attributes, as listed in Table 6-14. Of those, Action, Binding, and OneWay affect only the method’s representation in the service’s WSDL document, but we’ve included them for completeness.

Table 6-14: Properties of the [SoapDocMethod] Attribute

Property

Description

Action

Sets the SOAPAction value required in the request message to access this method.

Binding

Sets the binding to be used for the method. The binding is identified elsewhere in the class by a WebServiceBinding attribute.

OneWay

Sets whether the server must produce a response to the method request.

ParameterStyle

Sets whether the <methodname> and <methodnameResponse> wrapper elements should exist in messages to and from the service. Possible values are SoapParameterStyle.Wrapped (the default) for yes and SoapParameterStyle.Bare for no. This value overrides any default set with the [SoapDocumentService] attribute.

RequestElementName

If wrapper elements are to be used, this sets the name of the wrapper element for the request message.

RequestNamespace

If wrapper elements are to be used, this sets the namespace of the wrapper element for the request message.

ResponseElementName

If wrapper elements are to be used, this sets the name of the wrapper element for the response message.

ResponseNamespace

If wrapper elements are to be used, this sets the namespace of the wrapper element for the response message.

Use

Sets whether the message parts are encoded against a schema (literal) or SOAP Section 5 encoding (encoded). This overrides any default set by using the [SoapDocumentService] attribute.

Serialization Attributes for Encoded Services

If you need to create a service whose methods use Section 5 encoding rather than XSD types, tag the service class with [SoapDocumentService (Use=SoapBindingUse.Encoded)] or [SoapRpcService]. The serializer doesn’t support RPC/literal methods, so it’s assumed that you’ll use Section 5 encoding with RPC-style messages. If you need to set up encoding on individual methods in your service, you can tag them with [SoapDocumentMethod (Use=SoapBindingUse.Encoded)] or [SoapRpcMethod].

By default, the structure of an encoded method is quite a bit larger than that of the equivalent document/literal method. It’s also more difficult to establish what you need to include in the message if you’re writing a custom client. However, like the [Xml…] attributes, a handful of [Soap…] attributes let us alter the template for an encoded method’s request and response messages to at least give us a fighting chance.

In fact, they all do the same job as their [Xml…] counterparts and affect the same part of the schema for our methods. We won’t go through them all individually, but Table 6-15 shows the [Soap…] attributes, the [Xml…] attributes they correspond to, and a list of the properties you can include in the [Soap…] attributes. These have the equivalent effect as the properties of the same name for the [Xml…] attributes.

Table 6-15: XML Serialization Attributes

Section 5 Encoding Attribute

Literal Message Attribute

Properties

Description

SoapAttribute

XmlAttribute

AttributeName, DataType, Namespace

Specifies that the tagged item should be serialized as an attribute and how

SoapElement

XmlElement

DataType, ElementName, IsNullable

Specifies that the tagged item should be serialized as an element and how

SoapEnum

XmlEnum

Name

Sets how the tagged value in an enumeration will be serialized

SoapIgnore

XmlIgnore

SoapIgnore has no properties

Specifies that the tagged item should not be serialized

SoapInclude

XmlInclude

Type

Specifies that elements of the tagged class can be substituted by elements of the named subclasses

SoapType

XmlType

IncludeInSchema, Namespace, TypeName

Specifies that the tagged item should be serialized as an XML type and how

There are no equivalents to [XmlAnyAttribute], [XmlAnyElement], [XmlArray], [XmlArrayItem], [XmlChoiceIdentifier], [XmlRoot], or [XmlText].

If you take our obfuscated set of classes and method (in BareServiceClass.cs) and add them to an encoded service, you can experiment to confirm what they each do. You’ll see this in the example service called TagEncService in the sample code.

Serializer-Unfriendly Classes

You saw in the section on marker interfaces for [XmlArray] how the serializer used by the Web method API chokes on classes that inherit certain interfaces. If you’re happy to use the Handler API to create a custom serialization solution for the class, you can get around the issue that way. If not, your class should inherit from the IXmlSerializable interface in the .NET Framework; this interface will provide hints to the serializer on how to work with it. Its signature is as follows:

public interface IXmlSerializable {     System.Xml.Schema.XmlSchema GetSchema();     void ReadXml(System.Xml.XmlReader reader);     void WriteXml(System.Xml.XmlWriter writer); }

Unfortunately, IXmlSerializable is listed for internal use within .NET only. We can surmise, however, that GetSchema will be called when the WSDL and schema is generated for the service, that ReadXml will be called when you receive an XML message, and that WriteXml will be called when the class needs to be serialized into a message.

Tip

Only one class inherits from the IXmlSerializable interface in the .NET framework—System.Data.DataSet. You might want to disassemble that class and study how it implements the three IXmlSerializable methods.




Programming Microsoft. NET XML Web Services
Programming MicrosoftВ® .NET XML Web Services (Pro-Developer)
ISBN: 0735619123
EAN: 2147483647
Year: 2005
Pages: 172

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net