Roles Selector and hiding control based on user's roles

Roles Selector and hiding control based on user's roles

Posted on February 22, 2010 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

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>

progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation