SharePoint Designer 2007 is a ground breaking design tool to help information workers and designers create context rich composite applications without writing single line of code. SharePoint Designer enables many interesting customization and app building scenarios that might not have been originally envisioned by the control developers. Moreover the control preview and persistence mechanism is built on top of the Whidbey control designer framework which is inherently different from the ASP.Net runtime behaviors. That’s the reason sometimes control developers might be surprised to find out controls which work perfectly in browsers might not work at all in SharePoint Designers. Most of the time to fix the issue we need to tweak or redesign the server control in a way that is more compatible with the Whidbey and SharePoint design time framework.
This document tries to summarize some of the best practice of design time friendly server controls to help control developers avoid most common bugs when controls interact with SharePoint Designer. Actually all best practices listed below work well with Visual Studio design surface too so by following the practice we can potentially have the benefit of making our controls work better with VS design areas.
1: Just because a control works in browser doesn’t mean it’ll automatically work in SPD, and usually it’s not SPD or designer framework bug.
SharePoint Designer leverages Whidbey ControlDesigner framework to let WSS server renders control preview for SPD. This process is inherently different from runtime control rendering in many ways:
· When a control is being rendered for design time, the Page context is different from runtime. Many global variables that used to work in runtime doesn’t exist or even will throw uncaught exceptions if we’re not careful. Page.Request is one prime example.
· Control lifecycle is different. Default control designer framework doesn’t call OnPreRender and OnLoad. Only OnInit and Render are guaranteed to be called.
· Control tree structure might be different. Many times controls are added to designer Tree structure in a “flatted out” way, instead of the runtime hierarchical structure.
· In SPD users can update control property in property grid of SPD. This involves SPD sending a remote authoring command to SharePoint server, instantiate the control at server, setting the property, and then gather the markup out of the in memory control at server, and finally sending the markup and preview back to SPD client. This process involves “roundtrip” between markup and in-memory control, unlike runtime ASP.Net parsing which is only one-directional. Because of this complication, the control and its markup requires more from control developers in order for this roundtrip to work seamlessly.
· SPD users can easily drag & drop controls around design surface.
That’s the reason it’s highly recommended that developers verify server control preview fine in SPD and we can update control property through SPD property grid before check in.
2: Make sure Control is self-contained, and doesn’t contain unnecessary client side script.
Since SPD users can easily drag & drop controls around design surface, all server controls have to be self-contained to work in any SPD drop locations. There used to be a server control that spits out <td>content</td> and the markup looks like:
Obviously this control will generate invalid html once dragged out of the enclosing TR tag and thus needs to be modified to be more self-contained in order for this operation to make sense.
3: Use SPContext.Current.IsDesignTime or Control.DesignMode. Use them to protect dangerous Page.Request call.
For design time rendering, this.Page is not null since we do have design time Page object available. Unfortunately Whidbey will throw exception for Page.Request call in design time. To protect the exception, we can check SPContext.Current.IsDesignTime which is a global flag. If we’re inside a control object, we can also call the protected method Control.DesignMode to check if we’re in design mode. Those 2 flags are also helpful if control developers want to display special content for SPD design time, just do something like:
protected override void Render (HtmlTextWriter output)
//design time preview goes in here.
// runtime render code goes in here.
4. Be aware of design time control lifecycle. Use SPControlDesigner or DesignTimeHtmlProviderDesigner for your control if necessary to change control design time lifecycle.
Each server control will be assigned a default control designer by Whidbey. The default Whidbey ControlDesigner doesn’t call OnLoad or OnPreRender to generate control preview. So any server control that relies on logic in OnLoad or OnPreRender will most likely have issues in SPD. Control developers have potentially 2 options to solve the issue:
· Move the logic to OnInit or Render().
· Use a different designer than the default one chose by Whidbey.
To do the latter, control developers can simply add the following line of code as control class attribute:
Public class myControl : IDesignerEventAccessor,…
SPControlDesigner is a built in WSS designer which forces the control OnLoad and OnPreRender functions to called. It also makes sure control children expression binding process work, unlike the default ControlDesigner, and IDesignTimeHtmlProvider interface was honored by designer. Since OnInit and OnPreRender are both protected, control designer can’t directly call into those 2 functions. That’s the reason controls that use SPControlDesigner have to implement the trivial IDesignerEventAccessor interface and expose those 2 functions through the interface.
In SharePoint V2, we have IDesignTimeHtmlProvider interface to help FrontPage 2003 get control preview through a SOAP call. However that mechanism is deprecated in Office 2007 so all controls that use IDesignTimeHtmlProvider interface to create preview for SPD won’t really work in designer framework unless you either use SPControlDesigner, or more lightweight designer class DesignTimeHtmlProviderDesigner.
5. Use ParseChildren and PersistChildren attributes if appropriate to facilitate control and its markup roundtrip.
In runtime ASP.Net just parses control markup on the page and then instantiates. In SPD design time users can set control properties through SPD property grid, which involves the complicated process of control instantiation at the server, setting the property and then spit out the true control markup and preview. That whole process requires the control and the markup to “roundtrip”.
Class-level control attributes PersistChildren and ParseChildren dictate how a Web control reacts to any child content within its tags. Child content is the markup that appears between the starting and closing tags for a control's declarative syntax:
<asp:SomeWebControl runat="server" ...>
This content in here is considered the child content...
The child content can be interpreted in two ways:
- As property values - many built-in ASP.NET Web controls specify property values as child content. For example, the DataGrid class will specify style information, as well as column information as child content. This actual content maps to properties in the DataGrid class.
<asp:DataGrid runat="server" AutoGenerateColumns="False" ...>
<ItemStyle BackColor="Peach" Font-Name="Verdana" ...></ItemStyle>
<asp:BoundColumn ... />
- As children controls - the child content can be interpreted as controls that should be added to the Web control's control hierarchy. The Panel Web control, for example, has the HTML and Web controls that appear within it specified in its child content area:
<asp:Panel runat="server" ...>
What is your name?<br />
<asp:TextBox runat="server" ... />
To indicate which of these two mo7dels should be used, the PersistChildren and ParseChildren attributes are used. By default, the PersistChildren and ParseChildren have values of False and True. The PersistChildren attribute specifies whether Whidbey should persist the child content as children controls. The ParseChildren attribute indicates whether or not the child content should be parsed as properties. So, when PersistChildren is False and ParseChildren is True, the behavior is that inner XML content is treated as property values (again, this is the default behavior). If you want to child content to be treated as child controls in the control hierarchy, you need to explicitly set PersistChildren to True and ParseChildren is False, like so:
public class MyControl : WebControl
Because of this constraint, in general we can’t have mixed-up control child content--- it has to be either children controls or property values, otherwise markup and control roundtrip won’t work correctly.
6. Use PersistenceMode(PersistenceMode.InnerProperty) and [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] attributes, or TypeConverter if appropriate to facilitate control attribute serialization.
As we mentioned in the previous section, a control property can be persisted either as an attribute on the control tag markup, or as control child content. By default Whidbey and WSS designer framework will choose to persists any control property as an attribute on the control tag markup. This means the property text will be encoded inside the attribute. This might turn out ugly in SPD code view. For instance, the Xsl property for DataFormWebPart will be less than appealing if encoded as attribute. To make specific control property persisted as control child tag instead of encoded attribute, control developers can use property-level attribute PersistenceMode(PersistenceMode.InnerProperty) to notify framework.
Many times we have control or Webpart properties that are not of simple types like int, DateTime, Decimal, String, or Guid. Instead it is of complex type that can’t be directly serialized, like SPList. To roundtrip those control properties, control developers have to write its own TypeConverter class which converts between the complex type and string. In TypeConverter implementation we need to implement 4 override virtual functions:
public override bool CanConvertFrom(ITypeDescriptorContext td, Type t); // can the complex type be converted from input type t?
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType); //can the complex type be converted to input destinationType?
public override object ConvertFrom(ITypeDescriptorContext td, CultureInfo cultureInfo, object value); // convert from simple type value to the complex type.
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cultureInfo, object value, Type destinationType); //convert from complex type to simple type value.
Of course we might not need to do this at all, since the complex-typed control property might not need to be exposed to SPD at all. This can save all the trouble of creating a custom TypeConverter.
To do that, simply add the property level attribute [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
7. For advanced control designer developer, be aware of the design time control render peculiarly and control tree structure.
The default Whidbey ControlDesigner behaves unexpected to most runtime control developers in 2 ways:
1. The design time control tree structure is “flattened” by default. This means there’s no guarantee that parent of a control in the runtime is actually the parent in design time. Instead the parent could be a global stub control that is the parent of everything. To many controls this design time behavior is not desired. To control the parenting structure of control tree, developers can use DesignerRegion class to set the desired parent of controls inside the designer region. For more details, please see examples in DataFormWebPartDesigner class.
2. Designer framework calls “GetDesignTimeHtml” method on the ControlDesigner to render the control preview. This call usually happens before the page has finished loading and all controls in the page has been put into the control tree. This could cause problems for controls that need to interact with other controls in the page before doing the preview generation. There’s actually a way to delay the preview render after the page has been loaded in design time. The trick is this line:
SetViewFlags(ViewFlags.DesignTimeHtmlRequiresLoadComplete, true); It has superseded the obsolete virtual function DesignTimeHtmlRequiresLoadComplete in Whidbey.