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:
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:
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:
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:
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:
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