Sitefinity CMS can be extended very easily using traditional ASP.NET UserControls. Once a UserControl is added to Sitefinity, Sitefinity will automatically generate a web admin interface.
In addition to the auto-generated interface, Sitefinity Control Designers can be used to create very user-friendly custom web-based interfaces:
Control Designers can be attached to an existing UserControl by adding a Telerik.Framework.Web.Design.ControlDesigner attribute:
using System;
[Telerik.Framework.Web.Design.ControlDesigner("CustomDesigner")]
public partial class Custom_UserControls_CustomControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
Custom Control Designers can be created by creating a new class and inheriting from the Telerik.Framework.Web.Design.ControlDesigner base class:
~/App_Code/CustomDesigner.cs
public class CustomDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
public CustomDesigner()
{
}
}
The Control Designer becomes responsible for rendering a web-interface and setting the underlying control properties.
Getting Access to the Underlying Control Properties
Control Designers have access to the underlying control via the DesignedControl property. However, this isn't as simple as it might seem. Consider the following UserControl:
~/Custom/UserControls/CustomControl.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CustomControl.ascx.cs" Inherits="Custom_UserControls_CustomControl" %>
<asp:Literal ID="LabelText" runat="server" />
~/Custom/UserControls/CustomControl.ascx.cs
using System;
[Telerik.Framework.Web.Design.ControlDesigner("CustomDesigner")]
public partial class Custom_UserControls_CustomControl : CustomControlBase
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; }
}
protected void Page_Load(object sender, EventArgs e)
{
LabelText.Text = Text;
}
}
There is 1 public property (Text) in this UserControl. You might be inclined to access this property from the Control Designer using the following code:
~/App_Code/CustomDesigner.cs
public class CustomDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
public override void OnSaving()
{
// THIS WILL NOT WORK!!!!
DesignedControl.Text = "Hello world!";
}
}
The DesignedControl property is a System.Web.UI.Control property. This type does not contain our custom Text property. In order to access this custom property we need to cast DesignedControl to a type that contains the custom Text property.
ASP.NET UserControls are compiled dynamically when accessed. If the UserControl is never accessed, then the control is not compiled and is not part of the web application's namespace. This makes it difficult (impossible?) to cast DesignedControl as type (CustomControl.ascx).
The solution to this problem is the creation of a Base Class. Base Classes allow us to bridge the gap between pre-compiled classes and dynamically compiled ASP.NET UserControls. We can place this Base Class in the ~/App_Code folder. App_Code is a special ASP.NET folder that is compiled at runtime.
Here is an example ~/App_Code/CustomControlBase.cs class:
public class CustomControlBase : System.Web.UI.UserControl
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; }
}
}
Notice this Base Class inherits from the System.Web.UI.UserControl class. Since this Base Class extends UserControl the User Control can be modified to inherit from this class.
Here is the revised ~/Custom/UserControls/CustomControl.ascx.cs:
using System;
[Telerik.Framework.Web.Design.ControlDesigner("CustomDesigner")]
public partial class Custom_UserControls_CustomControl : CustomControlBase
{
protected void Page_Load(object sender, EventArgs e)
{
LabelText.Text = Text;
}
}
The Text property has been removed from this User Control and will now be inherited from the CustomControlBase base class. Since CustomControlBase sits in the ~/App_Code folder and is compiled & loaded at runtime, we can now cast DesignedProperty as type (CustomControlBase).
~/App_Code/CustomDesigner.cs:
public class CustomDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
public override void OnSaving()
{
((CustomControlBase)DesignedControl).Text = "Hello world!";
}
}
The OnSaving method is called automatically when the I'm done button is clicked.
Test this code; it all works! Your Control Designer will be empty, but clicking I'm done will set the underlying Text control property to "Hello world!".
Creating a Web Interface for the Custom Control Designer
Control Designers can be treated just like Composite Controls. The CreateChildControls() method can be overridden and child webcontrols added:
using System.Web.UI.WebControls;
public class CustomDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
protected override void CreateChildControls()
{
TextBox _textbox = new TextBox();
_textbox.ID = "TextBox1";
Controls.Add(_textbox);
}
}
The code above does very little except add a TextBox to the Control Designer. It would take additional work to make this TextBox do something or display correctly.
This being said, Sitefinity 3.6 has introduced some enhancements that make this easier than ever.
Here is a quick example that uses an external UserControl as a Control Designer Template:
~/App_Code/CustomDesigner.cs
using System.Web.UI.WebControls;
public class CustomDesigner : Telerik.Framework.Web.Design.ControlDesigner
{
public override void OnSaving()
{
((CustomControlBase)DesignedControl).Text = TextBox1.Text;
}
public override string LayoutTemplatePath
{
get
{
return "~/Custom/Admin/ControlDesigners/CustomDesigner.ascx";
}
}
protected virtual TextBox TextBox1
{
get { return base.Container.GetControl<TextBox>("TextBox1", true); }
}
protected override void InitializeControls(System.Web.UI.Control viewContainer)
{
TextBox1.Text = ((CustomControlBase) DesignedControl).Text;
}
}
~/Custom/Admin/ControlDesigners/CustomDesigner.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CustomDesigner.ascx.cs" Inherits="Custom_Admin_ControlDesigners_CustomDesigner" %>
<div class="ctrlProps">
<div class="ctrlContent">
<h3>Text to be displayed</h3>
<p class="news-location">
<asp:Label AssociatedControlID="TextBox1" Text="Enter text below:" runat="server" />
<asp:TextBox ID="TextBox1" Columns="40" runat="server" />
</p>
</div>
</div>
I'll explain this code in detail in my next blog post.