Introduction
The point of this tip is to show how to create/edit the web.config file dynamically for role based access to pages.
Background
Even if it’s not recommended to create/edit the web.config file dynamically, for different reasons you might want to do so . In my case our clients needed to give access to individual pages on their own using a user interface .
Here we won't be editing the main web.config file that contains the settings for the whole project .We will be creating/editing a web.config which is found on the folders where your .aspx pages reside.
For user management purposes I used SqlMembership provider and there are a lot of materials on the internet that shows how to use it and there is no need to repeat it here.
After you created users and roles using SqlMembership provider then the need comes for you to give access to pages either by role or to individual users. This tip shows you how to achieve that .
To get started with I used a tutorial by Dan Clem which is found at http://www.4guysfromrolla.com/articles/053007-1.aspx . The tutorial shows you how to give folder level access to roles or users .
But incase if you needed to provide access to your individual pages that are inside your folders and also create your web.config on the fly you can use this tip.
The Steps are organized as follows
1.Create the user interface
2.Populate the tree
3.Create the web.config file dyanamically (i.e if it doesn't exist)
4.Create a rule
5.Update a rule
6.Delete a rule
7.Display the Access rules on Grid
1.Creating the user Interface
Use the below code to create the user interface and don't forget to change the images with your own .
I am also using telerik RadGrid you might also want to change that to Microsoft Gridview control .
<div>
<table>
<tr>
<th>
<asp:Localize ID="Localize1" runat="server"
Text="Access Rule Management" />
</th>
</tr>
<tr>
<td class="details" valign="top">
<p>
<i>
<asp:Localize ID="Localize2" runat="server"
Text="Use this page to manage access rules for the system users. Rules are applied to pages." />
</i>
</p>
<asp:Label ID="lblError" runat="server" CssClass="Important" Visible="False"
style="font-weight: 700"></asp:Label>
<table style="width: 100%">
<tr>
<td valign="top" style="padding-right: 30px;">
<div class="treeview">
<asp:TreeView runat="server" ID="FolderTree"
OnSelectedNodeChanged="FolderTree_SelectedNodeChanged" ExpandDepth="1" OnTreeNodeExpanded="FolderTree_TreeNodeExpanded">
<RootNodeStyle ImageUrl="~/Content/Images/folder.gif" />
<ParentNodeStyle ImageUrl="~/Content/Images/folder.gif" />
<LeafNodeStyle ImageUrl="~/Content/Images/folder.gif" />
<SelectedNodeStyle Font-Underline="True" ForeColor="#A21818" />
</asp:TreeView>
</div>
</td>
<td valign="top" style="padding-left: 30px; border-left: 1px solid #999; width: 80%">
<asp:Panel runat="server" ID="SecurityInfoSection" Visible="False">
<h4 runat="server" id="TitleOne" class="alert"></h4>
<p align="center">
<asp:Label ID="ActionStatus" runat="server" CssClass="Important" Visible="False"></asp:Label>
</p>
<telerik:RadGrid ID="RadGridRuleDisplay" runat="server"
GridLines="None" CellSpacing="0" Skin="Vista" AutoGenerateColumns="False">
<ClientSettings>
<Selecting AllowRowSelect="True" />
</ClientSettings>
<AlternatingItemStyle BackColor="#F0F8FF" />
<MasterTableView>
<Columns>
<telerik:GridBoundColumn DataField="Roles" HeaderText="Allowed Roles">
<ColumnValidationSettings>
<ModelErrorMessage Text=""></ModelErrorMessage>
</ColumnValidationSettings>
</telerik:GridBoundColumn>
<telerik:GridTemplateColumn HeaderText="Delete Rule" UniqueName="DeleteRule">
<ItemTemplate>
<asp:ImageButton ID="Button1" ToolTip="Delete Rule" AlternateText="Delete Rule"
runat="server"
ImageUrl='<%# RadAjaxLoadingPanel.GetWebResourceUrl(Page, "Telerik.Web.UI.Skins.Default.Grid.Delete.gif") %>'
OnClick="DeleteRule"
OnClientClick="return confirm('Click OK to delete this rule.')" />
</ItemTemplate>
</telerik:GridTemplateColumn>
</Columns>
</MasterTableView>
</telerik:RadGrid>
<br />
<hr />
<h4 runat="server" id="TitleTwo" class="alert"></h4>
<b>
<asp:Literal ID="Literal1" runat="server" Text="Action:" /></b>
<asp:RadioButton runat="server" ID="ActionDeny" GroupName="action"
Text="Deny" Checked="True" />
<asp:RadioButton runat="server" ID="ActionAllow" GroupName="action"
Text="Allow" />
<br />
<br />
<b>
<asp:Literal ID="Literal2" runat="server" Text="Rule applies to:"
meta:resourcekey="Literal2Resource1" /></b>
<br />
<asp:RadioButton runat="server" ID="ApplyRole" GroupName="applyto"
Text="This Role:" Checked="True" meta:resourcekey="ApplyRoleResource1" />
<telerik:RadComboBox ID="UserRoles" runat="server" AppendDataBoundItems="True"
Filter="Contains">
<Items>
<telerik:RadComboBoxItem Text="Select Role" Value="Select Role" runat="server"
Owner="" />
</Items>
</telerik:RadComboBox>
<br />
<asp:Button ID="Button4" runat="server" Text="Create Rule" OnClick="CreateRule"
OnClientClick="return confirm('Click OK to create this rule.');" />
<asp:Literal runat="server" ID="RuleCreationError"></asp:Literal>
</asp:Panel>
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>Colourised in 161ms
On my machine the final UI looks like the image shown below

2.Populating the tree
Call the method populate tree on Page_Load to populate the tree
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
PopulateTree();
}
}
private const string VirtualImageRoot = "~/";
string selectedFolderName;
protected void Page_Init()
{
UserRoles.DataSource = Roles.GetAllRoles();
UserRoles.DataBind();
if (IsPostBack)
{
selectedFolderName = "";
}
else
{
selectedFolderName = Request.QueryString["selectedFolderName"];
}
}
protected void PopulateTree()
{
DirectoryInfo rootFolder = new DirectoryInfo(Server.MapPath(VirtualImageRoot));
TreeNode root = AddNodeAndDescendents(rootFolder, null);
FolderTree.Nodes.Add(root);
try
{
FolderTree.SelectedNode.ImageUrl = "~/Content/Images/folder.gif";
}
catch
{
}
}Colourised in 48ms
Here is the Method that addes the Nodes and it Descendants
protected TreeNode AddNodeAndDescendents(DirectoryInfo folder, TreeNode parentNode)
{
string virtualFolderPath;
if (parentNode == null)
{
virtualFolderPath = VirtualImageRoot;
}
else
{
virtualFolderPath = parentNode.Value + folder.Name + "/";
}
TreeNode node = new TreeNode(folder.Name, virtualFolderPath);
node.Selected = (folder.Name == selectedFolderName);
DirectoryInfo[] subFolders = folder.GetDirectories();
foreach (DirectoryInfo subFolder in subFolders)
{
if (subFolder.Name != "_controls" && subFolder.Name != "App_Data" && subFolder.Name != "App_Code"
&& subFolder.Name != "bin" && subFolder.Name != "fonts" && subFolder.Name != "Scripts"
&& subFolder.Name != "Styles" && subFolder.Name != "Temp" && subFolder.Name != "UserControls"
&& subFolder.Name != "obj" && subFolder.Name != "Service References" && subFolder.Name != "App_Start"
&& subFolder.Name != "Account" && subFolder.Name != "Properties" && subFolder.Name != "Models"
&& subFolder.Name != "Content" && subFolder.Name != "Images")
{
TreeNode child = AddNodeAndDescendents(subFolder, node);
node.ChildNodes.Add(child);
foreach (FileInfo File in subFolder.GetFiles())
{
if (File.Extension == ".aspx")
{
TreeNode childnode = new TreeNode(File.Name, File.FullName);
childnode.ImageUrl = "~/Content/Images/icon-new.png";
child.ChildNodes.Add(childnode);
}
}
}
}
return node; }Colourised in 78ms
3.Create the Web.config Dynamically
On the TreeNodeExpanded event check whether there is a web.config file on the selected folder and create it dynamically if it doesn't exist
protected void FolderTree_TreeNodeExpanded(object sender, TreeNodeEventArgs e)
{
if (!System.IO.File.Exists(e.Node.Value))
{
CreateWebConfigFile(e.Node.Value);
}
}
void CreateWebConfigFile(string folderPath)
{
string pathString = System.IO.Path.Combine(Server.MapPath(folderPath), "Web.config");
if (!System.IO.File.Exists(pathString))
{
FileInfo f = new FileInfo(pathString);
using (StreamWriter fs = f.CreateText())
{
string xmlFile = "<?xml version='1.0'?><configuration></configuration>";
fs.Write(xmlFile);
fs.Close();
}
}
} Colourised in 31ms
4.Create rule
On the button click event of createRule call the AddRoleRule method
protected void CreateRule(object sender, EventArgs e)
{
AuthorizationRule newRule;
if (ActionAllow.Checked)
newRule = new AuthorizationRule(AuthorizationRuleAction.Allow);
else
newRule = new AuthorizationRule(AuthorizationRuleAction.Deny);
if (ApplyRole.Checked && UserRoles.SelectedIndex > 0)
{
AddRoleRule(newRule, UserRoles.Text);
}
}Colourised in 12ms
The method that is responsible for creating the rule is shown below
How it works ,
It starts by loading the web.config file as an Xml document .Before creating the new authorization rule it checks whether the authorization rule is already updated . If the rule is not updated it means it's a new rule it and it creates it as an xml element including it’s attributes and child elements.Finally after it has created the elements it appends them to the end of the Xml document (i.e the loaded web.config file) .
protected void AddRoleRule(AuthorizationRule newRule, string selectedrole)
{
bool updated = false;
string virtualFolderPath = FolderTree.SelectedNode.Parent.Value;
Configuration config = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath);
XmlDocument xDoc = new XmlDocument();
xDoc.Load(config.FilePath);
FileInfo myFile = new FileInfo(config.FilePath);
myFile.IsReadOnly = false;
updated = getOldAssignedRolesAndUpdate(config, newRule, Path.GetFileName(FolderTree.SelectedValue), selectedrole, xDoc);
if (!updated)
{
XmlElement newLocationelement = xDoc.CreateElement("location");
XmlAttribute newLocationAttrib = xDoc.CreateAttribute("path");
newLocationAttrib.Value = Path.GetFileName(FolderTree.SelectedValue);
newLocationelement.Attributes.Append(newLocationAttrib);
XmlElement newSystemWebelement = xDoc.CreateElement("system.web");
XmlElement newAuthorizationelement = xDoc.CreateElement("authorization");
XmlElement newAllowelement = xDoc.CreateElement("allow");
XmlAttribute newAllowAttrib = xDoc.CreateAttribute("roles");
newRule.Roles.Add(selectedrole).ToString();
string listofRoles = "";
foreach (var item in newRule.Roles)
{
listofRoles = item.ToString() ;
}
newAllowAttrib.Value = listofRoles;
newAllowelement.Attributes.Append(newAllowAttrib);
XmlElement newDenyelement = xDoc.CreateElement("deny");
XmlAttribute newUsersAttrib = xDoc.CreateAttribute("users");
newUsersAttrib.Value = "*";
newDenyelement.Attributes.Append(newUsersAttrib);
newAuthorizationelement.AppendChild(newAllowelement);
newAuthorizationelement.AppendChild(newDenyelement);
newLocationelement.AppendChild(newSystemWebelement);
newSystemWebelement.AppendChild(newAuthorizationelement);
xDoc.DocumentElement.AppendChild(newLocationelement);
xDoc.PreserveWhitespace = true;
XmlTextWriter xwriter = new XmlTextWriter(config.FilePath, null);
try
{
xDoc.WriteTo(xwriter);
xwriter.Close();
PopulateRolesGrid(Path.GetFileName(FolderTree.SelectedValue), config);
ActionStatus.Visible = true;
ActionStatus.ForeColor = Color.Green;
ActionStatus.Text = "Role Added Successfuly!";
RuleCreationError.Visible = false;
}
catch (Exception ex)
{
RuleCreationError.Visible = true;
RuleCreationError.Text = "An error occurred and the rule was not added.";
}
}
}
Colourised in 101ms
5.Update rule
Use the below method to update a specific rule if exist for the selected page
How it works ,
It starts by loading the web.config file as an XDocument . First it gets the "location" element which has it's "path" attibute value of the selected page, then it gets the "authorization" element and checks whether it has "allow" element inside it if it has no "allow" element it creates it . If there already an "allow" element it updates it by concatenating the new role to the older one.
bool getOldAssignedRolesAndUpdate(Configuration config, AuthorizationRule newRule,
string selectedPage, string selectedRole, XmlDocument xDoc)
{
bool updated = false;
XDocument xmlFile = XDocument.Load(config.FilePath);
IEnumerable<XElement> location = from el in xmlFile.Elements("configuration").
Elements("location")
where (string)el.Attribute("path") == selectedPage
select el;
var authorizationElement = from authorization in location.Elements("system.web").
Elements("authorization")
select authorization;
var allowRoleRuleElements = from category in location.Elements("system.web").
Elements("authorization").Elements("allow")
where (string)category.Attribute("roles") != null
select category;
if (authorizationElement.Count() > 0)
{
if (allowRoleRuleElements.Count() == 0)
{
XmlDocument xDocNew = new XmlDocument();
xDocNew.Load(config.FilePath);
XmlNode xNode = xDocNew.CreateNode(XmlNodeType.Element, "allow", "");
XmlAttribute xKey = xDocNew.CreateAttribute("roles");
xKey.Value = selectedRole;
xNode.Attributes.Append(xKey);
authorizationElement.Single().AddFirst(xNode);
xDocNew.Save(config.FilePath);
ActionStatus.Visible = true;
ActionStatus.ForeColor = Color.Green;
ActionStatus.Text = "Role Added Successfuly!";
return true;
}
else
{
string oldrole = allowRoleRuleElements.Single().Attribute("roles").Value.ToString();
if (newRule.Action == AuthorizationRuleAction.Deny)
{
int index = oldrole.IndexOf(selectedRole);
string newRole = (index < 0) ? oldrole : oldrole.Remove(index, selectedRole.TrimStart(',').Length);
allowRoleRuleElements.Single().Attribute("roles").Value = newRole.TrimEnd(',').TrimStart(',');
}
else
{
string newRole = (oldrole == String.Empty) ? selectedRole.Trim() : (oldrole + "," + selectedRole);
allowRoleRuleElements.Single().Attribute("roles").Value = newRole;
}
try
{
xmlFile.Save(config.FilePath);
PopulateRolesGrid(Path.GetFileName(FolderTree.SelectedValue), config);
RuleCreationError.Visible = false;
}
catch (Exception ex)
{
RuleCreationError.Visible = true;
RuleCreationError.Text = "An error occurred and the rule was not added.";
}
}
updated = true;
}
return updated;
}Colourised in 146ms
6.Delete rule
How it works,
It loads the the web.config file as XDocument and gets the node which has it's path attibute of the selected page and remove/delete all that are associated with it.
protected void DeleteRule(object sender, EventArgs e)
{
ImageButton button = (ImageButton)sender;
GridDataItem item = (GridDataItem)button.Parent.Parent;
string virtualFolderPath = FolderTree.SelectedNode.Parent.Value;
Configuration config = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath);
FileInfo myFile = new FileInfo(config.FilePath);
myFile.IsReadOnly = false;
ConfigurationLocationCollection loc = (ConfigurationLocationCollection)config.Locations;
SystemWebSectionGroup systemWeb = (SystemWebSectionGroup)config.GetSectionGroup("system.web");
AuthorizationSection section = (AuthorizationSection)systemWeb.Sections["authorization"];
XDocument doc = XDocument.Load(config.FilePath);
doc.Declaration = new XDeclaration("1.0", null, null);
var ruleTobeDeleted = from node in doc.Descendants("configuration").Elements("location")
where (string)node.Attribute("path") == Path.GetFileName(FolderTree.SelectedValue)
select node;
ruleTobeDeleted.ToList().ForEach(x => x.Remove());
doc.Save(config.FilePath, SaveOptions.OmitDuplicateNamespaces);
ActionStatus.ForeColor = Color.Green;
ActionStatus.Visible = true;
ActionStatus.Text = "Role Deleted Successfuly!";
PopulateRolesGrid(Path.GetFileName(FolderTree.SelectedValue), config);
}Colourised in 58ms
7.Display Access rules on grid
For data binding purpose to the grid create a class which contains the necessary fields
public class AuthorizationRoleCollection
{
public string Roles { get; set; }
public string Users { get; set; }
public string Action { get; set; }
}
Colourised in 8ms
Display the access rules on Grid
protected void FolderTree_SelectedNodeChanged(object sender, EventArgs e)
{
if (FolderTree.SelectedNode.Value.Contains(".aspx"))
{
ActionDeny.Checked = true;
ActionAllow.Checked = false;
ApplyRole.Checked = true;
ActionStatus.Visible = false;
UserRoles.SelectedIndex = 0;
RuleCreationError.Visible = false;
lblError.Visible = false;
FolderTree.SelectedNode.ImageUrl = "~/Content/Images/icon-new.png";
string folderPath = FolderTree.SelectedNode.Parent.Value;
DisplayAccessRules(folderPath, Path.GetFileName(FolderTree.SelectedValue));
}
else
{
lblError.Visible = true;
lblError.Text = "Please select a Page.";
SecurityInfoSection.Visible = false;
}
}Colourised in 36ms
Display access rules method
protected void DisplayAccessRules(string virtualFolderPath, string page)
{
try
{
SecurityInfoSection.Visible = true;
lblError.Visible = false;
Configuration config = WebConfigurationManager.OpenWebConfiguration(virtualFolderPath);
PopulateRolesGrid(page, config);
TitleOne.InnerText = "Rules applied to " + page;
TitleTwo.InnerText = "Create new rule for " + page;
}
catch (Exception)
{
throw;
}
}Colourised in 35ms
Use the below method to populate the grid with roles that have access to pages
private void PopulateRolesGrid(string page, Configuration config)
{
XDocument xmlFile = XDocument.Load(config.FilePath);
IEnumerable<XElement> roles = from el in xmlFile.Elements("configuration").
Elements("location")
where (string)el.Attribute("path") == page
select el;
var authorizationElement = from authorization in roles.Elements("system.web").
Elements("authorization")
select authorization;
var allowRoleRuleElements = from category in roles.Elements("system.web").
Elements("authorization").Elements("allow")
where (string)category.Attribute("roles") != null
select category;
var denyUserRuleElements = from category in roles.Elements("system.web").
Elements("authorization").Elements("deny")
where (string)category.Attribute("users") != null
select category;
List<AuthorizationRoleCollection> arList = new List<AuthorizationRoleCollection>();
foreach (var item in authorizationElement)
{
AuthorizationRoleCollection ar = new AuthorizationRoleCollection();
ar.Roles = allowRoleRuleElements.First().FirstAttribute.Value.ToString();
ar.Users = denyUserRuleElements.First().FirstAttribute.Value.ToString();
ar.Action = "";
arList.Add(ar);
}
RadGridRuleDisplay.DataSource = arList;
RadGridRuleDisplay.DataBind();
}Colourised in 75ms
History
First version