Simon Fell > Its just code > SchemaImporterExtension

Tuesday, August 15, 2006

I've been digging into SchemaImporterExtension to see if I can fix the generated code from .NET's wsdl.exe to include setting the FooSpecified flag from the foo Setter (see this post from way back in 2004 if you need a refresher on the details). What's currently generated for an optional element (minOccurs='0' / maxOccurs='1') is this

private System.Nullable<System.DateTime> createdDateField;
    private bool createdDateFieldSpecified;

    [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
    public System.Nullable<System.DateTime> CreatedDate {
        get {
            return this.createdDateField;
        }
        set {
            this.createdDateField = value;
        }
    }
And what It really should of been is
private System.Nullable<System.DateTime> createdDateField;
    private bool createdDateFieldSpecified;

    [System.Xml.Serialization.XmlElementAttribute(IsNullable=true)]
    public System.Nullable<System.DateTime> CreatedDate {
        get {
            return this.createdDateField;
        }
        set {
            this.createdDateField = value;
            this.createdDateFieldSpecified = true;
        }
    }

Its frustrating, I'm sure someone up in Redmond could fix this in a couple of hours, but I've been waiting for 2 years now (and I expect the wait will be a whole bunch longer). But with .NET 2.0, SchemaImporterExtension gives hope that I can fix this myself without having to wait. Unfortuantly the samples and docs are pretty skinny, it seems like you get a chance to replace the code generation for a particular type, but you don't seem to get any access to the default code that would be generated, this means in this case you need to duplicate all of the logic that generates the code for a ComplexType. As anyone who's done work with generating code from WSDL will tell you, this is a non trivial amount of work to get right. Anway, I plowed in and got the basics working, however have run into a couple of road blocks, how do I tell the importer to import an additional type (e.g. complexType foo has an element of type bar, so I need to import the bar type as well), and have it generate its code / call back into the extensions etc ? You get passed an instance of a XmlSchemaImporter object to your code, (but this is doc'd as "This class supports the .NET Framework infrastructure and is not intended to be used directly from your code." huh? so why is it in the methods in a public extension point?) anyway, this seems to have the hopeful method called ImportSchemaType (amongst others), however all this seems to do is generate the naming mappings between the schema type QName you pass it, and the resulting CLR type, it doesn't generate any code in the final proxy. I'm also seeing that ImportSchemaType gets called for some schemaTypes (in particular complexTypes that are used by other complexTypes), and the resulting additions to the CodeNamespace seem to get dropped.).

I put together a quick sample that re-pro's the latter problem with code getting dropped, here's the ImporterExtension, a test WSDL, parameters file for wsdl.exe and the resulting generated file, notice that even though ImportSchemaType was called for the Bar type, and we added a CodeTypeDeclaration to the mainNamespace for it, it doesn't appear in the final code.

C:\Source\dotnet\sfSchemaImporterTest>wsdl.exe /par:params.xml
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 2.0.50727.42]
Copyright (C) Microsoft Corporation. All rights reserved.
Generating type Bar
adding Bar to mainNamespace
Generating type FooResult
adding FooResult to mainNamespace
Writing file 'C:\Source\dotnet\sfSchemaImporterTest\DoFooService.cs'.

This also only seems to allow me the chance to fiddle with the schema types, is there an equvilent extension to allow me to fiddle with the web services specifics bits? (like the DoFooService class in the above sample, or the classes generated for soap headers)