In the following blog post I will sample the creation of a custom WebUITypeEditor which will select roles from the default roles provider and return them as a string array. Then I will use this selector in a control derived from Generic Content Control in order to make it "secured" - it will hide its contents to users which do not belong to at least one of the selected roles. Before we start with the implementation I recommend that you first take a look at the following blog post and KB article in order to get a better idea how to build WebUITypeEditors and create controls deriving from Generic Content Control:
Creating a custom WebUITypeEditor
We will first start with the implementation of the RolesSelector. This clash should inherit WebUITypeEditor. What it will do is to populate the roles within the default role provider into a list box and allow users to select from them. Then it returns the selected roles in the form of a string array. First Lets Create the template for this control. We will place it in ~/Sitefinity/Admin/ControlTemplates/Selectors/RolesSelector.ascx, bellow is the sample markup:
<%@ Control Language="C#" %>
<
p
><
h2
>Select roles which will see the control's content.</
h2
></
p
>
<
asp:Label
AssociatedControlID
=
"RolesSource"
runat
=
"server"
ID
=
"label1"
Text
=
"Roles to select from:"
></
asp:Label
><
br
/>
<
telerik:RadListBox
runat
=
"server"
ID
=
"RolesSource"
Height
=
"200px"
Width
=
"200px"
AllowTransfer
=
"true"
TransferToID
=
"RolesDestination"
>
</
telerik:RadListBox
>
<
telerik:RadListBox
runat
=
"server"
ID
=
"RolesDestination"
Height
=
"200px"
Width
=
"200px"
/>
Now lets create our actual control. We will bind the first list box control to all roles coming from default role provider. If the view state of the control contains information of previously selected roles will will insert them in the second list box which will contain already selected roles. Sample code bellow:
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
Telerik.Cms.Web.UI;
using
Telerik.Web.UI;
using
System.Web.UI;
using
Telerik.Framework.Web;
using
Telerik.Security;
namespace
Sitefinity.Samples.WebControls
{
class
RolesSelector : WebUITypeEditor<
string
[]>
{
public
override
string
[] Value
{
get
{
IList<
string
> roles = (
new
string
[]{}).ToList();
foreach
(RadListBoxItem item
in
RolesDestination.Items)
{
roles.Add(item.Text.ToString());
}
return
roles.ToArray();
}
set
{
this
.ViewState[
"selectedRoles"
] = value;
}
}
private
string
layoutTemplatePath =
"~/Sitefinity/Admin/ControlTemplates/Selectors/RolesSelector.ascx"
;
public
string
Template
{
get
{
object
o =
this
.ViewState[
"Template"
];
if
(o ==
null
)
return
this
.layoutTemplatePath;
return
(
string
)o;
}
set
{
this
.ViewState[
"Template"
] = value;
}
}
protected
RadListBox RolesSource
{
get
{
return
this
.Controls[0].FindControl(
"RolesSource"
)
as
RadListBox;
}
}
protected
override
void
CreateChildControls()
{
base
.CreateChildControls();
userManger =
new
UserManager();
this
.template = ControlUtils.GetTemplate<SelectorTemplate>(Template);
this
.template.InstantiateIn(
this
);
RolesSource.DataSource = userManger.GetAllRoles();
RolesSource.DataBind();
if
(
this
.ViewState[
"selectedRoles"
] !=
null
)
{
IList<
string
> selectedRoles = (IList<
string
>)
this
.ViewState[
"selectedRoles"
];
RolesDestination.DataSource = selectedRoles;
RolesDestination.ItemDataBound +=
new
RadListBoxItemEventHandler(RolesDestination_ItemDataBound);
RolesDestination.DataBind();
}
}
void
RolesDestination_ItemDataBound(
object
sender, RadListBoxItemEventArgs e)
{
RadListBoxItem item = RolesSource.FindItemByText(e.Item.Text);
if
(item !=
null
)
{
RolesSource.Delete(item);
}
}
protected
RadListBox RolesDestination
{
get
{
return
this
.Controls[0].FindControl(
"RolesDestination"
)
as
RadListBox;
}
}
public
class
SelectorTemplate : ITemplate
{
#region ITemplate Members
public
void
InstantiateIn(Control container)
{
//throw new NotImplementedException();
}
#endregion
}
private
ITemplate template;
private
UserManager userManger;
}
}
The next step is to create the control which is going to use our RolesSelector. As in the KB article linked above we will create a custom control which inherits from Generic Content Control. Additionally we have to expose a property for setting selected roles and add some more logic to the Render method override. First lets add the property and get a reference of the div containing the actual generic content control:
/// <summary>
/// Gets or sets the roles which are allowed to see content of the cotnrol. We are going to use the RolesSelector for WebEditor
/// </summary>
[Browsable(
true
)]
[WebEditor(
"Sitefinity.Samples.WebControls.RolesSelector, Sitefinity.Samples"
),TypeConverter(
typeof
(Telerik.Framework.Utilities.StringArrayConverter))]
public
string
[] Roles
{
get
{
object
obj =
this
.roles;
if
(obj ==
null
)
return
new
string
[]{};
return
(
string
[])obj;
}
set
{
this
.roles = value;
}
}
/// <summary>
/// The div containing the generic content control
/// </summary>
[Browsable(
false
)]
public
HtmlGenericControl ContentWrapper
{
get
{
return
this
.Container.GetControl<HtmlGenericControl>(
"contentWrapper"
,
true
); }
}
In the method overriding Render we will check if the selected roles property is null and if not check against the current user's roles. If user does not belong to at least one of selected roles the user will not be able to see the content of the control:
protected
override
void
Render(HtmlTextWriter writer)
{
StringWriter content =
null
;
HtmlTextWriter contentWriter =
null
;
try
{
// if in normal mode, output the full content of the control
if
(!
this
.DesignMode)
{
// first, we need to "extract"
content =
new
StringWriter();
contentWriter =
new
HtmlTextWriter(content);
base
.Render(contentWriter);
this
.ContentPlaceholder.Text = content.ToString();
//check selected roles and set behavior accordingly
if
(
this
.roles !=
null
)
{
UserManager userManager =
new
UserManager();
MembershipUser currentUser = userManager.GetUser();
if
(currentUser !=
null
)
{
bool
userInRole =
false
;
foreach
(
string
role
in
this
.roles)
{
userInRole = userManager.IsUserInRole(role);
if
(userInRole ==
true
)
break
;
}
if
(userInRole ==
false
)
ContentWrapper.Visible =
false
;
}
else
{
ContentWrapper.Visible =
false
;
}
}
// we need to do this manually, as generic content control renders its
// content directly and does not inherit CompositeControl,
// so it will not render out template correctly (not at all, actually)
foreach
(Control child
in
this
.Controls)
{
child.RenderControl(writer);
}
if
(!
this
.Controls.Contains(
this
.Container))
{
this
.Container.RenderControl(writer);
}
}
else
{
// if in edit (design) mode, output the content of the genreric content control
// THIS IS REQUIRED
base
.Render(writer);
}
}
finally
{
if
(content !=
null
)
{
content.Dispose();
}
if
(contentWriter !=
null
)
{
contentWriter.Dispose();
}
}
}
You should note that overriding the render method is required when you are dealing with control inheriting from Generic Content Control. If you are going to create a custom control which inherits from SimpleControl for example you need to override CreateChildControls() and place the logic for hiding content there. If the user does not belong to selected roles you can simply clear the control collection - nothing will be displayed.
Sample project can be downloaded from this link: RolesSelector. The roles selector and custom generic content control are packed in a code library. After you add this to your solution you fix the assembly references in the Sitefinity.Samples project. Then add the Sitefinity.Samples project as a project reference in your Sitefinity website and build the Sitefinity.Samples project. Finally add the custom control to your toolbox, include this in your web.config:
<
toolboxControls
>
<
clear
/>
...
<
add
name
=
"Custom Generic Content"
section
=
"Sitefinity Samples"
type
=
"Sitefinity.Samples.WebControls.GCWrapper, Sitefinity.Samples"
/>
</
toolboxControls
>