Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Extract an XML Schema from a .NET 2.0 ConfigurationSection subclass for Configuration Validation & Intellisense in Visual Studio

4.89/5 (12 votes)
17 May 2007LGPL312 min read 1   831  
How to extract an XML Schema (XSD) from a .NET 2.0 ConfigurationSection subclass using the attached XSDExtractor application I've written. The article also shows how to use the XSD file to provide validation and Intellisense support in Visual Studio.

Screenshot - screenshot.GIF

Introduction

.NET 2.0 introduces significant changes to the System.Configuration namespace. There are plenty of articles out there that already deal with new ways of using the configuration classes. This article will not try to steal their thunder by essentially copying their content here. Instead, this article will describe a utility that I have written which helps bridge a gap in the new configuration model: providing intellisense and validation of the configuration file whilst actually editing it.

Prerequisites

It is assumed that you are already familiar with the .NET 2.0 System.Configuration namespace. If you aren't, then take a look at this article by Luis Angel R.C.

Contents

Examples

Throughout this article, I will refer to a sample project which contains a custom configuration section. The project is a simple FileWatcher application, which watches a number of files / folders based on the configuration settings. The settings are handled by a custom configuration section that has an XSD already generated for it. You can download the project from one of the links above.

Detail

One of the best improvements to developer productivity ever (in my opinion) has to be Intellisense. Microsoft added Intellisense features to the XML editor in Visual Studio 2002. The editor prompts you with automatic completion of elements and attributes, so long as the XML has an XML Schema supporting it that was known to Visual Studio. Now with Visual Studio 2005 and the .NET 2.0 configuration framework, we have a powerful configuration namespace built into the framework. The trouble with the new configuration namespace is that the configuration is based upon ConfigurationSection classes and not XML Schemas. So, the XML editor can't help us complete the configuration elements.

This was the precise reason why I wrote the XSDExtractor application. My other pet project makes extensive use of configuration files and I wanted to make sure that people could configure the application with minimal fuss. Maintaining lots of XML configuration files isn't fun when you don't get any assistance from the IDE. I also wanted to allow editing of configuration files remotely, but still with validation of the file. This meant either sending copies of the ConfigurationSection classes to the remote client or sending them an XML Schema instead. I think you can guess which one I chose.

The XSDExtractor program (source code available above) attached to this article creates an XML Schema file from a compiled ConfigurationSection type by using reflection. The resultant XSD file can then be used when writing the configuration files to give the editor Intellisense.

Significant Architectural Code

The XSDExtractor application is built up of parsers. Parsers in the XSDExtractor sense are able to read the metadata contained in a compiled assembly and then deal with it appropriately. The System.Configuration namespace contains support for writing your own elements, collections and attributes. So, I copied this model with a TypeParser inheritance model. The base class is shown below:

Image 2

The TypeParser abstract class serves as the base class for all the TypeParsers in the application. In addition, the type parsers are instantiated by calling a static method on the TypeParserFactory class. The responsibility for creating the correct parser is delegated to the factory class. All that the caller knows is that the return value from the factory class inherits from TypeParser.

Other significant code within the application is within the Validator model. The System.Configuration namespace contains attributes that can restrict the type of data that is stored in a configuration field. Luckily, the XML Schema specification is powerful enough to also allow restriction of data in fields. So, the Validator model's responsibility is to convert the validator attributes into XML Schema Simple Types, with facets that limit the values available to the field. The SimpleType shown below has been created from an enumeration in the ConfigurationSection class.

Image 3

In a similar fashion to the TypeParser model, the Validator model also uses a Factory class to instantiate the validator. The factory object returns an instance of the ValidatorAttributeParser. Well, a subclass of it actually:

Image 4

This hierarchy of classes handles the ValidatorAttribute family of attributes. These can be placed on any properties of the ConfigurationSection and are used to restrict the data that is viewed as acceptable to the configuration. For example, you can restrict the values of an integer to be between a certain range of values. If the value entered in the configuration file is outside the values, an exception is thrown. Each subclass of the ValidatorAttributeParser class deals with a particular attribute in the System.Configuration namespace. See the limitations section below for further details on these classes.

System.Xml.Schema namespace

When I first set out to write the XSDExtractor application, I thought that I would have to write my own classes to mimic the capabilities of the XML Schema language, such as ComplexTypes, SimpleTypes, Elements, etc. As it turns out, .NET ships with a System.Xml.Schema namespace that contains all of the classes I required. This enabled me to build the output XML Schema using a rich collection of objects.

It took me a little while to understand how to say that an element was of a particular complex type or that an attribute was of a type of simple type. For anyone who's interested, this is the simple rule that I followed:

To set an object's type to an existing global type -- either known in the document or via an imported namespace -- I set the SchemaTypeName equal to an XmlQualifiedName object, like so:

XML
<object>.SchemaTypeName = new XmlQualifiedName("xs:string") 

For an object whose type was to be consumed only by that object, I would set the SchemaType property equal to either a new XmlSchemaComplexType or XmlSchemaSimpleType, like so:

XML
<object>.SchemaType = new XmlSchemaSimpleType(); 

Once I got over these differences, the Xml.Schema namespace was nice and easy to use.

Using XSDExtractor

First of all, extract the utility into its own folder. XSDExtractor is a command window project and so has to be passed parameters on the command line. If you run XSDExtractor without any parameters, then it will ask you if it's ok to convert all ConfigurationSection subclasses in all assemblies discovered in the current directory and any subdirectories. This is a recommended usage of the utility if you have lots of ConfigurationSection classes that you would like converted. The XSDs will be saved in the same directory as the assembly that contains the ConfigurationSection class. It will be named the same as the ConfigutationSection class name in order to prevent filename collisions. Here's a list of command-line parameters that the utility accepts:

Switch Detail Example
/a Specifies the assembly that should be inspected /a C:\myassembly.dll
/c Class name that should be inspected /c MyNamespace.MyClass
/r Root namespace element. Name of the element which will be the first element in the Xsd. If omitted, then the ConfigurationSection class name is used instead. /r MyRoot
/s Silence. No prompting for overwriting or any decisions. /s true

How to Use the XSD Produced by XSDExtractor

Visual Studio 2005 ships with a powerful XML editor. To get Intellisense for your configuration section, all you have to do is add the xmlns attribute to the root element. This should make the job of writing the configuration section much easier. Visual Studio 2003 did a similar job, but it assumed that you were writing a document from the root. Unfortunately, when you're writing configuration sections, this is never the case. As the following diagram shows, adding an xmlns attribute to your configuration root element allows Visual Studio 2005 to provide Intellisense support.

Image 5

Adding the xmlns attribute does cause an unfortunate side effect: your configuration will not load! The reason why it won't load is because the configuration loader detects the attribute and then tells you that it contains an unknown attribute 'xmlns'. It's actually a very useful feature because if you mistype any attributes, you'll know about it straight away. The solution is to add an optional string property to your configuration like this:

C#
[ConfigurationProperty("xmlns", IsRequired = false)]
public string Xmlns {
  get { return (string)base["xmlns"];}
}

I find that it's easier to add this property to a base class that you inherit all your ConfigurationSection subclasses from, so that you get the property without thinking about it.

How Does It Work?

Using the source code from the MultiFileWatcher example application, you'll see that the ConfigurationSection class is relatively simple.

C#
public class MultiWatcherConfigurationSection  : ConfigurationSection {

  /*
   Xml Namespace attribute. Added so that the parser doesnt complain
  */
  [ConfigurationProperty("xmlns", IsRequired = false)]
  public string Xmlns {
    get { return (string)base["xmlns"]; }
  }

  /*
    Collection of files that will be monitored by the application
  */
  [ConfigurationProperty("files",
      IsDefaultCollection=true, IsKey=false, IsRequired=true)]
  public FilesCollection Files {
    get { return (FilesCollection)base["files"]; }
  }
}

It contains two properties, the xmlns property I mentioned in the previous section and a collection which allows multiple elements to appear in the configuration.

XSDExtractor works with the types and attributes contained in an assembly to build the XML Schema. In the above example, the first thing that XSDExtractor would have done is detect that the MultiWatcherConfigurationSection class is actually a subclass of the ConfigurationSection class. Then, it would examine all the public properties of the type and check which ones were decorated with the [ConfigurationProperty] attribute. These properties are the expected -- although sometimes optional -- elements of the configuration file.

The relationship between which properties are attributes and which are elements is simple. If the property returns either a built-in type -- such as int, string, bool, etc. -- then it's classed as an attribute of its containing element. If the property returns a type of ConfigurationElement, then it's classed as a child element of its containing element. Finally, if the property returns a type of ConfigurationElementCollection, then it's also a child element of its containing element, but has special rules applied to the particle element.

So, using the above rule, the configuration will be expected to have an attribute of type string in the root configuration element and a child element (repeatable) of the FilesCollection. Of course, the FileCollection also contains further information on the child element. Here's a snippet of FileCollection class:

C#
[ConfigurationCollection(typeof(FileElement),
                         AddItemName="add",
                         CollectionType
                         = ConfigurationElementCollectionType.BasicMap)]
public class FilesCollection : ConfigurationElementCollection {
   .
   .

The bit that we are interested in is the attribute [ConfigurationCollection]. This tells us that the collection will consist of ConfigurationElement's of the FileElement type and that the element name given to each of these FileElements will actually be "add." The CollectionType property tells us that there isn't a remove or clear option with this collection; you may simply add to the collection and that's all. Here's an example of what the collection would look like:

Image 6

The <files> element is determined by the following code fragment in the MultiWatcherConfigurationSection class:

C#
[ConfigurationProperty("files",
    IsDefaultCollection=true, IsKey=false, IsRequired=true)]

The <add> element represents the FileElement type determined by the following code fragment in the FilesCollection class.

C#
[ConfigurationCollection(typeof(FileElement),
                         AddItemName="add",
                         CollectionType
                         = ConfigurationElementCollectionType.BasicMap)]

XSDExtractor uses this meta data and the expected XML structure to build an XML Schema that matches the XML structure. It also does it using a recursive algorithm which allows it to build XML Schemas that match ConfigurationSection classes with many nested and sub-nested types.

Limitations

There are two ways to create a custom ConfigurationSection class. The first way is the declarative model where each of the elements are decorated with ConfigurationProperty and ConfigurationCollection attributes. The second way is the programmatic model where the individual elements are added manually in code. Due to the way that XSDExtractor works, it will only create an XML Schema for a ConfigurationSection that uses the first model, the declarative model. If you mix and match the two models, then XSDExtractor will still work, but the XML Schema produced may not be accurate.

One more limitation is that the ValidatorAttributeParser class hierarchy only supports parsing of the standard validators that ship with .NET 2.0. If your ConfigurationSection contains a bespoke validation attribute, then XSDExtractor will not understand how to deal with it. The default behaviour under those circumstances is to return an unrestricted XmlSchemaSimpleType.

Summary

I hope that you find this utility useful and that it helps you write configuration files faster. If you want to see how I'm using the features of the XSDExtractor application within another application, then take a look at my JFDI project (a .NET 2.0 Job Framework) over at Sourceforge. Any feedback that you have is more than welcome.

History

  • 17th May, 2007
    • Article edited and posted to the main CodeProject.com article base

  • 24th September, 2006
    • Version 1.1.1 released
      Fixes: Enhancements:
      • Issue where a collection used more than once in a class would be incorrectly added to the schema more than once. Thanks to Idael Cardoso for correcting this issue and providing a class that demonstrated the issue. (Unit test added to recreate the bug / prove the fix works)
      • Nant compatible build script added to the project
  • 10th August, 2006
    • Version 1.1 released
      Fixes: Enhancements:
      • The /R switch now works correctly
      • StringValidatorAttribute's that have a blank list of invalid characters no longer cause a blank pattern element to be generated (which caused the XSD to be invalid)
      • xmlns properties are now ignored
      • Documentation is now added to the XSD. Standard information on the field type, the default value and whether the field is required or not is now displayed in a tooltip in the VS2005 XML editor. If the property is decorated with a System.ComponentModel.DescriptionAttribute, then the description is appended to the default information also.
      • Correctly handles default collections such as the HttpModules configuration section
      • Useful information is added as a comment to the XSD so that it is possible to see how the XSD was generated, who by and when
  • 1st August, 2006
    • Initial article

Licensing

The utility and source code are free to use and are released under the GNU Lesser General Public License. They are both released with the usual yada-yada about limitations of responsibility when using it, etc. If you find the utility / source code useful, then all I ask is that you give this article a mention somewhere in your app / blog / whatever!

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)