Sitefinity ASP.NET CMS
Skip Navigation LinksSupport / Knowledge Base / How to wrap a Generic Content control in 3.6 SP1

How to wrap a Generic Content control in 3.6 SP1

Note!

Since 3.6 SP1 hotfix it is possible to wrap GeneriContent in a user control.

Note!
This article is specific to 3.6 SP1. There is a version specific to 3.6 hotifx.

Note!
Please note that this is going to be included in the Developer's Manual. As of now, it is not part of the official documentation, however.

Note!
Make sure you read the FAQ section in the end of the article.


Since there have been quite a lot of questions about this in the support tickets, and the documentation might take some time to be upgraded, I decided to show how to wrap a Generic Content control.

First of all, in the existing documentation, an obsolete method is suggested. I advise you not to use it, as it causes a lot of problems.

Here is what has to be done at a glance:
  • Create a custom control that inherits GenericContent
  • Add template support to it
  • Add custom functionality
  • Override Render
In this example I will add a very basic functionality on top of the GenericContent control, as the purpose of this article is to show how to accomplish this, not to be a showcase.

Note!
In previous versions of Sitefinity, it was impossible to correctly preview the real contents of an GenericContent inheritor due to limitations of GenericContentDesigner. As of 3.6 SP1, this is no longer the case. You should know, however, that GenericContentDeisgner relies on RenderBinaryContent() and RenderTextContent(), which are defined as virtual methods. You should override these only if you know what you are doing.

Create a custom control that inherits GenericContent

Let us first determine our goal: we want to use the GenericContent control designer, but we don’t want to have simple text only. What we need is to add some functionality on top of the basic GenericContent. Here, I will show how to:

  • use an embedded template
  • reuse the functionality of the GenericConent control, and
  • plug in the contents of a control in arbitrary place in our template

Now that we have our goals set, we need to examine our base class more closely – namely, the GenericContent control.

GenericContent inherits directly from WebControl and implements the IEmptyControl interface. Since GenericContent was designed to hold only formatted text, it doesn’t support Embedded Templates, and uses the Render method to directly write its content. While there is nothing wrong with this approach, we need to be aware of this limitation if we are going to inherit from GenericContent and extend it. Implementers of IEmptyControl can write an arbitrary message (including html) when the control holds no data (which is determined by the IEmptyControl.IsEmpty property implementation) in design mode.

The attributes that are applied to GenericContent are:  ControlDesignerAttribute and ToolboxItemAttribute. They are the key to achieving the goals we set before. The specified GenericContentDesigner provides the familiar interface for editing a GenericContent control: entering text in RadEditor, sharing the written content and using already shared content. Since it works only with GenericConent classes, and there is no way to circumvent it (including overriding conversion operators), we will inherit from GenericContent.You can omit ControlDesignerAttribute if you want, as it is iheritable. The second attribute is crucial and required, though.  It specifies the type of the control that will be used once it is dragged from the toolbox and dropped onto a ContentPlaceholder. Since it is inheritable, we need to re-apply it and specify the type of our class, or the page editor will generate a GenericContent control instead.

Here is what our code will look like so far:
 
/// <summary> 
/// Demonstrates how to wrap a Generic Content control and use the full functionality 
/// of the base class. 
/// </summary> 
[DefaultProperty("ContentTitle")] 
[ToolboxItem(typeof(GCWrapperToolboxItem))] 
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")] 
public class GCWrapper : GenericContent 
… 
  
    /// <summary> 
    /// This message will be displaye when the control is initially dragged 
    /// onto the design surface 
    /// </summary> 
    /// <returns>a message to display when there is no content in the control</returns> 
    public override string SetEmptyControlDefaultMessage() 
    { 
        return "This is a GenericContent wrapper! Please enter some content!"
    }          
  
    … 
}  

/// <summary> 
/// This will determine what will be shown in the toolbox 
/// </summary> 
public class GCWrapperToolboxItem : System.Drawing.Design.ToolboxItem 
    /// <summary> 
    /// Initializes the toolbox item with the proper type 
    /// </summary> 
    /// <remarks> 
    /// The section under which the control is initially placed is determined by the 
    /// [assembly: AssemblyCompany("Telerik")] attribute. 
    /// Of course, it can later be changed to something else throuh web.config 
    /// </remarks> 
    public GCWrapperToolboxItem() 
        : base(typeof(GCWrapper)) 
    { 
        base.DisplayName = "Generic Content Wrapper"
        base.Description = "Custom control that describes how to wrap a Generic Content control."
    } 


Adding embedded templates support

The goal of having embedded templates (or templates at all) is to simulate a user control behaviour. Another nice point about this is that there is a very clear separation between logic and UI. If you don't need to add some custom and rather static content you want to place around the generated text, you can safely skip this section. What you care most about is the technique used in Render.

Normally, you will inherit either from SimpleControl, ViewModeControl<T> or ViewModeUserControl<T>, and therefore – have support for embedded templates out of the box. If this were the case, you would only need to override LayoutTemplateName and LayoutTemplatePath and apply an EmbeddedTemplateAttribute (or derivative) to the latter. The template name would hold the relative path to the template file (see where our template is found in the solution explorer and compare it to the value of the GCWrapperLayoutTemplatePath constant). The attribute would hold the same relative path plus some descriptive information. LayoutTemplatePath holds the current url of the template, if it is not embedded. In most cases, you would use the base implementation and apply EmbeddedTemplateAttrubute.

Note!
Modules in Sitefinity implement inheritor attributes of EmbeddedTemplateAttribute, and are marked as internal. This is done to facilitate localization. If you pass resouce file keys instead of string literals, these can be easily translated. The same applices to all Microsoft classes that have either DescriptionAttribute (commony called WebSysDescription), or EmbeddedTemplateAttribute (commonly called WebSysTemplate).

Since we have to inherit from GenericControl, which doesn’t inherit from either of the above mentioned classes, we need to implement this by ourselves. Here comes another drawback from inheriting from WebControl, and not from CompositeControl. If we overrode CreateChildControls, it would never be called!

These are the properties that are needed for embedded templates support:

  • Container
  • LayoutTemplate
  • LayoutTemplatePath
  • LayoutTemplateName

Two of those we already discussed. Let us explain the two remaining: LayoutTemplate is to eventually return an ITemplate implementer, and the Container will hold the template data (the template will be initialized in the container).

LayoutTemplate has to be marked with TemplateContainerAttribute, which defines the type we will use for our Container properly – namely, an IGenericContainer implementer.

Container is used to load controls from the embedded template.

Note!
Since CreateChildControls is never called, and the Container has to be part of the Controls collection, we will render it directly in the Render override.

Here is what our very simple template looks like:

<h2 class="gc_wrapper_title">                
    <asp:Literal ID="title" runat="server" />     
</h2>   
  
<div class="gc_wrapper_content"
    <asp:Literal ID="content" runat="server" />   
</div> 


The first label, title, is a custom title that will be displayed above the content. The second label, content, is used to hold the contents of the generic content control.

Note that it is important to compile this file as an embedded resource. To do so:

  • Open solution explorer
  • Navigate to the template file
  • Right-click and choose Properties from the context menu
  • Set  Build Action to Embedded Resource

In this step we add the following code:

#region layout properties 
  
/// <summary> 
/// Used for loading controls from a template (defines the layout) 
/// </summary> 
[Browsable(false)] 
protected virtual GenericContainer Container 
    get 
    { 
        if (this.container == null
        { 
             this.container = new GenericContainer(); 
             if (this.LayoutTemplate != null
             { 
                 this.LayoutTemplate.InstantiateIn(this.container); 
             } 
        } 
        return this.container; 
    } 
  
/// <summary> 
/// Gets or sets the template defining the layout of the control. 
/// </summary> 
[TemplateContainer(typeof(GenericContainer))] 
[PersistenceMode(PersistenceMode.InnerProperty)]

[Browsable(false)] 
[DefaultValue(typeof(ITemplate), "")] 
public virtual ITemplate LayoutTemplate 
     get 
     { 
         if (this.layoutTemplate == null
         { 
            this.layoutTemplate = ControlUtils.GetTemplate(this.LayoutTemplatePath, 
                this.LayoutTemplateName, this.GetType());                      
         } 
         return this.layoutTemplate; 
     } 
     set 
     { 
         this.layoutTemplate = value; 
     } 
          
          
/// <summary> 
/// Gets or sets the path to a custom layout template for the control. 
/// </summary> 
[Browsable(true)] 
[Category("Appearance")] 
[Description("Gets or sets the path to a custom layout template for the control.")] 
[EmbeddedTemplate(GCWrapper.GCWrapperLayoutTemplatePath, "Layout template path for GC wrapper""/Templates/"true"2009/3/31")] 
public virtual string LayoutTemplatePath 
    get 
    { 
        object persistedValue = this.ViewState["LayoutTemplatePath"]; 
                  
        if (persistedValue != null
        { 
            return (string)persistedValue; 
        } 
        else 
        { 
            return String.Empty; 
        } 
    } 
    set 
    { 
        this.ViewState["LayoutTemplatePath"] = value; 
    } 
  
/// <summary> 
/// Gets the name of the embedded layout template. This property must be overridden to provide the path (key) to an embedded resource file. 
/// </summary> 
[Browsable(false)]          
public virtual string LayoutTemplateName 
   get 
   { 
       return GCWrapper.GCWrapperLayoutTemplatePath; 
   } 

#endregion 

#region constants 
  
public const string GCWrapperLayoutTemplatePath =  "DidoTestControls.Resources.Templates.GC_WrapperTemplate.ascx"

#endregion 

#region fields 
  
/// <summary> 
/// Holds the value of the LayoutTemplate property 
/// </summary> 
private ITemplate layoutTemplate; 
          
/// <summary> 
/// Holds the value of the Container property 
/// </summary> 
private GenericContainer container; 

#endregion  

 
Add custom functionality

We have already added the functionality by adding the title label in the template, so we simply load the two labels from the template and add another to control them. And set the default property for this class.

Here is what the code looks like:

#region properties 
  
/// <summary> 
/// The text of the title above the content area 
/// </summary> 
[Browsable(true)] 
[Category("Appearance")] 
[Description("The text of the title above the content area")] 
[TypeConverter(typeof(StringConverter))] 
public string ContentTitle 
     get { return this.ContentTitleText.Text; } 
     set { this.ContentTitleText.Text = value; } 

#endregion 
        
#region control references 
  
/// <summary> 
/// The title above the content 
/// </summary> 
[Browsable(false)]          
public ITextControl ContentTitleText 
    get { return this.Container.GetControl<ITextControl>("title"true); } 
  
/// <summary> 
/// The wrapped generic content control 
/// </summary> 
[Browsable(false)]          
public ITextControl ContentPlaceholder 
    get { return this.Container.GetControl<ITextControl>("content"true); } 

#endregion  

Override Render

In Render, we have to do the following things:

  • Render the template and the two labels
  • Render the contents of the base (GenericContent) to a string
  • Plug in the contents of the generic control in the content label

Everything is explained so far, so let the code speak for itself:

/// <summary>  
/// Called to write the actual html for this control  
/// </summary> 
/// <param name="writer">a stream that is specialized in wrtinig html output</param>  
protected override void Render(HtmlTextWriter writer) 
    using (StringWriter content = new StringWriter()) 
    { 
        using (HtmlTextWriter contentWriter = HtmlTextWriter(content)) 
        { 
            // render the content into a string 
            base.Render(contentWriter); 
 
            // plug in the content into the placeholder 
            this.ContentPlaceholder.Text = content.ToString(); 
 
            // we need to do this manually, as generic content control does  
            // not inherit CompositeControl, so its child controls won't  
            // render before Render is called 
            foreach (Control child in this.Controls) 
            { 
                child.RenderControl(writer); 
            } 
            // CreateChildConrols is never called, so Container is not made a child there. Therefore, it is rendered manually 
            // alternatively, we could add it to the Controls collection in the 
            // Container property initialization code and skip this last line. 
            this.Container.RenderControl(writer); 
        } 
    } 


How do we extract the contents of the generic content control? There is more than one way, but the chosen one here is to render it to a string, then plug in the rendered output as the content label’s text.

How do we render the template? Well, we render all child controls and then the layout (the Container).

Conclusion

This is a very basic example, which leaves actual customization of the looks of the control as an exercise for the reader. If you are not familiar how to apply css to Sitefinity pages, watch this web cast.

FAQ
  • Why should I care about wrapping GenericContent?
    Because GenericContent is used everywhere. Suppose you want to implement BBCode for your forums. Since the back-logic expects GenericContent, and your control inherits it, the type conversion will work. All you wil have to do is register your control in the appropriate templates and replace the GenericContent with your custom control.
  • I don't want to wrap GenericContent. I want to wrap XXX instead.
    The technique shown here can be applied to any  non-composite custom control. Write the content to a string, add your custom functionality, write the string to something else, and the magic is done.
  • And what if XXX is a composite control?
    In such case, it would already use template for its UI (be it embedded or not), and would be inheritable, so it would be even easier to directly add your custom functionality without touching Render.
  • I can edit only a small part of my control in the Basic tab. This is inconvenient. How can I overcome this?
    Then you can either implement your own control designer, or inherit GeneriContentDesigner and add the bits and pieces you need. You will need to get the extracted embedded templates in order to get its template file (.ascx) and modify it.

 

Related Links:

3.6 Dev Manual Preview

Article Info

Article relates to 3.6 SP1
Created by Dilyan Rusev
Last modified by Dilyan Rusev
Related categories: Controls

About Telerik

Telerik, the publisher of Sitefinity CMS, is a leading vendor of ASP.NET AJAX, ASP.NET MVC, Silverlight, WinForms and WPF controls and components, as well as .NET Reporting and .NET ORMTFSCode Analysis and Web Application Testing tools. Building on its solid expertise in interface development and Microsoft technologies, Telerik helps customers build applications with unparalleled richness, responsiveness and interactivity. Created with passion, Telerik products help thousands of developers every day to be more productive and deliver reliable applications under budget and on time. Read more about Telerik

Copyright © 2002-2010 Telerik. All rights reserved. Powered by Sitefinity