Working extensively with Roles in Webforms.

The example below have different scenarios with roles, at various core levels, like –
Role based functionalities in a given page
Role based pages
Role based folders
Role based UserControls

To begin with, I created a simple forms-based website which uses the in-built membership api to work with roles. Using the WSAT (Website Administration Tool), I created the following roles —  Admin, Manager, Owner, Support, user.  My test website accepts authenticated users, belonging in one of the above groups. An example, as how the user “Tom” belongs to 2 roles — Manager as well as Owner.

tom
web.config

<authentication mode="Forms">
    <forms cookieless="UseCookies" defaultUrl="~/HomePage.aspx" loginUrl="LoginStuff/login.aspx" enableCrossAppRedirects="false" protection="All" timeout="30">
    </forms>
</authentication>
<membership defaultProvider="GMemberShipProvider" >
    <providers>
        <add name="GMemberShipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="Gconnectionstring" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="3" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression=""/>
    </providers>
</membership>
<roleManager enabled="true" cacheRolesInCookie="true" cookieName="TROLES" defaultProvider="GRoleProvider">
    <providers>
        <add connectionStringName="Gconnectionstring" applicationName="/" name="Demo_RoleProvider" type="System.Web.Security.SqlRoleProvider, System.Web,  Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
    </providers>
</roleManager>

<authorization>
  <allow roles="Admin,Manager,Owner,user,Support"/>
  <deny users = "*"/>
</authorization>

If you see the “Solution Explorer”, I have created various folders like —  AdminStuff, PublicStuff, RoleSensitiveStuff, RoleSensitiveUserControls.

img1
1) AdminStuff –>  Only users under “Admin” role can access any page under it. Since I know this upfront, I just added a web.config in that folder, which makes sure that only admin roles can access it. Hence on a folder level, applying roles is quite easy.
<?xml version=”1.0″?>
<configuration>
    <system.web>
      <authorization>
        <allow roles=”Admin”/>
        <deny users=”*” />
      </authorization>
    </system.web>
</configuration>

2) Very similar, I have another folder called “PublicStuff”. Any page/contents under that folder does not need any kind of authentication. You type-in that aspx page name in browser and that page shows up. Again, since this is known during design phase, a simple web.config in that folder will take care of it.
<?xml version=”1.0″?>
<configuration>
    <system.web>
      <authorization>
        <allow users=”*”/>
      </authorization>
    </system.web>
</configuration>

3) Now consider the folder “RoleSensitiveStuff”.  Here we would protect 2 kind of things.
3.1) -> Any aspx page which we know, can be accessed only by certain roles.
3.2) -> Inside a given aspx page, certain functionalities/actions which are role-based and NOT the complete page. 

3.1) First let’s see — how to work with an aspx page which is totally role dependent. Here’s my ManagerOnlyPage.aspx. Only authenticated users belonging to “manager” role can access it. If any other user tries to load that page, they get redirected to “UnAuthorized.aspx”. I used a base page class to help me out here. My ManagerOnlyPage.aspx is derived from a BasePage class. In the basePage class, I check, if the incoming authenticated used belongs to a certain role. If yes, render that page which is asked for — else redirect him to “UnAuthorized.aspx”.The advantage of using a base page class here is, if in future, I need to add more such role dependent pages, I would add/extend all the role related logic at one central place.
here’s my base page class

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    if (this.Page.ToString().ToLower().Contains("manageronlypage_aspx"))
    {
        //Easy way to detect and redirect
        if (!Roles.IsUserInRole(RoleName.Manager.ToString()))
            Context.Response.Redirect("~/UnAuthorized.aspx");
           
        //Another more generic way of doing the same thing
        if (!IsAuthorizedToView())
            Context.Response.Redirect("~/UnAuthorized.aspx");

        return;
    }        
}

private bool IsAuthorizedToView()
{
    DataTable dt = DataLayer.DL_GetRolesByAction("ManagerOnlyPage");
    foreach (DataRow dr in dt.Rows)
    {
        if (Roles.IsUserInRole(dr["RoleName"].ToString())) return true;
    }
    return false;
}

In the base page class, there are 2 ways to redirect the user. In the first method, I just check if the user is in “Manager” role and then redirect appropriately. The second method is little different. Currently my page “ManagerOnlyPage.aspx” should be accessible by managers only. Say in future I may want to make it accessible for “Admin” role too. In order to do that, without modifying any code, I needed to create a mapping table in Membership database, which maps roles with do-able functionalities/actions. Then it gets dynamic, so in future, if I want to add “Admin” role , which can also see that page, all I need to do is add another record in DB and the logic will take care of it.

mapping

public static DataTable DL_GetRolesByAction(string _ActionName)
{
SqlDataReader reader = null;
DataTable dt = new DataTable();
using (SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["Gconnectionstring"].ConnectionString))
{
    cn.Open();
    SqlCommand cmd = new SqlCommand();
    cmd.CommandText = "GetRolesByAction";
    cmd.CommandType = CommandType.StoredProcedure;
    cmd.Parameters.AddWithValue("@ActionName", _ActionName);
    cmd.Connection = cn;
    reader = cmd.ExecuteReader();
    dt.Load(reader);
}
return dt;
}

3.2) In RoleSensitiveFunctionality.aspx page , certain functionalities/actions are role dependent and not the complete page. I added the role dependant functionalities under different panels, and then based on my mapping table in membership database, I show/hide appropriate functionality/Action.

<asp:panel ID="Panel1" runat="server">
    Action A1: <asp:Button ID="btnActionA1" runat="server" Text="Owner Only" /><br /><br />
</asp:panel>
<asp:panel ID="Panel2" runat="server">
    Action A2:<asp:button ID="btnActionA2" runat="server" text="Manager Only" /><br />
</asp:panel>
<asp:panel ID="Panel3" runat="server">
    Action A3:<asp:button ID="btnActionA3" runat="server" text="Manager Or Admin" /><br />
</asp:panel>
<asp:panel ID="Panel4" runat="server">
        Action A4:<asp:button ID="btnActionA4" runat="server" text="Either User Or Owner" /><br />
</asp:panel> 
public partial class RoleSensitiveStuff_RoleSensitiveFunctionality : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        ShowHideUI();
    }
}

public void ShowHideUI()
{
    Panel1.Visible = false;
    DataTable dt = DataLayer.DL_GetRolesByAction("A1");
    foreach (DataRow dr in dt.Rows)
    {
        Panel1.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel1.Visible) break;
    }

    Panel2.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A2");
    foreach (DataRow dr in dt.Rows)
    {
        Panel2.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel2.Visible) break;
    }

    Panel3.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A3");
    foreach (DataRow dr in dt.Rows)
    {
        Panel3.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel3.Visible) break;
    }

    Panel4.Visible = false;
    dt = DataLayer.DL_GetRolesByAction("A4");
    foreach (DataRow dr in dt.Rows)
    {
        Panel4.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
        if (Panel4.Visible) break;
    }
         
}
}

4) Lastly the folder “RoleSensitiveUserControls”.  If certain chunk of role-dependent functionality (say any menu) is repeating across many pages, it’s best to put that in a user control and add all role based logic in that UserControl. This way, the UserControl itself takes care of roles in one central place and the parent page don’t have to worry on that during re-use.

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="SubMenu1.ascx.cs" Inherits="RoleSensitiveUserControls_SubMenu1" %>
<ul>
<li>L1:<asp:LinkButton ID="LinkButton1" runat="server">Manager Only</asp:LinkButton></li>
<li>L2:<asp:LinkButton ID="LinkButton2" runat="server">LinkButton</asp:LinkButton></li>
<li>L3:<asp:LinkButton ID="LinkButton3" runat="server">LinkButton</asp:LinkButton></li>
<li>L4:<asp:LinkButton ID="LinkButton4" runat="server">LinkButton</asp:LinkButton></li>
</ul>

public partial class RoleSensitiveUserControls_SubMenu1 : System.Web.UI.UserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            ShowHideUI();
        }
    }
    public void ShowHideUI()
    {
        LinkButton1.Visible = false;
        DataTable dt = DataLayer.DL_GetRolesByAction("L1");
        foreach (DataRow dr in dt.Rows)
        {
            LinkButton1.Visible = Roles.IsUserInRole(dr["RoleName"].ToString());
            if (LinkButton1.Visible) break;
        }
    }
}

Thanks for reading.

Advertisements
This entry was posted in General ASP.Net C# and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s