Kirk Evans Blog

.NET From a Markup Perspective

Web Services Contract First: Drop Schema in app_code with a Custom Build Provider

Web Services Contract First: Drop Schema in app_code with a Custom Build Provider

  • Comments 9

I now have complete respect for Fritz' reaction to custom build providers.

Web applications in Visual Studio 2005 allow you to drop items in the app_code directory in your project and classes are compiled in the background for you.  For instance, drop a WSDL file in there and you get a proxy for the web service, drop a schema and you get a DataSet.  I wanted to drop a schema in app_code and get an XML serializable class.

I cheated. 

I posted to an internal distribution list asking if anyone knew how to do this.  Rick Lievano pointed me to Fritz' article.  I then pinged Elena Kharitidi, and she referenced creating a custom build provider as well.  Hmm... now, how do I get that done?  Then I remembered Daniel Cazzulino's excellent article on XsdCodeGen.  A couple modifications between Fritz' blog posting and Daniel's article, and I had a jaw-dropping experience as well.

using System;
using System.Text;
using System.Web.Compilation;
using System.Web;
using System.Web.UI;
using System.CodeDom;
using System.Web.Hosting;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace Msdn.Samples.Compilation
{    
    public class XsdClassBuildProvider : System.Web.Compilation.BuildProvider
    {
        public override void GenerateCode(AssemblyBuilder assemblyBuilder)
        {            
            // HACK: Need to update this to get namespace from schema somehow.  Good for blog code.
            string ns = "Evans.Test";            
            XmlSchema xsd = null;

            using (Stream schemaStream = base.OpenStream(base.VirtualPath))
            {                                
                xsd = XmlSchema.Read(schemaStream, null);
                XmlSchemaSet set = new XmlSchemaSet();
                set.Add(xsd);
                set.Compile();
            }

            XmlSchemas schemas = new XmlSchemas();
            schemas.Add(xsd);
            XmlSchemaImporter importer = new XmlSchemaImporter(schemas);

            CodeNamespace codeNamespace = new CodeNamespace(ns);

            // Generate a CodeCompileUnit from the dataset
            CodeCompileUnit codeCompileUnit = new CodeCompileUnit();

            codeCompileUnit.Namespaces.Add(codeNamespace);
            XmlCodeExporter exporter = new XmlCodeExporter(codeNamespace);

            foreach (XmlSchemaElement element in xsd.Elements.Values)
            {
                // Import the mapping first.
                XmlTypeMapping mapping = importer.ImportTypeMapping(
                  element.QualifiedName);
                // Export the code finally.
                exporter.ExportTypeMapping(mapping);
            }

            // Add the CodeCompileUnit to the compilation
            assemblyBuilder.AddCodeCompileUnit(this, codeCompileUnit);
        }
    }
}

I built the class and added a reference from a test web service project.  Since there is already a BuildProvider registered for files with .XSD by default, you need to remove the existing mapping then add in your custom provider.  In the web service project, I added the following to configuration/system.web/compilation:

<compilation debug="false">
 <buildProviders>
  <remove extension=".xsd"/>
  <add appliesTo="Code" extension=".xsd" type="Msdn.Samples.Compilation.XsdClassBuildProvider"/>
 </buildProviders>
</compilation>

That was too easy.  I created a very simple schema and added it to the app_code directory in a web service:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="test" targetNamespace="http://tempuri.org/test.xsd" elementFormDefault="qualified" xmlns="http://tempuri.org/test.xsd" xmlns:mstns="http://tempuri.org/test.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:complexType name="customerType">
        <xs:sequence>
            <xs:element name="customerID" type="xs:string" />
            <xs:element name="customerName" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
    <xs:element name="customer" type="customerType">
    </xs:element>
</xs:schema>

Here is what the screen looked like when my jaw hit the floor as well.

 

Custom build provider in ASP.NET that provides strongly-typed access to XML serializable schemas

The reason this is so cool is because it removes the extra step of dropping back to command-line with XSD.EXE to use the /classes switch.  Now, you just drop the schema in there and you are good to go.

  • Yeah, they're cool. Check out ExpressionBuilders as well -- they provide the ability to create custom ASP.NET tags which call functions that emit code into the page during compilation.

    The only thing missing is the ability to do this outside of a web project...
  • kfarmer - Scott Guthrie posted about BuildProviders and the original plan to provide broad declarative support, but it was scaled back due to time and resources.

    http://weblogs.asp.net/scottgu/archive/2005/09/02/424337.aspx
  • Talked with Aaron Skonnard for a little while today at the PluralSight booth, he told me that he used...
  • Thanks to everyone that attended yesterday's Atlanta Code Camp 2006 session on &quot;What's New in ASMX 2.0&quot;.&amp;nbsp;...
  • This is old news for some of you, but I&#39;ve never looked at the ASP.NET buildprovider functionality

  • This is old news for some of you, but I&#39;ve never looked at the ASP.NET buildprovider functionality

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 3 and 2 and type the answer here:
  • Post
Translate This Page
Search
Archive
Archives