ASMX 2.0 and SchemaImporterExtensions

I like the concept of SchemaImporterExtensions in ASMX 2.0.  As you import the schema through proxy generation using WSDL.exe, you can generate your own code to control serialization for the client.

This can be a great tool if you have a particularly nasty interop problem and the generated proxy code doesn't work.  For instance, Dan Maharry has been having some problems with the XmlSerializer.  Scott Hanselman suggested for him to control the serialization on the server side using IXmlSerializable, but on the client side, I am betting that he sees the reverse problem.  This is a situation where a SchemaImporterExtension and some custom generated client-side code can provide a solution.

I combined Yasser Shohoud's Net Tunes SIE and John Bristowe's SIE example, and added the ability actually emit a new file that contains the generated code.  This is the SchemaImporterExtension sample that I showed on today's ASMX webcast (also available for on-demand viewing). 

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

namespace CustomerServiceExtensions
{
    public class BillItemExtension : SchemaImporterExtension
    {

        public override string ImportSchemaType(
            string name, string ns, XmlSchemaObject context,
            XmlSchemas schemas, XmlSchemaImporter importer,
            CodeCompileUnit compileUnit, CodeNamespace mainNamespace,
            CodeGenerationOptions options, CodeDomProvider codeProvider)
        {
            ConsoleColor defaultbgColor = Console.BackgroundColor;
            ConsoleColor defaultfgColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("\tOrderExtension called for name = " + name + " and namespace = " + ns);
            Console.ForegroundColor = defaultfgColor;

            if (name.Equals("Customer") && ns.Equals("https://contoso"))
            {
                Console.BackgroundColor = ConsoleColor.Blue;
                Console.WriteLine();
                Console.WriteLine("\t\tExtension matched for Customer!");
                Console.WriteLine();
                Console.BackgroundColor = defaultbgColor;
                
                mainNamespace.Imports.Add(new CodeNamespaceImport("Msdn.Web.Samples"));
                return "Customer";
            }
            if (name.Equals("BillItem") && ns.Equals("https://contoso"))
            {
                Console.BackgroundColor = ConsoleColor.Blue;
                Console.WriteLine();
                Console.WriteLine("\t\tExtension matched for BillItem!");
                Console.WriteLine();
                Console.BackgroundColor = defaultbgColor;
                
                mainNamespace.Imports.Add(new CodeNamespaceImport("Msdn.Web.Samples"));
                return "OrderDetails";
            }
            if (name.Equals("ArrayOfBillItem") && ns.Equals("https://contoso"))
            {
                Console.BackgroundColor = ConsoleColor.Blue;
                Console.WriteLine();
                Console.WriteLine("\t\tExtension matched for ArrayOfBillItem!");
                Console.WriteLine();
                Console.BackgroundColor = defaultbgColor;
                
                mainNamespace.Imports.Add(new CodeNamespaceImport("Msdn.Web.Samples"));

                CodeTypeDeclaration speaker = new CodeTypeDeclaration("Person");
                mainNamespace.Types.Add(speaker);

                CodeMemberField firstNameField = new CodeMemberField(
                        new CodeTypeReference(
                        typeof(string)),
                        "_firstName");
                speaker.Members.Add(firstNameField);

                CodeMemberProperty firstNameProperty = new CodeMemberProperty();
                firstNameProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                firstNameProperty.GetStatements.Add(new CodeMethodReturnStatement(
                        new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(),
                        "_firstName")));
                firstNameProperty.Name = "FirstName";
                firstNameProperty.SetStatements.Add(new CodeAssignStatement(
                        new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(),
                        "_firstName"),
                        new CodePropertySetValueReferenceExpression()));
                firstNameProperty.Type = new CodeTypeReference(typeof(string));
                speaker.Members.Add(firstNameProperty);

                CodeMemberField surnameField = new CodeMemberField(
                        new CodeTypeReference(
                        typeof(string)),
                        "_surname");
                speaker.Members.Add(surnameField);

                CodeMemberProperty surnameProperty = new CodeMemberProperty();
                surnameProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                surnameProperty.GetStatements.Add(new CodeMethodReturnStatement(
                        new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(),
                        "_surname")));
                surnameProperty.Name = "Surname";
                surnameProperty.SetStatements.Add(new CodeAssignStatement(
                        new CodeFieldReferenceExpression(
                        new CodeThisReferenceExpression(),
                        "_surname"),
                        new CodePropertySetValueReferenceExpression()));
                surnameProperty.Type = new CodeTypeReference(
                        typeof(string));
                speaker.Members.Add(surnameProperty);

                System.IO.StreamWriter writer = new StreamWriter("customCode.cs");

                codeProvider.GenerateCodeFromCompileUnit(compileUnit,writer, null);
                Console.WriteLine("\n\nWriting file customCode.cs\n\n");
                writer.Flush();
                writer.Close();
                return "ArrayOfBillItem";
            }


            return null;
        }
    }
}

You can see in the generated code that you have the ability to emit the code to a file.  When you run WSDL.exe, the code will be generated in the same directory as the SoapHttpClientProtocol generated type.  The generated code looks like this:

 //------------------------------------------------------------------------------
// 
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.42
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// 
//------------------------------------------------------------------------------

using Msdn.Web.Samples;



public class Person {
    
    private string _firstName;
    
    private string _surname;
    
    public string FirstName {
        get {
            return this._firstName;
        }
        set {
            this._firstName = value;
        }
    }
    
    public string Surname {
        get {
            return this._surname;
        }
        set {
            this._surname = value;
        }
    }
}

The lesson here is that you can generate whatever serialization code you want, and SchemaImporterExtensions can provide a nice way of getting around some of the interop problems that you can still face between platforms.

I am looking forward to broader adoption of the Indigo approach of DataContract / DataMember to get rid of most of the interop problems that are in the wild today.