Zoek

Uitgebreid zoeken Artikelen per auteur

  

Fixing the IExtenderProvider in Visual Studios ASP.NET designer

Introduction

Provider controls are a design time method of extending existing controls on the designer surface based on the IExtenderProvider interface. Visual Studio .NET 2003 also supports the IExtenderProvider interface in its Windows Forms designer. It however does not properly support the use of the IExtenderProvider in its ASP.NET designtime environment.

Properties set on an IExtenderProvider are serialized into code statements in the codebehind file. Figure 1 displays the type of code which should be present for a fictitious ASP.NET ToolTip provider. This code is not generated correctly, resulting in a codebehind file resembling Figure 2. Because the code isn’t generated, it will not be compiled. Thus the logic provided by the IExtenderProvider isn’t present at runtime.

ToolTipExtender Extender1;
Button MyButton;
private void InitializeComponent()
(
  this.Load += new EventHandler(this.Page_Load);
  this.Extender1.SetToolTip( MyButton, "SomeTooltip");
}

Figure 1 - Required code

ToolTipExtender Extender1;
Button MyButton;
private void InitializeComponent()
{
  this.Load += new EventHandler( this.Page_Load);
}

Figure 2 - Actual code

This article proposes a method to remove Visual Studio's limitations towards the IExtenderProvider in its ASP.NET designer. A limited understanding of the IExtenderProvider interface is required for proper understanding of this article.

Perhaps one of the reasons why the IExtenderProvider isn’t supported in Visual Studio's ASP.NET designer can be derived from a report on their feedback centre. This report can be found at Microsoft.

Behind the scenes of the designer

In order to solve the problem with the IExtenderProvider, first a basic understanding needs to be achieved of what's going on behind the scenes of the Visual Studio designer. Visual Studio offers two basic methods of editing a Page or UserControl, one is a direct view of the HTML and codebehind file, the other is the visual designer. The designer displays the HTML file after the server controls in the HTML file have been compiled. This means the designer contains an object graph corresponding with the items declared in the codebehind file. When a property is changed on one of the components on the designer surface, either the HTML file is updated to reflect that change, or the corresponding object is updated. When you switch from the designer to the code view, the objects in the designer are serialized into code statements, which are placed in the 'Web Form Designer Generated Code' section. If you make changes in the code file and switch back to the designer, the code is deserialized into an object graph again (sometimes you’ll need to refresh the designer in order to see the changes made in the codebehind file). When switching to and from the designer a process takes place called serialization. CodeDom forms an intermediate layer in this serialization process. The CodeDom provides various types corresponding with common code elements and offers classes to perform operations on CodeDom. The following figure displays how CodeDom is used in the process of switching to and from the designer.

Figure 3 - Background process when switching between design- and code view

Examining the problem

Now that it’s clear what is going on between the Visual Studio designer and the source code that it designs, it can now be determined what the problem with the IExtenderProvider might be. It will be interesting to see which part of the IExtenderProvider isn’t functioning properly. Figure 3 shows that there are two different processes to examine, each consisting of two steps. The first process, switching from code view to the designer, entails parsing the code and deserializing the parsed statements into objects. The second process, switching back to the code view, consists of serializing the objects into CodeDom and generating code from those CodeDom statements.

The code accompanying this article contains a broken ToolTip provider which can be utilized to examine the problem. The provider is able to extend Button classes and should be placed on a Page or UserControl containing a Button in order to test with it.

Switching from code view to the designer

The first problem to examine is switching from the code view to the designer. The Button and ToolTip should be on the Page and the Button should not have a ToolTip set.

Using the code view, a line of code is inserted to the designer section calling the SetToolTip method for the Button. When the switch from the code view to the design view is made, the text set using the code view can be observed in the properties of the Button. This means the deserialization process is functioning correctly and doesn’t need a fix

If you are unsure this method actually works, try changing the background color of the Button in the codebehind file. Switch to the designer and refresh the page to prove the correctness of this method.

Switching from designer to the code view

The second test is easy enough, just by setting a text on the ToolTip property of the Button and switching to the code view, it can be observed that the code for the ToolTip isn’t generated. This part will have to be addressed by the fix. Notice that there are two parts to the serialization process; Building a CodeStatementCollection using a CodeDomSerializer and generating source code from the collection using a CodeGenerator.

The IExtenderProvider requires a method call to set one of the provided properties. This makes it unlikely that the CodeGenerator is causing the problems. Since the CodeGenerator is tied to a language and used throughout the .NET Framework, it will always be able to write method calls corresponding with a CodeStatement.

Extending the Visual Studio designer

When extending the designtime environment of Visual Studio using a custom CodeDomSerializer two things have to be addressed; building the serializer and attaching it to an IExtenderProvider component. Let’s run through both things.

A generic CodeDomSerializer

The CodeDomSerializer which will support Visual Studio will have to be generic enough to be applied to all IExtenderProvider components. This means no knowledge of the names and types of properties is available. This knowledge has to be obtained by using Reflection.

A subclass from CodeDomSerializer is required to implement two methods; Serialize and Deserialize. The Serialize method receives an object to serialize into a CodeStatementCollection. The Deserialize method does the opposite, creating an object from the statements in the collection. Since the Serialize method is broken, let’s tackle the Deserialize method first.

Deserializing an IExtenderProvider

This method doesn’t need exhaustive work, because the original Deserialize method isn’t broken. The base class declares the Deserialize method as abstract, so it will have to be implemented.

Because IExtenderProvider components are placed on a Page or UserControl at designtime, the provider has to be a subclass of Component. There is a derived CodeDomSerializer specialized for serializing Components. Using the objects passed into the Deserialize method, a reference to a CodeDomSerializer for the Component class can be obtained. Given the fact that building a custom CodeDomSerializer is not something which is required often, the CodeDomSerializer for the Component class will be sufficient in ninety-eight percent of the cases. Figure 4 displays the code required to call the Component serializer.

public override object Deserialize(
  IDesignerSerializationManager manager,
  object codeDomObject)
{
  CodeDomSerializer baseSerializer =
    (CodeDomSerializer)manager.GetSerializer(
      typeof(Component),
      typeof(CodeDomSerializer));
  return baseSerializer.Deserialize(manager, codeDomObject);
}

Figure 4 - Implementation of the Deserialize method

Serializing an IExtenderProvider

The serialization process will take more steps than deserialization. Since the IExtenderProvider could provide properties to every component on the designer surface, it is required to run through each component. Lets start with creating the first part of the required steps, the overridden Serialize method. Figure 5 displays this method.

Because this serializer should only be applied to IExtenderProvider components verification is made to ensure the serializer is applied correctly. Next the CodeDomSerializer for the base class is used to serialize all the normal properties which the IExtenderProvider may contain. This leaves only the extended properties for the customized serialization process. A reference to the collection of components on the designer surface can be obtained using the service model; the IDesignerHost service contains a reference to these components.

public override object Serialize(
  IDesignerSerializationManager manager,
  object value)
{
  if(!(value is IExtenderProvider)){
    throw new ArgumentException();
  }

  CodeDomSerializer baseSerializer =
    (CodeDomSerializer)manager.GetSerializer(
      value.GetType().BaseType,
      typeof(CodeDomSerializer));

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

  try
  {
    CodeStatementCollection statements =
      (CodeStatementCollection)codeObject;
    IDesignerHost host =
     (IDesignerHost)manager.GetService(
       typeof(IDesignerHost));
    ComponentCollection components = host.Container.Components;

    SerializeExtender(
      manager, (IExtenderProvider)value, components, statements);
  }
  catch(Exception ex){}
  return codeObject;
}

Figure 5 - Implementation of the Serialize method

The SerializeExtender method needs to make sure whether a specific property / component combination needs to have a code statement serialized. The method is displayed in Figure 6.

void SerializeExtender(
  IDesignerSerializationManager manager,
  IExtenderProvider provider,
  ComponentCollection components,
  CodeStatementCollection codeObject)
{
  ProvidePropertyAttribute[] properties = GetProvidedProperties(provider);
  foreach(IComponent component in components)
  {
    if(provider.CanExtend(component))
    {
      foreach(ProvidePropertyAttribute attribute in properties)
      {
        object currentValue =
          ReflectionHelper.GetCurrentValue(
            provider, attribute, component);
        bool hasDefault =
          ReflectionHelper.HasDefaultValue(
            provider, attribute);
        object defaultValue =
          ReflectionHelper.GetDefaultValue(
            provider, attribute);
        if(!hasDefault || Object.Equals(defaultValue, currentValue) == false)
        {
          CodeExpression exp =
            CreateExpression(
              manager, provider,
              attribute, component,
              currentValue);
          codeObject.Add(exp);
        }
      }
    }
  }
}

Figure 6 - Implementation of the SerializeExtender method

The first action taken in this method is running through each component and verifying if the provider can extend them. The provider contains a convenience method for this purpose; called CanExtend. When it is possible to extend the component, all the provided properties are serialized depending on the default value of the property. Comparing the default and actual value uses the Object.Equals method. Instead of Object.Equals it is possible to use the == operator. However, this results in the wrong comparison being made. The == operator returns true when the left and right hand side point to the same object instance, not when they hold the same value. An integer property for instance, when comparing two boxed integers the == operator will yield false, but Object.Equals yields true when the integers are the same value.

When it has been determined that the property has no default value, or the actual value differs from the default one, the property / component combination needs to be serialized into a CodeStatement. This is when the final part of the solution comes in to play. Behold the CreateExpression method.

CodeExpression CreateExpression(
  IDesignerSerializationManager manager,
  IExtenderProvider provider,
  ProvidePropertyAttribute attribute,
  IComponent component,
  object currentValue)
{
  CodeExpression targetObject =
    base.SerializeToReferenceExpression(
     manager, provider);

  CodeMethodInvokeExpression methodCall =
    new CodeMethodInvokeExpression(
      targetObject, "Set" + attribute.PropertyName);

  methodCall.Parameters.Add(
    CreateReferencingExpression (
      manager, component));

  methodCall.Parameters.Add(
    CreateReferencingExpression (
      manager, currentValue));
  return methodCall;
}

Figure 7 - Implementation of the CreateExpression method

Since it takes a method call to set a property on an IExtenderProvider, the CodeMethodInvokeExpression has to be used. This expression requires a CodeDom reference to the object on which to call the method as well as the name of the method to call.

The reference to the target object can be obtained using the CodeDomSerializer base class, which provides convenience methods for this purpose. The IExtenderProvider will have to be a Component in order to be dropped on the designer; a reference expression is therefore in order.

Referencing expressions result in a bit of code starting with the ‘this’ pointer, e.g. this.myComponent. This type of expression can be applied to reference types. Value types such as a struct or enum take a different method of serializing. The Color.Black or BorderStyle.3D value can not be referenced using the ‘this’ pointer. An exception to this rule is the String class. The String is a reference type, but should be serialized in the same way as primitives such as the Integer or Character. The actual value of the string needs to be serialized and not a reference to an instance of String.

The name of the method to call can be derived from the name of the property. The documentation of the IExtenderProvider states that the property name should be prefixed with the ‘Set’ string in order to create the name of the method.

The final requirement for building a correct method call in CodeDom is populating the parameter list of the new expression. A convenience method has been created in order to create the right type of CodeDom expression for the type of object.

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;
}

Figure 8 - Implementation of the CreateReferencingExpression method.

Binding the serializer to an IExtenderProvider

All that it takes to use the new serializer is the DesignerSerializer attribute. Using this attribute the serializer used for the component can be specified. The final code example displayed in Figure 9 shows how the attribute should be applied.

[ProvideProperty("ToolTip", typeof(Button)),
DesignerSerializer(typeof(ASPExtenderSerializer), typeof(CodeDomSerializer))]
public class WorkingProvider :
  Component,
  IExtenderProvider
{
}

Figure 9 - An IExtenderProvider which uses the new serializer.

Conclusion

The IExtenderProvider is a useful interface for extending other controls on the designer surface. The code demonstrated in this article provides a solution to remove the shortcoming of the Visual Studio .NET IDE. Through the use of a custom CodeDomSerializer a proper method of handling the limitations of the Visual Studio IDE has been found.

Downloads

Commentaar van anderen:
bags op 9-7-2010 om 8:34
We can buy anything we like Loewe replica, including copies replica Louis Vuittonof chanel bag and hermes copies in our desk handbag don't leave theirMarc Jacob replica homes. We can enjoy shopping our favorite Marni handbags reproductions, replica Montblancwe stayed in replica movado we comfortable room. We don't need replica Patek Philippe to brave the harsh winter weather, drive long distances, replica Dior handbags. The life replica Dolce & Gabbana handbagsreally simplified network. All the products replica Dooney & Bourke handbags we need will be Dior handbags delivered in us.the shop to another person gets Dolce & Gabbana handbags the best deal. We can find a lot of quotation and productsDooney & Bourke handbags in many online store discounts.
replica handbag op 16-7-2010 om 9:23
When itdesigner handbags comes to forking over yourhermes replica handbags hard earned cash, you want to make surecartier replica handbags that you A)don't pay more thanMulberry replica you have to, but B) you don'tBreitling for sale pay for something that is notswiss watches worth its value. Pay attentionLongines replica to the details. Your handbag Maurice Lacroix replicashould carry the same Louis Vuitton replicaquality on the inside as itMaurice Lacroix replica does on the outside.fendi leather handbags If the outside material looks passablechristian dior handbags for the real thing, but the inside materialcheap burberry handbags looks less than perfect, you've gotMiu Miu replica a knockoff. Check that the hardware looks as high quality omega for saleas the rest of the purse. If it looksreplica handbags less cheap, doesn't functionswiss watches properly, carries a off odor or Cartier for saleisn't aligned correctly, it's more than likely a fake.ysl handbags Another tell tale sign is chopard for salethe stitching. Crooked, loose or poorCheap Handbags stitching means its not the real deal.
replica watches op 29-7-2010 om 11:13
There are different variations, like bottega veneta replica, a medium-to-small-sized replica gucci handbags with a short handle, designed to be carried (clutched) in one's hand. A larger mulberry leather bag with two handles is often called a tote. A bottega veneta replica is similar to a thomas wylde. A security d&g handbags protects the carrier from travel theft. The chloe handbags replica includes an invisible stainless steel strap sewn into the fabric and a protectant on the main zipper. Tote replica coach handbags have been used a lot, especially in the latest years. A tote bag is a large handheld bag or purse that is used to carry things, such as books, beach wear, or everyday items. A tote bag is normally made of treated canvas, nylon or heavy pebbled leather. Some totes come with a zipper compartment that divides the burberry handbags into sections. The term tote, meaning "to carry" can be traced back to the 17th century but was not used to describe dolce gabbana handbags until 1900. Several chains of supermarkets offer reusable designer handbags, often made of cotton, as an alternative to paper or plastic shopping juicy couture replica. These are often labeled tote fendi leather handbags and sometimes also canvas tote cheap burberry handbags to highlight their durability. Also messenger chloe handbags replica are very popular. A messenger bag is a type of sack, usually made out of some kind of cloth (natural or synthetic), that is worn over one shoulder with a strap that winds around the chest resting the bag on the lower back.
Geef feedback:
Verzend Commentaar