We recommend using Visual Studio 2017
This documentation is archived and is not being maintained.

New Features for Web Service Developers in Beta 1 of the .NET Framework 2.0

Visual Studio 2005
 

Elliot Rapp
Yasser Shohoud
Matt Tavis

Microsoft Corporation

June 2004

Applies to:
   Microsoft .NET Framework 2.0
   Microsoft Visual Studio 2005

Summary: Check out the new improvements in productivity, performance, extensibility, and standards support in Microsoft .NET Framework 2.0. (12 printed pages)


Introduction

The .NET Framework 2.0 Beta 1 release represents a significant milestone for the Web services and XML Serialization stack in the .NET Framework. The enhancements in this release are centered on delivering a mature platform that makes it even easier to develop connected systems with Web services. Developers who have built Web services using previous versions of the Framework will be happy to find this version delivers many of the features and enhancements they've asked for. These new features fall into four categories:

  1. Productivity Enhancements. There are a number of features that provide a better developer experience when consuming Web services. For example, the new event-based asynchronous programming model provides an intuitive way for invoking Web services asynchronously.
  2. Extensibility. Many Web services developers want to take full control over the Web service schema, the wire message, and the proxy types generated by wsdl.exe or Add Web Reference. Developers will find what they're looking for in the new Schema Importer Extensions and the improved IXmlSerializable interface.
  3. Performance. Two new features lower client start-up time and network utilization, in addition to other XML serialization performance improvements.
  4. Commitment to Interoperability and Standards. Out-of-the-box support makes it even easier to build Web services that conform to the WS-I Basic Profile 1.0. In addition, Version 2.0 of the .NET Framework supports the W3C SOAP 1.2 standard.

Productivity Enhancements

Data Binding and Event-Based Asynchronous Programming

A major focus of our efforts in the .NET Framework 2.0 has been enhancing developer productivity. Our goal is to make .NET the most productive platform for Web service application development. To that end, we have listened to your feedback and introduced a suite of three new features that we hope will provide an immediate improvement to your productivity. These features include enabling data binding to data returned from a Web service, a new event-based asynchronous programming model, and type sharing between services.

Many developers have asked for the ability to automatically bind to data returned from Web services. Version 2.0 of the .NET Framework enables this scenario by generating properties on client proxy types rather than fields making auto-generated proxy types suitable for data binding by default.

The second feature concerns the use of asynchronous method calls. The new event-based asynchronous programming model simplifies the task of invoking a Web service asynchronously. In addition to using an event paradigm instead of BeginInvoke/EndInvoke, the new programming model also takes care of thread synchronization automatically so that event handlers are invoked on the same thread that made the asynchronous call. Here is a code example that shows how to use the new event-based asynchronous programming model. The SearchCompleted function also shows how to bind the returned data to a UI data grid.

private void btnSearch_Click(object sender, 
                             EventArgs e)
{

   //application code omitted for brevity
       ...

   //hookup async event handler
   proxy.SearchCompleted += 
           new SearchCompletedEventHandler(this.SearchCompleted);
   //call the Search method asynchronously
   proxy.SearchAsync(criteria, currentSearchToken);
   btnCancel.Enabled = true;
}

void SearchCompleted(object sender, SearchCompletedEventArgs args)
{
   //bind returned results to the UI data grid
   gridSongs.DataSource = args.Result;
}

private void btnCancel_Click(object sender, EventArgs e)
{
   //by clicking on the Cancel button users 
       //can cancel the asynchronous search operation
   proxy.CancelAsync(currentSearchToken);
}

Type Sharing

In many real-world scenarios, you may want to factor your application's functionality into individual services that group methods that logically fit together. This typically leads to sharing one or more data types between those Web services. For example, you may have an Order Entry service that returns an Order object and an Order Status service that takes in an Order object. Today, a client that wants to consume both of these services ends up with two different Order classes, making it cumbersome to take the output of the first service and pass it to the second.

Version 2.0 of the .NET Framework introduces a feature that provides the client with one Order class that's shared by the two service proxies. This feature is exposed on wsdl.exe with the /sharetypes switch. When using this switch you supply the URLs of two or more WSDL documents on the command line. For example:

wsdl.exe /sharetypes http://localhost/webservice1.asmx?wsdl http://localhost/webservice2.asmx?wsdl http://localhost/webservice3.asmx?wsdl

The code generation engine recognizes when types are equivalent based on their names and namespaces, and by comparing their Schema definition.

Extensibility

The goal of any good infrastructure component is to be invisible until needed and provide an extensibility model for custom scenarios. In most Web services scenarios the XmlSerializer meets these requirements. However, recent customer feedback and more advanced Web services usage scenarios have made apparent the need for greater extensibility in the serialization infrastructure. To that end, the .NET Framework 2.0 introduces two major new features that provide you with more control for advanced serialization and code generation capabilities:

  • Full support of imperative serialization using IXmlSerializable
  • Custom proxy code generation through Schema Importer Extensions

Imperative serialization using IXmlSerializable

The IXmlSerializable interface has been in the .NET Framework since version 1.0, but was not intended for general usage. This interface was designed specifically for System.Data.DataSet to enable custom serialization control and was marked for internal use to discourage its usage. Full support for IXmlSerializable in ASP.NET Web services has been introduced in the .NET Framework 2.0 in direct response to customer feedback for scenarios that require more control of the schema and wire format.

Now developers can control both the representation of their types on the wire as well as the schema that is generated by the Web service. To remain consistent with the existing interface, the methods and signatures have been left unaltered but the IXmlSerializable.GetSchema method should no longer be used. A new attribute, [XmlSchemaProvider], has been introduced to indicate the static method on the type that generates and inserts the schema in the XmlSchemaSet for the service.

The ReadXml method controls reading the serialized format and is handed an XmlReader to read from the stream and populate the type. The WriteXml method controls writing the serialized format and is handed an XmlWriter to write out the data to the stream.

An example where you might need this type of control is for streaming large amounts of data. To enable streaming of data, you have to turn off response buffering and then chunk the data into discrete blocks of data demarcated with XML elements. IXmlSerializable lets you control the schema for this chunking format and control the reading and writing of this data to the stream with the ReadXml and WriteXml methods.

The following example shows a class implementing IXmlSerializable:

using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
//application code omitted for brevity
...

//This is the new XmlSchemaProviderAttribute
[XmlSchemaProvider("GetMySchema")]
public class SongStream : IXmlSerializable
{
       private const string ns = "http://demos.teched2004.com/webservices";
       private string filePath;
       public SongStream()
       {
              //default constructor for serializer
       }

       public static XmlQualifiedName GetMySchema(XmlSchemaSet xs)
       {
              //code omitted for brevity
              //Here you want to add your type's schema to xs
              //and returned your type's QName

       }

       void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
       {

              //code omitted for brevity
              //Here you want to write out the serialized instance
       }

       System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
       {
              // GetSchema on the IXmlSerializable interface is not used.
              return null;
       }

       void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
       {
              //code omitted for brevity
              //Here you want to deserialize instance data from the reader

       }
}

By implementing IxmlSerializable, you get full control over both the Schema representation and wire format of your type.

Schema Importer Extensions

In addition to runtime extensibility provided by IXmlSerializable, there is also a need for greater design-time control. When using Add Web Reference or wsdl.exe, the Web services infrastructure interprets the WSDL of the target service and generates proxy classes to interact with that service. Customer feedback has indicated the need for even further control. Version 2.0 of the .NET Framework introduces several new features in this area to improve the usability of the generated proxy classes. To enable custom proxy code generation, Schema Importer Extensions have been introduced.

A Schema Importer Extension is a type that can be registered with the Web services infrastructure through code or via config and will be called during the schema import process to allow an extension to interpret the schema and inject code into the proxy. During schema import, each extension is called in the configured order for each schema type encountered, and the extension can choose to inject code or to simply ignore that type in the schema. This mechanism allows developers to build extensions that will map schema constructs matched by name, namespace, and/or shape to custom classes or code.

This type of control is useful when the client of a Web service has custom types that are much richer than those generated by wsdl.exe that the developer wants to be used by the proxy. Prior to the .NET Framework 2.0, this was possible only by modifying the generated proxy. These changes were lost when the proxy was regenerated. Schema Importer Extensions can now be developed and registered to map schema type to the custom type every time the proxy is generated.

The following example shows a simple example of using a Schema Importer Extension to map a schema type to an existing client class:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;
using System.CodeDom;
using System.CodeDom.Compiler;

public class NetTunesSIE : SchemaImporterExtension
{

       public override string ImportSchemaType(string name, string ns,
              XmlSchemaObject context, XmlSchemas schemas, 
XmlSchemaImporter importer, CodeNamespace codeNamespace,
StringCollection references, CodeGenerationOptions options,
ICodeGenerator codeGenerator)
       {
            if (name.Equals("songStream") &&        
                ns.Equals("http://demos.teched2004.com/webservices"))
              {
                    codeNamespace.Imports.Add(
       new CodeNamespaceImport("NetTunes"));
                    return "SongFile";
              }
              return null;
       }

       public override string ImportSchemaType(XmlSchemaType type,
              XmlSchemaObject context, XmlSchemas schemas, 
XmlSchemaImporter importer, CodeNamespace codeNamespace,
StringCollection references, CodeGenerationOptions options, 
ICodeGenerator codeGenerator)
       {
              return null;
       }

       public override CodeExpression ImportDefaultValue(string value, 
string type)
       {
              return new CodePrimitiveExpression(null);
       }
}

To allow this extension to execute when generating proxy code, you need to register it in machine.config using the following configuration section:

       <system.xml.serialization>
              <schemaImporterExtensions>
<add name="SongFile" type="NetTunes.NetTunesSIE, NetTunesSIE, 
       Version=1.0.0.0, Culture=neutral,
PublicKeyToken=abcdef0123456789" />
              </schemaImporterExtensions>
       </system.xml.serialization>

Once registered, this extension would cause the Schema type with a name of "songStream" in the namespace of "http://demos.teched2004.com/webservices" to be mapped to the NetTunes.SongFile type.

Web Services Anywhere

There are some interesting scenarios that require exposing a Web service from an application that is not running in IIS; for example, running a Windows Forms desktop application that receives callbacks from a remote server. Since version 1.0 of the .NET Framework, it has been possible to host ASP.NET (including ASMX Web services) in any process, such as a console or Windows Forms application. Now HttpListener makes it easier to do this. For example, the ASP.NET Cassini sample Web server is a Windows application hosting ASP.NET.

Version 2.0 of the .NET Framework makes it easier to host ASP.NET and ASMX services in a managed application using the new HttpListener class which sits on top of http.sys and handles listening for incoming HTTP requests within your application. The figure below shows at a high level how a managed application uses HttpListener to host ASMX services.

ms379631.wsnetfx2_01(en-US,VS.80).gif

Performance

Pre-generation of serialization assemblies

To enable developers to modify proxy classes without having to worry about updating serialization code, the XmlSerializer now dynamically generates serialization code for all proxy types the first time that the proxy is instantiated. Despite the additional processing time required to generate the serialization code and compile a temporary assembly on the fly, this approach works quite well in the majority of Web service scenarios. Applications that have very complex proxies with numerous types may require a longer period to generate the serialization code, and this has prompted the development of a new deployment tool for pre-generating serialization assemblies. Version 2.0 of the .NET Framework introduces sgen.exe, which can be run on the assemblies containing proxy classes to create a serialization assembly that will be loaded at runtime to avoid dynamic generation and compilation.

This tool simply runs through all the types in an assembly and generates the necessary serialization code, which can be loaded at runtime. Once you are ready to deploy a Web service client, you can use this tool to generate a serialization assembly that can be shipped with your application's assemblies to considerably reduce complex proxy instantiation times. Running this tool is as simple as:

C:\>sgen.exe MyProject.dll

This will generate MyProject.XmlSerializers.dll, which should be placed in the same directory with MyProject.dll. At runtime, when the XML serializer finds this assembly, it will use it for serialization of proxy types found in MyProject.dll.

SOAP Reply Compression

The .NET Framework HttpWebRequest class now supports decompression of HTTP replies. You can take advantage of this feature combined with the IIS 6.0 compression feature to save on bandwidth and potentially improve your application's response time. To enable decompression in the Beta 1 release of the .NET Framework 2.0, you need to get the HttpWebRequest and set its EnableDecompression property to "true" on the client side. This code snippet shows how to do that:

using System.Web.Services.Protocols;
using System.Net;
class ServiceProxy : SoapHttpClientProtocol
{
protected override System.Net.WebRequest GetWebRequest(Uri uri)
   {
      HttpWebRequest request=
base.GetWebRequest(uri) as HttpWebRequest;
      if (request!= null)
      {
         request.EnableDecompression = true;
      }
      return request;
   }

}

The final release of the .NET Framework 2.0 will have this property exposed directly on SoapHttpClientProtocol so you will be able to write code like:

ServiceProxy proxy=new ServiceProxy();
proxy.EnableDecompression = true;

You will also need to configure IIS to enable compression of replies from Web services. See Knowledge Base article 322603, HOW TO: Enable ASPX Compression in IIS for instructions on how to do this.

Commitment to Interoperability and Standards

Support for WS-I Basic Profile 1.0

The WS-I Basic Profile 1.0 specifies a subset of SOAP 1.1 and WSDL 1.1 designed to improve interoperability between Web services. Following the guidance in Building Interoperable Web Services: WS-I Basic Profile 1.0, you can build services that conform to the Basic Profile today using .NET Framework 1.0 or 1.1. Version 2.0 of the .NET Framework makes it even easier to build Web services that conform to the Basic Profile 1.0 by introducing a property named ConformanceClaims on WebServiceBindingAttribute. Setting this property to WsiClaims.BP10 as shown below causes the Web services stack to behave in a manner than conforms to the Basic Profile.

using System.Web.Services;
[WebServiceBinding(ConformanceClaims = WsiClaims.BP10)]
public class TheService
{ ... }

Note that there are some service features/settings that would break Basic Profile conformance and would cause an exception when you invoke the Web service or request its WSDL. For example, if you use SOAP encoding (by using SoapRpcService or SoapRpcMethod attributes), your service no longer conforms to the Basic Profile. To use these non-conforming features, you simply need to indicate that your service does not conform to the Basic Profile by removing the WebServiceBindingAttribute.ConformanceClaims property.

When consuming any Web service, wsdl.exe will check the service's WSDL for Basic Profile conformance and will display warnings if it finds the service to be non-conformant. These warnings let you know upfront any conformance issues with the service's WSDL without preventing you from consuming the service.

Support for W3C SOAP 1.2

Version 2.0 of the .NET Framework includes support for the W3C SOAP version 1.2 protocol in addition to the existing SOAP version 1.1 protocol. On the server side, SOAP 1.2 is on by default and can be turned off via configuration settings in machine.config or web.config:

<configuration>
   <system.web>
    <webServices>
         <protocols>
        <remove name="HttpSoap1.2"/>
       </protocols>
   </webServices>
    </system.web>
</configuration>

With both SOAP 1.1 and SOAP 1.2 enabled on the server side, each Web service will support both protocols, allowing the client to choose one of the two protocols. This increases the reach of your Web services.

On the client side, you can select SOAP 1.1 or 1.2 by setting the SoapVersion property of the proxy class. For example:

proxy.SoapVersion = System.Web.Services.Protocols.SoapProtocolVersion.Soap12;

When using wsdl.exe to consume a Web service that supports both SOAP 1.1 and 1.2, you can specify /protocol: SOAP12 on the command line to generate a proxy class that has its SoapVersion property set to SoapProtocolVersion.Soap12. When you invoke the service you will be using SOAP 1.2.

In Conclusion

Web services and the XML serialization stack in the .NET Framework 2.0 offer a set of compelling features that improve developer productivity, application performance, and standards conformance. We would like you to install the Visual Studio 2005 Express Beta products (which include the .NET Framework 2.0), build Web services, and send us your feedback using the dotnet.framework.webservices newsgroup.

Show: