I <3 Steve McConnell*
Coding Horror
programming and human factors
by Jeff Atwood

August 10, 2006

The Last Configuration Section Handler.. Revisited

If you need to store a little bit of state-- in your configuration file, or on disk-- nothing is faster than some quick and dirty serialization. Or as I like to call it, stringization.

In late 2004, I wrote about The Last Configuration Section Handler, which does exactly this for *.config files. It's based on earlier work by Craig Andera of Pluralsight. Let's bring that code up to date for Visual Studio 2005, and furthermore, we'll do it in C# and The Language of the Gods, VB.NET.

The first thing to do is set up a little class that represents the data you want to serialize. Include whatever types you need, but make everything public so it'll be visible to the serializer.

namespace SomeNamespace
{
    
public class MyStuff
    {
        
public int i;
        
public string s;
    }
}

Now use this routine to serialize it:

static string SerializeObject(object o)
{
    
StringBuilder sb = new StringBuilder();
    
StringWriter sw = new StringWriter(sb);
    
XmlTextWriter xtw = new XmlTextWriter(sw);
    
xtw.Formatting = Formatting.Indented;
    
xtw.WriteRaw(null);

    
XmlSerializerNamespaces xsn = new XmlSerializerNamespaces();
    
xsn.Add("", "");

    
XmlSerializer xs = new XmlSerializer(o.GetType());
    
xs.Serialize(xtw, o, xsn);
    
string s = sb.ToString();

    
// <Foo> becomes <Foo type="MyClass.Foo">
    s = Regex.Replace(s, "(<" + o.GetType().Name + ")(>)", "$1 type=\"" + o.GetType().FullName + "\"$2");
    
return s;
}

The output is your class, serialized as a nice human-readable string.

<MyStuff type="SomeNamespace.MyStuff">
  <
i>1234</i>
  <
s>A bunch of information</s>
</
MyStuff>

It's just so darn.. straightforward. As if I needed another reason to love strings. Anyway, take that string and paste it into your web.config file.

To read it in, you'll need a custom config section. Paste this into your config file to define one:

<configSections>
  <
section name="MyStuff" type="XmlSerializerSectionHandler, CSSerializerSection" />
</
configSections>

The actual XmlSerializerSectionHandler is a bit too much code to paste into a blog post, but it's still relatively simple:

  1. Extract the type from the XML Type attribute
  2. Make sure the type is valid
  3. Deserialize the XML into a new object of that type

The XmlSerializerSectionHandler is too verbose to reprint here mainly because I added a bunch of error trapping. If something goes wrong, you get a nice explanatory exception instead of a cryptic error. It's good stuff.

There's almost no difference at all between the two languages, except that VB for some reason requires an additional namespace; instead of "SomeNamespace.MyStuff", it's "VBSerializerSection.SomeNamespace.MyStuff".

Posted by Jeff Atwood    View blog reactions
« Fitts' Law and Infinite Width
Sometimes It's a Hardware Problem »
Comments

"The Language of the Gods, VB.NET"

Oh no you di'int.

-D

Dan Esparza on August 11, 2006 4:03 PM

Tish and pish, how about in the language of the proletariat, PHP:

$string=serialize( $my_array );

...

$my_array=deserialize($string);

:-P

RichA on August 12, 2006 12:37 AM

Here is an intensive course of serialization with Ruby. Note that the XML serialization could be further improved. :-)

require 'cl/xmlserial'
require 'yaml'

a = %w(aaaa bbbbbb ccccccccccc) # Array

# String
s = Marshal.dump(a)
a_copy = Marshal.load(s)

# XML
xml = a.to_xml.to_s
a_from_xml = Array.from_xml(REXML::Document.new(xml).root)

# YAML
yaml = a.to_yaml
a_from_yaml = YAML.load(yaml)


class Person
include XmlSerialization
attr_accessor :name
end

o = Person.new
o.name = 'Joe'

# String
s = Marshal.dump(o)
o_copy = Marshal.load(s)

# XML
xml = o.to_xml.to_s
o_from_xml = Person.from_xml(REXML::Document.new(xml).root)

# YAML
yaml = o.to_yaml
o_from_yaml = YAML.load(yaml)

Nx on August 12, 2006 7:02 AM

VB.NET may not be the language of the Gods, but it's certainly the language of love. There, I said it.

Burton on August 12, 2006 7:15 PM

VB.NET has something called a root namespace, which is different from the C# default namespace. Whenever a root namespace is defined (in project properties) it's added behind your back.

I have no idea why anyone would want this, or why it is that way. It's strange to have something added invisibly in a language which force us to write code about the size of a book just to get a read-only property.

Thomas Eyde on August 12, 2006 7:58 PM

Yeah, I reference the root namespace every time I have to summon up an embedded resource. Big deal.

VB.NET is the Language Of Love!!!

Love. <3

Allow me to reiterate. Assembly (and, to a lesser extent, C and its' variants) is the language of love for the computer. It affords every convience for the lowest level, for the compiler, and makes people bend to its' will. Similarly, the LOL (Language of Love, VB.NET), bends over backwards to make itself readable to humans. This means that is is easier for LOL (VB.NET) programmers to write code that is more self-documenting. To write code which resembles the syntax of the English lanuage. VB.NET is the language of love for the developer!

The true virtue of any language these days is not speed or brevity. It's readability and maintainability. VB.NET leverages the same framework as C#, but in a way that allows the developer to name things in such a way that his code is self explanatory. And that's worth a thousand semicolons and two thousand curly braces.

And that's love for the person typing in the code. And the friend that's reading it.

LOL!!

Burton on August 13, 2006 12:37 AM

I still think those public fields just look wrong wrong wroooong...

Daniel Lehmann on August 13, 2006 3:11 AM

As of .NET 2.0, there's no need to use this approach for configuration handlers - just use the new attribute-based (or object model) approach.

The key attribute is ConfigurationPropertyAttribute and its MSDN docs illustrate nicely how to use them:

http://msdn2.microsoft.com/en-us/library/system.configuration.configurationpropertyattribute.aspx

This new method even allows validation of your config properties.

Hehe, but I still use the old method in any 1.1 apps :)

Jarrod Dixon on August 13, 2006 3:06 PM

If you try to serialize objects this way, you're going to run into security problems. The app.config file is stored in the same folder as the executable which is usually somewhere in the Progrm Files folder. Standard users do not have write access to Progrm Files.

Jeff M on August 14, 2006 4:57 AM

.NET 2.0's config classes/attributes are great for basic persistence. Not so great for deep-nested data structures or for producing truly human-readable XML, and then there's the security issue.

Using proxy classes as in this post is generally a good way to tackle the issue because they are easy to serialize. It's also pretty much the only way to transport data over a Web Service in a form readable by non-.NET apps. One could also write a small persistence class library, if one were so inclined; 2.0's Generics make life a lot easier in that respect.

As for the VB.NET sub-discussion, simply because the syntax is closer to plain English than C# does not make it any more readable or maintainable. Verbosity may facilitate understanding for some but is a distraction to others. And I'm not a C# evangelist - my primary language is Delphi, which is similar to VB in a lot of ways, but I "love" all of those languages equally because I am "fluent" in all of them, and I think that if people would bother to learn other tools beyond 10-minute tutorials then they might be a little less stuck-up about the tool of their choice. Ruby and Python, for example, give orders of magnitude more "love" to developers than any of the .NET languages, in some areas at least.

Aaron G on August 14, 2006 9:20 AM

Here's a good... uh, arguable (dodging bullets?) metric to fan some flames in the CS v. VB war:
5K < 10K
I challenge your Language of the Gods with my Language of the Verbose.

Groucho on August 14, 2006 11:56 AM

I think all ideas which produce discussion are great.
NB: I would use generics - we will gain some performance and make code look more elegant.

Laboremus on August 15, 2006 5:37 PM

You must also consider that the class might have overriden the root element name for the class (XmlRoot).

Therefore, add this code:

// <Foo> becomes <Foo type="MyClass.Foo">
Type t = o.GetType();
string name = t.Name;
object[] attributes = t.GetCustomAttributes(typeof(XmlRootAttribute), false);
if (attributes != null && attributes.Length > 0)
{
XmlRootAttribute xmlRoot = attributes[0] as XmlRootAttribute;
name = xmlRoot.ElementName;
}
s = Regex.Replace(s, "(<" + name + ")(>)", "$1 type=\"" + t.FullName + "\"$2");

Michael Pelikan on August 29, 2006 3:44 PM

You have to be crazy to use regex's for Xml Serialization... here it is in four lines:


public string Serialize(object obj)
{
using(StringWriter sw = new MemoryStream())
{
XmlSerializer xs = new XmlSerializer(obj.GetType());
xs.Serialize(sw, obj);
return sw.ToString();
}
}

If you want to change the name of fields or objects in the XML you simply apply attributes to them:

[XmlElement("Foo.MyStuff")]
public class MyStuff
{
[XmlAttribute("WhateverIWant")]
public int i;

[XmlAttribute]
public string s;
}


The reason why PHP serializes more simply is because XML is only one type of serialization available to .NET you can also (for instance) serialize to a byte[] or to JSON by using the above code with simply using different formatters. Also you should check out Windows Communication Foundation and how to make a DataContract... very cool stuff.

Justin Chase on September 19, 2006 7:06 PM

Still brilliant; I can't count how many times I've used this. Also, I believe you may use:

New XmlSerializer(t, New XmlRootAttribute(section.Name))

...to remove the dependancy of the element name to class name (even if root is renamed or redeclared or attribute sprinkled - thanx 4 the hint Michael P) .

(You may also consider encoding the xml values to UTF8 first)

Thanks Jeff, Craig, Michael & Jef N.

PS: I'm also posting this here so I don't forget :)

Diesel on December 12, 2006 5:58 AM






(no HTML)


Verification (needed to reduce spam):


Content (c) 2009 Jeff Atwood. Logo image used with permission of the author. (c) 1993 Steven C. McConnell. All Rights Reserved.