Click here to Skip to main content
15,886,422 members
Articles / Web Development / ASP.NET

Fixing the IExtenderProvider in Visual Studio's ASP.NET designer

Rate me:
Please Sign up or sign in to vote.
4.67/5 (20 votes)
4 Jan 200510 min read 68K   817   37  
An implementation of a custom CodeDomSerializer for repairing the IExtenderProvider in Visual Studio's ASP.NET designer.
namespace ASPExtenderSample
{
	#region [===== Using =====]
	using System;
	using System.CodeDom;
	using System.ComponentModel;
	using System.ComponentModel.Design;
	using System.ComponentModel.Design.Serialization;
	#endregion
	
	/// <summary>
	/// A <see cref="System.ComponentModel.Design.Serialization.CodeDomSerializer"/> which is able
	/// to serialize an <see cref="System.ComponentModel.IExtenderProvider"/> in a ASP.NET application.
	/// </summary>
	public class ASPExtenderSerializer : CodeDomSerializer
	{	
		#region [===== Public instance methods =====]
		/// <summary>
		/// Deserializes the specified serialized CodeDOM object into an <see cref="System.ComponentModel.IExtenderProvider"/>.
		/// </summary>
		/// <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
		/// <param name="codeDomObject">A serialized CodeDOM object to deserialize.</param>
		/// <returns></returns>
		public override object Deserialize(IDesignerSerializationManager manager, object codeDomObject)
		{
			// just get the serializer of the base class and use that, it is sufficient.
			CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(
				typeof(Component), typeof(CodeDomSerializer));
			CodeStatementCollection col = codeDomObject as CodeStatementCollection;

			return baseSerializer.Deserialize(manager, codeDomObject);
		}
		
		/// <summary>
		/// Serializes the specified <see cref="System.ComponentModel.IExtenderProvider"/> into a CodeDOM object.
		/// </summary>
		/// <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
		/// <param name="value">The <see cref="System.ComponentModel.IExtenderProvider"/> to serialize.</param>
		/// <returns>A CodeDOM object representing the <see cref="System.ComponentModel.IExtenderProvider"/> that has been serialized.</returns>
		public override object Serialize(IDesignerSerializationManager manager, object value)
		{
			// Only serialize IExtenderProviders
			if(!(value is IExtenderProvider))
			{
				throw new ArgumentException("ASPExtenderSerializer not applied to IExtenderProvider.");
			}
			
			// First let the base class serializer do its work.
			CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(
				value.GetType().BaseType, typeof(CodeDomSerializer));

			object codeObject = baseSerializer.Serialize(manager, value);

			try
			{				
				// Get the container with statements
				CodeStatementCollection statements = (CodeStatementCollection)codeObject;
				// And the components on the designer surface
				IDesignerHost host = (IDesignerHost)manager.GetService(typeof(IDesignerHost));
				ComponentCollection components = host.Container.Components;
				// Now serialize the stored extender
				SerializeExtender(manager, (IExtenderProvider)value, components, statements);
			}
			catch(Exception ex)
			{	
				// An error occured, display to the user
				string msg = "An error occured in the ASPExtenderSerializer:" + Environment.NewLine +
					ex.Message;
				ApplicationException appEx = new ApplicationException(msg, ex);
				manager.ReportError(appEx);				
			}
			return codeObject;
		}
		#endregion

		#region [===== Private instance methods =====]
		/// <summary>
		/// Serialize the <see cref="System.ComponentModel.IExtenderProvider"/> which is stored
		/// as an instance field.
		/// </summary>
		/// <param name="components">The components for which the extender might provide 
		/// properties.</param>
		/// <param name="codeObject">The collection of statements to add the 
		/// created statements to.</param>
		/// <param name="provider">The IExtenderProvider which is being serialized.</param>
		/// <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
		void SerializeExtender(IDesignerSerializationManager manager, IExtenderProvider provider, ComponentCollection components,
			CodeStatementCollection codeObject)
		{
			// TODO A quicker parsing method than 2 foreach statements?
			ProvidePropertyAttribute[] properties = GetProvidedProperties(provider);
			// Run all components
			foreach(IComponent component in components)
			{
				// Verify if extension is possible for this component.
				if(provider.CanExtend(component))
				{
					// Run all provided properties
					foreach(ProvidePropertyAttribute attribute in properties)
					{
						// Get the current value, if a default value is present 
						// we need to compare if they are not equal, only then
						// can we serialize the property into an expression.
						object currentValue = ReflectionHelper.GetCurrentValue(provider, attribute, component);
						bool hasDefault = ReflectionHelper.HasDefaultValue(provider, attribute);
						object defaultValue = ReflectionHelper.GetDefaultValue(provider, attribute);
						// We need to use Object.Equals because == returns a value which indicates
						// whether the two are the same object, not if they have the same value.
						if( !hasDefault || Object.Equals(defaultValue, currentValue) == false)
						{
							// Create the expression and add it to the container used by the extender.
							CodeExpression exp = CreateExpression(manager, provider, attribute, component, currentValue);
							codeObject.Add(exp);	
						}
					}
				}
			}
		}

		/// <summary>
		/// Returns an array of the properties provided by an IExtenderProvider.
		/// </summary>
		/// <param name="provider">The provider to return the provided properties for.</param>
		/// <returns>An array of the properties provided by <paramref name="provider"/>.</returns>
		ProvidePropertyAttribute[] GetProvidedProperties(IExtenderProvider provider)
		{
			return (ProvidePropertyAttribute[])Attribute.GetCustomAttributes(
				provider.GetType(), typeof(ProvidePropertyAttribute), false);
		}

		/// <summary>
		/// Serializes the value of a provided property into a CodeDom statement.
		/// </summary>
		/// <param name="attribute">The property to serialize into a statement.</param>
		/// <param name="component">The component to serialize the value for.</param>
		/// <param name="currentValue">The current value of the provided property.</param>
		/// <param name="provider">The IExtenderProvider which is being serialized.</param>
		/// <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
		/// <returns>The serialized expression.</returns>
		CodeExpression CreateExpression(IDesignerSerializationManager manager, IExtenderProvider provider, 
			ProvidePropertyAttribute attribute, IComponent component, 
			object currentValue)
		{
			/* The statement has the following structure:
			 * <provider>.<setMethod>(<control>,<value>)
			 * For this we need to serialize a method call with 2 parameters,
			 * which will be called on the provider.
			 * */
			
			// 1: Serialize a reference to the provider
			CodeExpression targetObject = base.SerializeToReferenceExpression(manager, 
				provider);

			// 2. Create the method call.
			CodeMethodInvokeExpression methodCall = new CodeMethodInvokeExpression(targetObject, "Set" + attribute.PropertyName);
			
			// 3. Create a reference to the object for which the property 
			//    is provided as the first parameter
			methodCall.Parameters.Add(CreateReferencingExpression(manager, component));
			// 4. Create a reference to the new value of the 
			//    property as the second parameter 
			methodCall.Parameters.Add(CreateReferencingExpression(manager, currentValue));
			// Return the constructed expression
			return methodCall;
		}

		/// <summary>
		/// Creates a CodeExpression referencing a value, taking into account that 
		/// the value can be of different types which need referencing in a specific way.
		/// </summary>
		/// <param name="manager">A serialization manager interface that is used during the deserialization process.</param>
		/// <param name="value">The value to reference in CodeDom.</param>
		/// <returns>A CodeDom expression referencing <paramref name="value"/>.</returns>
		CodeExpression CreateReferencingExpression(IDesignerSerializationManager manager, object value)
		{
			Type currentType = value.GetType();
			CodeExpression refExpression = null;
			if(currentType.IsValueType || value is String)
			{
				refExpression = base.SerializeToExpression(manager, value);
			}
			else
			{
				refExpression = base.SerializeToReferenceExpression(manager, value);
			}
			return refExpression;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer Code Counsel
Netherlands Netherlands
Wouter van Vugt is a Microsoft MVP with Office Open XML technologies and an independent consultant focusing on creating Office Business Applications (OBAs) with SharePoint, the Office 2007 system and related .NET technologies. Wouter is a frequent contributor to developer community sites such as OpenXmlDeveloper.org and MSDN and has published several white papers and articles as well a book available on line titled Open XML: the markup explained. Wouter is the founder of Code-Counsel, a Dutch company focusing on delivering cutting-edge technical content through a variety of channels. You can find out more about Wouter by reading his blog and visiting the Code-Counsel Web site.

Comments and Discussions