Fixing the Shared Type Schema Importer Extension for nullable value types

When you add a Web Reference to your project, Visual Studio will also import all schema types defined in the WSDL’s schema section. You can create a schema importer extension (SIE) to influence the way the XSD schema types are mapped to your CLR types. There is a very nice article by Jelle Druyts on a SIE that shows how you can create a extension that will map based on an XML configuration section in your devenv.exe.config file. Go read that one first, then read on.

<?xml version="1.0" encoding="utf-8" ?>

<schemaImporterExtensions>

  <sharedTypeMappings>

    <mapping xmlNamespace="http://schemas.samplebusiness.net/SharedTypes"

            xmlTypeName="Customer"

            clrAssemblyPath="C:\Sample\SampleBusinessTypes.dll"

            clrClassName="SampleBusinessTypes.Customer" />

  </sharedTypeMappings>

</schemaImporterExtensions>

It a reasonably straightforward process of looking up the imported XML element name and namespace in the mapping elements and adding returning the mapped CLR class name instead of the 1–1 mapping that would normally occur. We are using this schema importer extension on a project, but project-colleague Frans Harinck found that it did not map nullable enums correctly. So I ventured out to fix this.

The problem is that nullable value types on the web service will now have a xsd:nillable=”true” attribute set in the WSDL schema. This was always true for reference types, which (of course) can have a null value. Value types couldn’t until .NET 2.0. The SIE from Jelle did not take this into account (although SIE was introduced in Visual Studio 2005). When you debug the SIE (side note: Set the build path directly into the folder C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies and attach the debugger to another VS2005 instance. In the attached instance Add or Update a Web Reference and your breakpoints should be hit), you will see how the name and namespace of your nullable value type pass by. These are mapped to your own CLR type, but not as nullable. You can determine whether your type is marked xsd:nillable=”true” by evaluating the XmlSchemaObject object supplied. This indicates that the corresponding schema element is nillable, and that it is a simpleType. The combination tells you the type is nullable. So instead of returning the mapped CLR name, it should return System.Nullable<CLR name>.

Below is the bolded change you need to make to the source code from the article, i.e. the SharedTypeSchemaImporterExtension.cs file to be exact.

public override string ImportSchemaType(string name, string ns,

  XmlSchemaObject context, XmlSchemas schemas,

  XmlSchemaImporter importer, CodeCompileUnit compileUnit,

  CodeNamespace mainNamespace, CodeGenerationOptions options,

  CodeDomProvider codeProvider)

{

  try

  {

    foreach (SharedTypeMappingElement mapping in

      SchemaImporterExtensionsConfiguration.Instance.SharedTypeMappings)

    {

      // Check if the namespace and type name match.

      if (mapping.Matches(ns, name))

      {

        // Add an assembly reference.

        if (!string.IsNullOrEmpty(mapping.ClrAssemblyPath))

        {

          compileUnit.ReferencedAssemblies.Add(mapping.ClrAssemblyPath);

        }

 

        // Indicate that no XML schema type should be imported but that a

        // well-known shared CLR type will be used.

 

       // Valuetypes can be nullable in .NET 2.0

       XmlSchemaElement schemaElement = context as XmlSchemaElement;

       if (schemaElement != null && schemaElement.IsNillable &&

           schemaElement.ElementSchemaType is XmlSchemaSimpleType)

       {

         return String.Format("System.Nullable<{0}>", mapping.ClrClassName);

       }

 

        return mapping.ClrClassName;

      }

    }

  }

  catch (Exception exc)

  {

    System.Windows.Forms.MessageBox.Show(exc.Message + Environment.NewLine +

      exc.StackTrace, "SharedTypeSchemaImporterExtension Exception",

      System.Windows.Forms.MessageBoxButtons.OK,

      System.Windows.Forms.MessageBoxIcon.Error);

  }

 

  // No match, delegate to the base class.

  return base.ImportSchemaType(name, ns, context, schemas, importer,

    compileUnit, mainNamespace, options, codeProvider);

}

Comments

# Mahmoud Arram said:

Hello Alex,

Thanks for the hint on how to actually debug the schema importer.

I have just implemented Jelle's solution. Another adjustment I had to make was to add an import statement:

string assemblyNameSpace = mapping.ClrClassName.Substring(0, mapping.ClrClassName.LastIndexOf('.'));

mainNamespace.Imports.Add(new CodeNamespaceImport(assemblyNameSpace));

I have just added a [WebMethod] to my web service that returns a nullable int (int?). After refreshing the web reference in my client project, I see that the proxy has generated the correct type:

       public System.Nullable&lt;int&gt; testMethod() {

           object[] results = this.Invoke("testMethod", new object[0]);

           return ((System.Nullable&lt;int&gt;)(results[0]));

       }

So, I would like to reiterate that your patch is needed in the case one implements his or her own "nullable value"

dinsdag 22 augustus 2006 22:24
# Klaus said:

Hello Alex,
Thank you for your article.
Im still unable to implement Jelle's solution. I make all steps in that article but still cant make it work.
I have put the Dll into GAC(gacutil /i c:\JelleDruyts.SchemaImporterExtensions.dll), but vs2005 told me that it cant find the dll.
I have tried put it in the Visual Studio 2005 private assemblies directory. but still cant find it.
could you plz email the modefied devenv.exe.config and something more helpful.
thank you very much!!

zondag 3 december 2006 10:12
# Klaus said:

my Email is Gyy@mzdol.com,thank you!!

zondag 3 december 2006 10:13
# Alex Thissen said:

Hello Klaus,

Here’s (part of) the devenv.exe.config that I have to get things working:

<configuration>
  <system.xml.serialization>
    <schemaImporterExtensions>
      <add name=”MyExtension” type=”Demo.MyExtension, ExtensionLibrary” />
    </schemaImporterExtensions>
  </system.xml.serialization>
  …
</configuration>

Assuming that the extension class is called MyExtension in the Demo namespace and inside the ExtensionLibrary assembly (e.g. ExtensionLibrary.dll).

Next, you add the configuration for the extension class. In the case of Jelle Druyts extension this would be

<configuration>
  …
  <schemaImporterExtensions>
    <sharedTypeMappings>
      <add xmlNamespace=http://somenamespace xmlTypeName=”responseHeader” clrAssemblyPath=”C:\Source\bin\soaputils.dll” clrClassName=”MyNamespace.ResponseHeader” />
      … more mappings …
    </sharedTypeMappings>
  </schemaImporterExtensions>
</configuration>

You should map each and every class involved in your object graph, to avoid the default code generation to take over. When I used Jelle’s implementation, I mapped all of the custom types involved in a web service, including the base classes. You can check (if you intend to map all types) that there are only methods in the generated code.

Hope this helps.

donderdag 7 december 2006 22:18