Tim Van Wassenhove

Passionate geek, interested in Technology. Proud father of two

17 Aug 2007

Exploring CodeDomSerializer

Sometimes we want absolute control over the code that the visual studio designer generates. Imagine that we have a UserControl with a Number property and instead of the default “this.userControl1.Number = 27;” code that the designer would generate we want it like “this.userControl1.Number = 1 + 3 + 23”. In order to achieve this we first have to inform the designer that we want custom serialization. This is done by adding a DesignerSerializerAttribute to our UserControl

[DesignerSerializer(typeof(PrimeSerializer), typeof(CodeDomSerializer))]
public partial class UserControl1 : UserControl
{
	private int number;

	public int Number
	{
		get { return this.number; }
		set { this.number = value; }
	}

	// ...
}

And now it’s time to implement the PrimeSerializer for the custom assignment code

public class PrimeSerializer : CodeDomSerializer
{
	public override object Serialize(IDesignerSerializationManager manager, object value)
	{
		UserControl1 uc = value as UserControl1;
		if (uc == null) { return null; }

		// Instead of implementing all the serialization code, we'll rely on the implementation of the baseclass, namely UserControl
		CodeDomSerializer baseClassSerializer = manager.GetSerializer(typeof(UserControl1).BaseType, typeof(CodeDomSerializer)) as CodeDomSerializer;
		Object codeObject = baseClassSerializer.Serialize(manager, value);

		// The only thing we have to do is find the statement where the assigment to the Number property is made, and replace that...
		CodeStatementCollection codeStatements = codeObject as CodeStatementCollection;
		CodeAssignStatement numberAssignmentStatement = this.FindNumberCodeStatement(codeStatements) as CodeAssignStatement;
		numberAssignmentStatement.Right = new CodeSnippetExpression(GetNumberAsSumOfPrimes(uc.Number));

		return codeObject;
	}

	private CodeStatement FindNumberCodeStatement(CodeStatementCollection codeStatements)
	{
		foreach (CodeStatement codeStatement in codeStatements)
		{
			CodeAssignStatement codeAssignment = codeStatement as CodeAssignStatement;
			if (codeAssignment != null)
			{
				CodePropertyReferenceExpression left = codeAssignment.Left as CodePropertyReferenceExpression;
				if (left != null && left.PropertyName == "Number")
				{
					return codeStatement;
				}
			}
		}

		throw new Exception("The CodeStatement for Number was not found");
	}

	private static string GetNumberAsSumOfPrimes(int number)
	{
		StringBuilder sb = new StringBuilder();

		int[] primes = new int[] { 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101 };
		for (int i = primes.Length -- 1; i >= 0 && number > 0; --i)
		{
			if (primes[i] <= number) 
			{ 
				sb.Insert(0, primes[i]); sb.Insert(0, " + "); 
				number -= primes[i]; 
			} 
		} 
		return sb.ToString().Substring(3); 
	} 
}

And now we can look at the generated code in Form1.Designer.cs to verify everything works as expected

//
// userControl1
//
this.userControl1.BackColor = System.Drawing.Color.Maroon;
this.userControl1.Location = new System.Drawing.Point(50, 23);
this.userControl1.Name = "userControl11";
this.userControl1.Number = 1 + 3 + 23;
this.userControl1.Size = new System.Drawing.Size(686, 294);
this.userControl1.TabIndex = 0;

I’ll leave the implementation of the Deserialize method up to you. By adding the DesignerSerializer attribute to our IExtenderProvider implementations we can get full control over their code generation too 🙂