This is a continuation of our previous discussion about the beauty and wonder that is xsd.exe. Now that we're all masters at creating auto-generated classes from XML schemas (or WSDLs), there are some very neat advanced tricks that we can leverage to make our code even cleaner and more efficient.
Tip 1: Adding Methods
Astute and inquisitive developers may have noticed something interesting about the classes that xsd.exe generates. Take a look at the generated version of our class that we created last time:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.1432")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public partial class People {
Notice something there? I spy with my little eye... something blue. In fact, the something I'm so excited about is the meat in a blue-words sandwich: partial. Yes, xsd.exe marks all the classes it generates as partial. Coincidentally, if you look at that link, you'll see that Microsoft envisioned this exact application for partial classes; they cite this as a reason for creating them:
"When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio uses this approach when creating Windows Forms, Web Service wrapper code, and so on. You can create code that uses these classes without having to edit the file created by Visual Studio."
Neat, huh? So why do we care?
What if we wanted a super-simple way to get a Person's first and last name as one string, and we don't want to store that string in XML, since we already have the first and last name separately:
<Person>
<FirstName>Casey</FirstName>
<LastName>Liss</LastName>
However, we don't have that in code:
So now what? We take note of the fact that PeoplePerson is a partial class and make a new file to extend it. In that new file, we add a FullName property:
namespace XmlDemo
{
partial class PeoplePerson
{
[System.Xml.Serialization.XmlIgnore]
public string FullName
{
get
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(firstNameField))
{
sb.Append(firstNameField);
}
if (!string.IsNullOrEmpty(lastNameField))
{
if (sb.Length > 0)
{
sb.Append(" ");
}
sb.Append(lastNameField);
}
return sb.ToString();
}
}
}
}
So now we have a convenient way to get a person's full name. However, notice that there's a hidden gem in there:
[System.Xml.Serialization.XmlIgnore]
This attribute indicates to the serializer that it is not supposed to look at that property for either serialization nor deserialization. If you don't include that attribute, and serialize a PeoplePerson, you'll suddenly have a new element in the resultant XML, which doesn't match your schema. That element will be FullName. Including the XmlIgnore attribute prevents the serializer from doing anything with that property.
Naturally, you can also add methods. Those do not need any special attributes, because methods are already ignored by the serializer. You can even take it one step further and add private fields, additional constructors (you must leave the default constructor), etc.
I used this technique recently for a project wherein we, in essence, were storing a dictionary in XML. When we read the XML, I added a Dictionary<TKey, TValue> as a private field. I then added a LoadHash() method which would load that dictionary with values. Finally, I added a GetValue(TKey key) method to get the value out of the dictionary. This dramatically increased the speed of my application, because I was simply plucking values out of a dictionary.
However, using auto-generated classes also dramatically increased the speed of development. There were several times that I needed to add fields to the schema, and thanks to my auto-generated classes, I was a build away before those fields were automatically represented in my classes.
Tip 2: Source Control
One of the problems with using the pre-build event that we created last time is that you need to have write access to the CS file that's being auto-generated every time you build. That doesn't typically play well with source control, because this forces every developer to have every auto-generated file perpetually checked out. Naturally, this gets even more dicey when dealing with exclusive checkouts.
Instead, what I advise may sound slightly strange at first: don't check the CS file into source control. When adding the project to source control, undo the pending change for the CS file, as shown below:
Undoing the pending change (which is a pending add) will cause the CS file to remain outside source control. Once you've submitted the addition, your solution explorer should look as such:
Now, since the file is outside of source control, it can remain writable for every user. Thus, during build time, there is no need to check the file out, since every developer has their own writable file.
This has the caveat of meaning one (or sometimes two, as mentioned previously) builds are necessary to get a new developer on board. However, the saved frustration during the normal development process is well worth it.
For Further Reading
It is worth noting that the XmlSerializer does have a small performance penalty. Under the hood, when the serializer is first asked to handle a type, it actually dynamically inspects that type and generates code to perform the serialization and deserialization at runtime. In the case of stupidly large XML schemas (in the order of tens of thousands of lines) that code generation step can take literally a couple of minutes.
However, as described in this link, there is a companion tool, xmlser.exe, that you can use to pre-generate and thus pre-compile this code for you. This cuts down on the time required during execution to create and compile all that code.
In Summary
So, there you have it, a couple of useful and convenient tips to keep you happy with your auto-generated classes. Have any other tips you've come across or invented? Please leave a comment!
ICF Ironworks is always on the lookout for experienced professionals who believe in hard work, having fun, and great client service.
Great article, Casey! I should have read this article yesterday. Thanks for stopping by though to emphasize that I really should never touch the generated code.
Posted by: Anna Uong | 10/27/2010 at 11:25 AM