Building a recipe application using Vista and .NET 3.0 (Part II: Schema Design and implementation)
In the last blog post I discussed the general design of the recipe application that we wanted. In this section I will discuss the XML format that we decided on.
I chose to use an XML format because now there are so many ways to consume/emit XML that it makes XML a very useful format. Still, there are still cases when XML is not optimal such as a vehicle for transport or for file size constraints, but when developing a prototype, XML is extremely flexible and useful.
Simple sample recipe
This is an obviously oversimplified example of a recipe, but it shows some of the main elements of the Food schema in use. There are a few competing recipe schemas in the world, so that is why I choose to have the parent node named “Food”, to avoid confusion with other recipe schemas. It also conveys the notion of it being more than just a recipe XML format. Note that I’ve chosen to represent all the tags in the XML exclusively as elements (with exception of the namespaces) rather than attributes, for consistency, readability, and to simplify the programming model (elements map well to class properties) though one could argue that it is a bit verbose.
<?xml version="1.0" encoding="utf-8"?>
<Food xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ThumbnailImage>\path to image\chocolate_milk.jpg</ThumbnailImage>
<Step>Pour milk in glass</Step>
<Step>Add Cocoa and stir</Step>
<Note>Chocolate milk is a great innovation!</Note>
Schema-tic v 0.5.4
I generated the below image (Fig. 1) of the “Food” schema by loading an instance of a recipe XML file using the XSD.exe that ships with Visual Studio.NET 2005. XSD.exe automatically generates an XSD file (XML Schema definition) from an XML file. I doctored the resulting file by setting the IsDataSet attribute (located at bottom of XSD file) to false. I then opened it up in Visual Studio and took a screenshot of it. Changing this setting has two desired effects. When generating a class XSD.exe will generate a standard class as opposed to a DataSet, also, Visual studio can draw the schema *much* faster when it is not a DataSet.
Fig. 1 – Food schema (version 0.5.4) relationships
Note: XSD.exe and DataSet’s do not support nested self-referencing of types. For example if an “Ingredients” element contains “Ingredient” elements plus a “Substitutes” element, the “Substitutes” element then cannot contain more “Ingredient” elements, since it is not supported.
C# and the XML Serializer model are more flexible than the DataSet and do not have this constraint. To be able to generate the diagram in Fig. 1 using XSD.exe, I had to generate the XSD file using a different name for the element to coax it into working. I simply named the “Ingredient” element “SubstituteIngredient”, generated the .xsd and then edited the created file by renamed it back to “Ingredient”. Also, Visual Studio properly displays nested self-referencing types!
As mentioned above, you can choose to have XSD.exe generate a class from the resulting XSD file; however, I rarely get the result that I’m seeking. The code that XSD.exe generates does not always create the XML format that I was expecting. As a result, I generally create the class by hand, but this requires a bit more knowledge about how to make a class serializable, and how to use attribute conventions appropriately. See the Food class in food.cs that maps to the schema in Fig. 1.
Here are the basic rules of thumb I go by:
- If you use exclusively elements in your XML, the properties exposed in the class do not need any attributes set!
- Everything in XML is considered a string, so if you want the data stored in the XML marshaled into a non-string type during serialization into a class, such as a float, you have to provide an appropriate attribute. (See the Quantity property for an example)
- Use properties and not fields. When it comes to reflection, fields are not discoverable. (The quick way to create properties is to use Visual Studio’s refactoring capabilities! Create a private variable name, such as: private string _note; right click on it and choose Refactor->Encapsulate Field from the menu)
- Use Collections instead of arrays. It really simplifies coding against the class, and they deserialize into XML identically. Also, no special attributes are necessary! (See the “StepCollection” class that implements the ICollection interface for an example)
- If you want an element that contains text at its root, mark a custom property with the XmlTextAttribute attribute. (see the “Step” class for an example) You will also need to Override the ToString() method on the class to return the custom property value.
In the next blog post I will cover how to take advantage of Windows Vista to enhance this new Electronic Cookbook file type that we have created.