Printer Friendly Version      Send     
Click to Rate and Give Feedback
Related Articles
This article presents an overview of the motivation behind new techniques that decompose problems into independent pieces for optimal use of parallel programming.

By David Callahan (October 2008)
We take a look at planned support for parallel programming for both managed and native code in the next version of Visual Studio.

By Stephen Toub and Hazim Shafi (October 2008)
Here we describe some of the more common challenges to concurrent programming and present advice for coping with them in your software.

By Joe Duffy (October 2008)
Here is an ASP.NET AJAX data-driven Web application that takes the best features from server- and client-side programming to deliver an efficient, user-friendly experience.

By Bertrand Le Roy (October 2008)
More ...
Popular Articles
Here is an ASP.NET AJAX data-driven Web application that takes the best features from server- and client-side programming to deliver an efficient, user-friendly experience.

By Bertrand Le Roy (October 2008)
Efficient parallel applications aren’t born by merely running an old app on a parallel processor machine. Tuning needs to be done if you’re to gain maximum benefit.

By Rahul V. Patil and Boby George (June 2008)
If you're unfamiliar with Windows Presentation Foundation (WPF), building that first Silverlight custom control can be a daunting experience. This article walks through the process.

By Jeff Prosise (August 2008)
This article presents an overview of the motivation behind new techniques that decompose problems into independent pieces for optimal use of parallel programming.

By David Callahan (October 2008)
More ...
Read the Blog
Well designed code keeps things that have to change together as close together in the code as possible and allows unrelated things in the code to change independently, while minimizing duplication in the code. In the October 2008 issue of MSDN Magazine, Jeremy Miller shows you some design ...
Read more!
The process for ink capture and analysis on the Tablet PC is straightforward in managed code. To the uninitiated developer, however, creating unmanaged Tablet PC applications can be rather daunting. In the October 2008 issue of MSDN Magazine, Gus Class a quick introduction to the Tablet PC ...
Read more!
Multicore systems are becoming increasingly prevalent, but the majority of software today will not automatically take advantage of this additional processing ability. And multithreaded programming, for anything but the most trivial of systems, is incredibly difficult and error prone today. In the October 2008 issue of MSDN ...
Read more!
Concurrent programming is notoriously difficult, even for experts. You have all of the correctness and security challenges of sequential programs plus all of the difficulties of parallelism and concurrent access to shared resources. In the October 2008 issue of MSDN Magazine, David Callahan describes ...
Read more!
A major advantage of AJAX and Silverlight applications is that they can transparently and continuously interact with a back-end service. The problem is that they run over HTTP, which wasn't designed with security in mind. In the September 2008 issue of MSDN Magazine, Dino Esposito shows you ...
Read more!
Unhandled exception processing shouldn't be a mystery. It's actually quite useful since it gives a crashing application an opportunity to perform last-minute diagnostic logging about what went wrong. In the September 2008 issue of MSDN Magazine, Gaurav Khanna discusses how ...
Read more!
More ...
XML Schemas
Take Advantage of Existing External XML Schemas with a Custom Import Framework in ASP.NET
Scott Short
Code download available at: XMLSchemaImporting.exe (58 KB)
Browse the Code Online

This article assumes you're familiar with XML and ASP.NET
Level of Difficulty 1 2 3
SUMMARY
Over the years, many industry-standard XML schemas and dialects have been developed. These industry-specific schemas embrace the original purpose of XML and are extremely valuable in promoting and supporting B2B interaction. Unfortunately, the ASP.NET Web Services runtime does not allow developers to directly reference external schemas from within their XML Web Services interface (the WSDL file). This article builds an external schema framework as an extension to the ASP.NET Web Services runtime to enable you to reference external schemas within your XML Web Service interface.
One of the primary goals of XML Web Services is to facilitate interoperability between applications running on a broad range of platforms. This interoperability is achieved through the adherence to recognized standards. They include, but are not limited to, the standards at the core of XML Web Services: XML, XML Schema (XSD), Web Services Description Language (WSDL), and SOAP.
In addition, you may also want to take advantage of emerging standards such as the Global XML Web Services Architecture (GXA). GXA defines a set of modular extensions to the XML Web Services platform for security, routing, and discovery.
Adherence to standards is not limited to platform protocols. Many industries have collaboratively developed XML schemas that define industry-specific concepts. Among others, the travel industry (http://www.opentravel.org), the hospitality industry (http://www.hitis.org), and the education industry (http://www.imsproject.org) have published schemas. If your XML Web Service is intended for a particular industry, it makes sense to leverage targeted schemas.
You can also use task-oriented XML dialects. Some examples include the Astronomical Markup Language (AIML), Robotic Markup Language (RoboML), and the Speech Application Language Tags (SALT). The more an XML Web Service adheres to recognized standards, the higher the probability that it will be consumed by others.
In most cases, leveraging an industry standard involves referencing external schemas within the WSDL document of your XML Web Service. Unfortunately, the initial version of the ASP.NET Web Services platform does not provide out-of-the-box support for referencing external schemas.
One way to overcome this limitation would be to write your WSDL file by hand and ensure that your XML Web Service supports the interface defined by the custom WSDL file. Doing so, however, makes development and maintenance more complex. First, it is pretty easy for the implementation and the interface to become out of sync. Second, not only does the original developer need to have a strong knowledge of WSDL, but any developer who maintains the code will have to as well.
Another way to reference external schemas from your XML Web Service is to extend the ASP.NET Web Services platform, which provides a rich interception model that allows developers to add this kind of extended functionality. I used this interception model to create what I call an external schema framework (ESF).
Using an ESF has many advantages over modifying WSDL by hand. For example, it supports an attribute-based programming paradigm that should already be familiar to developers writing to the ASP.NET platform. It also circumvents our problem associated with WSDL.exe when it is used to generate Microsoft® .NET proxy classes for a WSDL document that imports an external schema. The problem is that the current version of WSDL.exe requires a superfluous WSDL import, even if none of the entities defined within the external schema are directly referenced by WSDL elements. I'll explain this in more detail later in the article, but if you use my external schema framework, you will avoid this.
This article is composed of two primary sections. In the first section, I'll describe how to use the ESF and in the second, I'll discuss its implementation. If you are interested in delving deeper into the implementation, download the entire source code from the link at the top of this article.

Using the External Schema Framework
The ESF will feel very familiar to anyone who has used the XML serialization attributes—the set of attributes that can be used to control how instances of .NET classes, as well as the class definitions themselves, are serialized to XML. The XML serialization attributes are also used by the ASP.NET runtime to control the creation of the XSD within the WSDL file of a document-literal style XML Web Service.
The ESF defines three attributes: XmlImportedType, XmlImportedElement, and XmlImportedRoot. These custom attributes are extensions of their XML serialization attribute counterparts. For example, the XmlImportedElement attribute is used in much the same way as the XmlElement attribute.
Before you can use the ESF attributes, you must configure the web.config file of the XML Web Service that will use the framework. Add the <soapExtensionReflectorTypes/> section to the web.config file for the ASP.NET Web Service that uses the ESF attributes. (In the case of the ESF, I defined the XmlImportedAttributesReflector class.)
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <webServices>
      <soapExtensionReflectorTypes>
        <add type=
          "SShort.Xml.Serialization.XmlImportedAttributesReflector, 
          SShort.Xml.Serialization"/>
      </soapExtensionReflectorTypes>
    </webServices>
  </system.web>
</configuration>
The XmlImportedAttributesReflector is registered as a SOAP Extension Reflector type. I will discuss this class and the SOAP Extension Reflector extension framework in much greater detail in the second half of this article. For now, suffice it to say that this class is responsible for modifying the WSDL document based on the ESF attributes that are used in the implementation of the ASP.NET Web Service.
Now that I have explained how to properly configure the XmlImportedAttributesReflector, I'll walk through a few examples that demonstrate how to use the ESF attributes.

The XmlImportedType Attribute
In my first example, suppose I want to create an XML Web Service that consumes an element of type MyExternalDatatype as defined in a schema file called MyExternalSchema.xsd. The contents of MyExternalSchema.xsd are shown in the MyExternalDatatype definition (see Figure 1).
First, I define a .NET type to which the MyExternalDatatype will be mapped. There are a number of ways you can define the .NET type; the primary requirement is that the type not be more restrictive than instances of the MyExternalDatatype XML datatype. This ensures that the XML serializer, the component responsible for serializing and deserializing instances of .NET types to and from XML, will be able to deserialize all valid instances of the XML datatype received from the client into an instance of the corresponding .NET type.
Consider the following type definitions:
[XmlImportedType("urn:MyExternalSchema", "MyExternalSchema.xsd",
TypeName="MyExternalDatatype")]
public class MyLooseType
{
    [XmlAnyAttribute]
    XmlAttribute [] attributes;

    [XmlAnyElement]
    XmlElement [] elements;
}
The MyLooseType .NET type definition certainly meets the criteria of not being more restrictive than instances of the MyExternalDatatype XML datatype. However, consider the following alternative .NET type definition:
[XmlImportedType("urn:MyExternalSchema", "MyExternalSchema.xsd",
TypeName="MyExternalDatatype")]
public class MyType
{
    public string    MyString;
    public int       MyInt;
    public bool      MyBool;
}
The MyType .NET type maps more closely to the MyExternalDatatype XML datatype. Because of this, it offers a number of advantages over the MyLooseType .NET type.
First, the MyType .NET type is easier to program against. Most developers find it easier to access fields and properties than to iterate through an array of loosely typed elements. Second, the MyType .NET type provides better safeguards against errors seeping into the implementation of the XML Web Service. For example, if you try to set the MyBool field to a string value, the C# compiler will generate an error when you compile the Web Service.
It doesn't matter if you use the MyLooseType or the MyType .NET types within your XML Web Service; the same WSDL will be generated since they both are decorated with the XmlImportedType attribute that specifies the same set of parameters. Because of these advantages, the MyExternalDatatype code (shown in Figure 2) uses the MyType .NET type definition.

The XmlImportedElement Attribute
Many times, an external schema will use anonymous XML datatypes, which are defined within an element declaration and cannot be referenced by some other definition. Instead of referencing the XML datatype directly, the schema will define a set of elements that you can reference within your XML Web Service.
The use of anonymous XML datatypes is becoming common in modern schema definitions. For example, many of the GXA schemas, such as the WS-Security schema (http://schemas.xmlsoap.org/ws/2002/04/secext), make heavy use of anonymous XML datatypes. As a result, I cannot use the XmlImportedType attribute because I cannot reference them directly. Instead, I will use the XmlImportedElement attribute.
For my next example, I will create an XML Web Service that accepts a purchase order as defined by the external schema shown in Figure 3. The schema, contained with the PurchaseOrder.xsd file, defines the PurchaseOrder element that contains a number of anonymous XML datatype definitions.
I cannot reference the underlying XML datatype of the PurchaseOrder element because it is anonymous. Instead, I need to reference the element definition. In the next example, the SubmitPurchaseOrder1 Web method, I do so using the XmlImportedElement attribute shown here:
[WebMethod]
[SoapDocumentMethod(OneWay=true)]
public void SubmitPurchaseOrder(
  [XmlImportedElement("urn:SomeIndustryStandardsOrganization", 
    "PurchaseOrder.xsd",
    ElementName="PurchaseOrder")]
  PurchaseOrder po)
{
  // Implementation ...
}
In the previous code, the SubmitPurchaseOrder Web method accepts a purchase order as defined by the schema contained in the PurchaseOrder.xsd file. This is done by using the XmlImportedElement attribute to map the PurchaseOrder parameter to the PurchaseOrder XML element. When the user submits a valid purchase order document, it will be deserialized by the ASP.NET runtime into an instance of the PurchaseOrder .NET type.

The XmlImportedRoot Attribute
In general, the XmlImportedElement attribute should be used to decorate public properties and fields defined by a class and not parameters within a method signature. To understand why, suppose I wanted to define another Web method called RequestPurchaseOrder that returns a purchase order to the caller.
Both the SubmitPurchaseOrder and ReceivePurchaseOrder methods could reference the same class definition, but each of the parameters of type PurchaseOrder would have to be decorated with the XmlImportedElement attribute. In the event that the definition of the PurchaseOrder element changed, both attributes would need to be updated.
Instead of decorating each parameter with the XmlImportedElement attribute, I will decorate the PurchaseOrder class itself with the XmlImportedRoot attribute, as shown here in the SubmitPurchaseOrderRequest2 Web method:
[WebMethod]
  [SoapDocumentMethod(OneWay=true)]
  public void SubmitPurchaseOrder(PurchaseOrder po)
  {
    // Implementation ...
}
XmlImportedRoot controls how an XML instance document is deserialized into an instance of the PurchaseOrder .NET datatype.
Even though the purchase order element is not the root element of the request document, the use of the XmlImportedRoot attribute is valid because the implementation of the ASP.NET runtime serializes/deserializes each parameter separately. Therefore, because each input parameter is deserialized from its own instance document, the XmlImportedRoot attribute decorating the underlying .NET type definition is honored.
There are some scenarios where you must use the XmlImportedRoot attribute rather than the XmlImportedElement attribute. For example, suppose your Web method accepts a SOAP header defined by an external schema. The ASP.NET runtime deserializes the SOAP header into a member variable of the class that types the XML Web Service. The problem with SOAP headers is that if you decorate the member variable that represents the SOAP header with the XmlImportedElement attribute, the attribute will be ignored by the ASP.NET runtime. Like parameters, the member variable containing the SOAP header is serialized and deserialized separately. Therefore, you must decorate the .NET type definition that corresponds to the SOAP header with the XmlImportedRoot attribute in order to reference SOAP headers that are defined in external schemas.

Implementing the ESF
Now that you know how to use the ESF, I want to show you the fun part—how the framework was created. To cover every detail, however, would require more than a single article, so I'll cover only some of the more interesting sections of the code and recommend that you take a look at the full code download for more details.
As you have seen, the framework is composed of two parts: a set of custom attributes and a component responsible for modifying the WSDL created by ASP.NET, the XmlImportedAttributesReflector. The implementation of the custom attributes is fairly straightforward, whereas the bulk of the work is performed within the XmlImportedAttributesReflector.

Defining the Custom Attributes
Within the framework, I define three custom attributes: XmlImportedElement, XmlImportedRoot, and XmlImportedType. The XmlImportedTypeAttribute is shown in Figure 4. All three of these attributes are derivatives of the custom attributes defined by the XML serialization framework.
As with all the ESF attributes, the XmlImportedType attribute extends its base type by exposing one additional property, the SchemaLocation property, which exposes the location of the external schema. Since the schema location as well as the XML namespace URI is required by the XmlImportedAttributesReflector, a constructor is defined to ensure that both are provided when creating a new instance of the attribute.
Since the XmlImportedTypeAttribute class derives from the XmlTypeAttribute class, .NET types decorated with the XmlImportedType attribute will be processed by the ASP.NET runtime as if they were decorated with the XmlType attribute. The XmlImportedAttributesReflector class will take advantage of this and build upon this functionality. I will describe this in more detail later.

The XmlImportedAttributesReflector
As previously mentioned, the XmlImportedReflector class is responsible for modifying the WSDL that is auto-generated by the ASP.NET runtime based on the use of the custom attributes I defined. It does so by using the SOAP Extension Reflector interception mechanism provided by the ASP.NET platform.
To create a SOAP Extension Reflector, you create a class that derives from the SoapExtensionReflector class. You can then add your own implementation by overriding the ReflectMethod virtual method defined by the SoapExtensionReflector class. In the case of the ESF, the XmlImportedReflector class derives from the SoapExtensionReflector class.

The ReflectMethod Method
The ASP.NET runtime builds the WSDL file incrementally by iterating through each method decorated with the WebMethod attribute within the class that implements the Web Service. When the SOAP Extension Reflector is registered within an ASP.NET Web Service's web.config file, the ASP.NET runtime will create an instance of the class and then invoke its ReflectMethod method.
In most cases, the ASP.NET runtime will invoke ReflectMethod after the WSDL has been created for each Web method. This provides the SOAP Extension Reflector with an opportunity to modify the WSDL as it sees fit.
In the case of the SoapExtensionReflector class, I need to process the ESF attributes. This involves processing each Web method as well as the complete WSDL document.
Unfortunately, the SOAP Extension Reflector framework does not provide an interception point for performing processing that must occur once the WSDL document has been completely built. Therefore, I implement a workaround within the ReflectMethod method, which I will cover later in the article.
For now, I will discuss the implementation of ReflectMethod for the XmlImportedAttributes class shown in Figure 5. Each time ReflectMethod is invoked, it is responsible for reflecting on the method signature of the Web method in search of ESF attributes. This is accomplished through the SoapExtensionReflector.ReflectionContext property.

Processing the ESF Attributes
The ReflectionContext property provides access to an instance of the ProtocolReflector class. This object is updated before the ReflectMethod is executed by the runtime and contains references to objects related to the Web method currently being processed. The properties of ProtocolReflector provide access to WSDL-specific objects, such as the corresponding binding definition and instances of the Reflection API objects related to the Web method.
For an example where I use the ReflectionContext property, let's review the ProcessReturnParameter method, shown in Figure 6. This method is responsible for processing any ESF attributes that decorate the return parameter and its underlying type. To determine if any ESF attributes do this, I access the Method property through the ReflectionContext property. This property contains information relevant to the method, including its return parameter. First, I determine if the return parameter is decorated with the XmlImportedElement attribute. I do so by examining the ReturnTypeCustomAttributeProvider property. Despite its name, it provides access to the custom attributes exposed by the return itself and not its underlying type. Next, I determine if the return parameter's underlying type is decorated with the XmlImportedRoot and XmlImportedType attributes. I perform searches for each attribute via the ReturnType property's GetCustomAttributes method. Finally, I call the ProcessTypeMembers method, which recursively processes the public properties and fields exposed by the type. This recursion can be expensive, so consider publishing a static copy of the WSDL document generated by the ASP.NET runtime when the XML Web Service is released to production.
I perform similar steps when processing each of the method parameters as well as any SOAP headers associated with the method. The major difference is when processing the SOAP headers, I don't need to search for the XmlImportedElement attribute.
If an attribute is found on a public property or field, the WSDL document is not immediately modified. Instead, the namespace and the schema location are obtained from the attribute and placed in a hash table. Once the entire WSDL document is created, it is then modified to reflect the imported elements and/or datatypes.
The primary reason for waiting to modify the WSDL until it has been entirely populated by the ASP.NET runtime is efficiency. By processing a consolidated list of namespaces and schema locations, I avoid having to delete the schema for a given namespace multiple times.
Unfortunately, the SOAP Extension Reflector framework does not provide an interception point for this purpose. As a workaround, I take advantage of the fact that I have the entire WSDL generated by the ASP.NET runtime during the execution of the last call to ReflectMethod.
When ReflectMethod is first called, I calculate the number of times the method will be called (totalReflectMethodCalls) and maintain a counter on how many times the method has been called (reflectMethodCallCount). Once totalReflectMethodCalls is equal to reflectMethodCallCount, I will execute the code that modifies the WSDL so that it properly imports the external schemas.
The real trick is figuring out the number of times the ReflectMethod will be called. The GetTotalReflectMethodCalls method shown in Figure 7 is responsible for obtaining the count.
The implementation is actually pretty straightforward, with one minor twist. I iterate through each of the methods exposed by the .NET type that contains the implementation of the Web Service. While doing so, I maintain a count of the methods that are decorated by the WebMethod attribute. Now here's the twist. The ReflectMethod will not be called for any methods that reference an externally defined WSDL binding. Therefore, I check to see if the method is also decorated with the SoapDocumentMethod attribute where its Binding property contains a non-empty string. If so, I will not increment the counter.

Modifying the WSDL
Once the WSDL document has been created by the ASP.NET runtime, the XmlImportedAttributesReflector needs to modify the WSDL so that it properly imports any external schemas. This may involve importing the external schema into the WSDL, importing the external schema in one of the schemas embedded within the types section of the WSDL document, or both.
Both the WSDL and XML Schema specifications define their own mechanism for importing an external schema. To confuse matters, both of the specifications accomplish this by defining an <import/> element.
If an entity defined within the external schema is referenced within the WSDL document itself, the external schema can be referenced using the WSDL <import/> element. If entities defined within the external schema are referenced within a schema definition that is in turn embedded within the WSDL document, then the external schema can be referenced by the embedded schema using the XML Schema <import/> element.
When the ASP.NET runtime makes its last call to ReflectMethod, I invoke the ProcessImportedNamespaces method shown in Figure 8. This method is responsible for modifying the WSDL document so that it properly references elements and datatypes defined within external schemas.
Recall that the ESF attributes derive from their XML serialization attribute counterparts. Because of this, the ASP.NET runtime will process the attributes as if they were their XML serialization counterparts. Therefore, the ASP.NET runtime will create a schema and redefine the externally defined XML elements and datatypes, properly import the schema into other schemas that reference it, and fully qualify all references to elements and datatypes defined within the schema.
Although incomplete, quite a bit of useful work is performed by the ASP.NET runtime to properly reference entities defined within the external schema. Because of the work performed by the ASP.NET runtime, I avoid writing a considerable amount of grunge code. As a result, I have a couple of additional tasks that I need to perform.
1.Delete any schemas that redefine externally defined XML elements and datatypes.
2.Modify any XML Schema <import/> elements that reference external schemas to include the URL where the external schema can be obtained.
3.If the externally defined XML elements and datatypes are referenced directly from WSDL, I need to add a WSDL <import/> element.
I implement these tasks in the ProcessImportedNamespaces method shown in Figure 8. First, I complete the first and third tasks by iterating through the dictionary of namespaces of external schemas used by the XML Web Service's interface. For each item in the dictionary, I delete the schema that defines the namespace within the WSDL and add a WSDL <import/> element.
I add a WSDL <import/> element for each namespace, regardless if it is used within the WSDL document itself. The primary reason for this is that the current version of WSDL.exe has a bug. WSDL.exe only uses the WSDL <import/> element to resolve the location of an external schema and will ignore any XML Schema <import/> elements.
Finally, I perform the second task and modify all XML Schema <import/> elements that reference the externally defined schemas in order to include the URL where the associated external schemas can be obtained. This is accomplished by iterating through the import elements for each schema within the WSDL document. If the namespace attribute matches one of the external schemas in the importedNamespaces dictionary, then I add the schemaLocation attribute to the import element. I set the value of the schemaLocation attribute to the value of the URI where the external schema is located.

Conclusion
I hope you find the ESF as indispensable as I do. In a future version, I would like to incorporate caching. Better yet, I would like a future version of the SOAP Extension Reflector framework to provide integrated support for caching, even though recursion and the Reflection API impose a performance penalty when generating the WSDL file.
In the meantime, I typically include a static version of the WSDL file as part of the ASP.NET Web Service's deployment instructions. If it is included as part of the installation process, you reduce the chance of the WSDL file getting out of sync with the implementation. I would also like to extend the ESF to allow a developer to import additional kinds of entities. This would include XSD entities such as enumerations. This would also include WSDL entities such as port type and message definitions. The ASP.NET runtime provides a means for you to import binding definitions through the use of the WebServiceBinding attribute. Stay tuned as the ESF continues to evolve and feel free to contact me if you have any questions or suggestions.

For background information see:
Building XML Web Services for the Microsoft .NET Platform by Scott Short (Microsoft Press, 2002)

Scott Shortis a Senior Consultant with Microsoft Consulting Services and author of Building XML Web Services for the Microsoft .NET Platform (Microsoft Press, 2002). Contact him at sshort@microsoft.com.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.
Page view tracker