.NET (296) administrative (41) Ajax (42) AngularJS (2) ASP.NET (145) bicycle (2) books (180) browser (8) C# (135) cars (1) chess (29) CodePlex (10) Coma (8) database (49) deployment (3) Entity Framework (2) essay (114) flash/shockwave (2) flex (1) food (3) friend (2) game (20) idea (5) IIS (8) javascript (83) LInQ (2) Linux (6) management (4) manga (43) misc (676) mobile (1) movies (92) MsAccess (1) murder (2) music (64) mysql (1) news (100) permanent (1) personal (68) PHP (1) physics (2) picture (309) places (12) politics (14) programming (507) rant (120) religion (3) science (43) Sharepoint (3) software (58) space (1) T4 (2) technology (12) Test Driven Development (4) translation (2) VB (2) video (98) Visual Studio (45) web design (46) Windows API (8) Windows Forms (3) Windows Server (6) WPF/Silverlight (63) XML (11)

Friday, February 26, 2010

ASP.Net using both inner properties and default innner properties

This post should have been titled Have your cake and eat it, too, but people need to find it in Google.

First of all, a bit of theory.

Web controls in ASP.Net can persist their properties in markup in two ways: attributes and nested tags. The problem with attributes is that the type of the property may be complex and iself have properties.

ASP.Net does have a mechanism for specifying subproperties by using a hyphen to separate properties and their subproperties. For example the BackColor property of a row of a GridView can be specified as both BackColor="Red" in the RowStyle tag or as RowStyle-BackColor="Red" in the GridView tag. However, that way is pretty unreadable and cumbersome that is why using inner tags is sometimes necessary (and implemented by decorating the properties with [PersistenceMode(PersistenceMode.InnerProperty)]).

But using inner tags is not always possible! Imagine a control like Panel. Its contents are interpreted as controls that are supposed to be rendered, so an inner tag would just be rendered to the page and not interpreted. There is no way of making such a control (decorated in its class declaration with [ParseChildren(false)]) have inner properties, but one can use something like an ITemplate property so that the controls can be placed in a tag of their own.

Also, consider controls like the DropDownList, in which ListItems are placed directly in the control tag and it is assumed automatically that they are children of the Items collection. That is because the Items collection is marked as [PersistenceMode(PersistenceMode.InnerDefaultProperty)] which means all the children of the control are considered elements in the property. Adding an inner tag would raise an error, as it would not be a ListItem object.

Yet, there is a way! And finding it was like discovering the holly grail. I have only tested it on a ListControl, but it should work on templated controls as well.

Let's start with a class that inherits from ListControl, therefore it has the Items collection in it and adding a complex property to it. I will create a special class called InnerTagClass that has a Text property. The control that inherits from ListControl and which I will call BaseClass will have an InnerTag property of type InnerTagClass. The code would look like this:

public class BaseClass:ListControl
{
private InnerTagClass mInnerTag;

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[NotifyParentProperty(true)]
public virtual InnerTagClass InnerTag
{
get
{
if (mInnerTag==null)
{
mInnerTag=new InnerTagClass();
}
return mInnerTag;
}
set
{
mInnerTag = value;
}
}
}
Now go into the designer and modify the Text property of InnerTag. You will notice that it will be saved as a hyphen attribute, while adding items to the Items collection would render the list items as children in the BaseClass tag:

<local:BaseClass ID="BaseClass1" runat=server InnerTag-Text="Test">
<asp:ListItem>Test</asp:ListItem>
</local:BaseClass>
Now, an InnerDefaultProperty is still an InnerProperty. You can put the ListItem inside an Items tag and it will still work, and also you can add an InnerTag tag with a Text attribute and it will work as well! However, if you go into the designer and you change the Text for the InnerTag property you get the changed property as an attribute and the previous InnerTag tag, with the old value. That is not acceptable.

However, you can inherit from the BaseClass, override the Items and the InnerTag properties and change their persistence mode to InnerTag! Here is the code:

public class InheritedClass : BaseClass
{
[PersistenceMode(PersistenceMode.InnerProperty)]
public override System.Web.UI.WebControls.ListItemCollection Items
{
get
{
return base.Items;
}
}

[PersistenceMode(PersistenceMode.InnerProperty)]
public override InnerTagClass InnerTag
{
get
{
return base.InnerTag;
}
set
{
base.InnerTag = value;
}
}
}
Now editing in the designer will create the Items tag and add the list items to it and an InnerTag tag with a Text attribute! But when you type it in source view, you get intellisense for both the Items tag and the ListItem tag! Adding only list items to the control works just like an inner default property.

There is a catch, though, as adding list items as direct children of the InheritedClass tag will not allow you to change the values of Items or InnerTag from the designer. It will save the current items in an Items tag, create the InnerTag, but fail to remove the previous items and thus throw a designer error. Still, fixing that is as easy as switching to source view and removing the old items.

Another possible question would be if one could add both InnerDefaultProperty and InnerProperty as decoration to a property in a class, but the answer is no. An error of Duplicate 'PersistenceMode' attribute is thrown.

Also, it might be possible to be able to fix that via a specialised editor, but I think it would be too complicated by far.

So there you have it: a way to satisfy both the styles of source people (like myself) and designer people by using inheritance and persisting differently the base property and its override.

No comments: