{"id":215,"date":"2007-01-22T00:00:00","date_gmt":"2007-01-22T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/controls-based-security-in-a-windows-forms-application\/"},"modified":"2021-04-29T15:28:29","modified_gmt":"2021-04-29T15:28:29","slug":"controls-based-security-in-a-windows-forms-application","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/controls-based-security-in-a-windows-forms-application\/","title":{"rendered":"Controls Based Security in a Windows Forms Application"},"content":{"rendered":"<p>One of my clients wanted to be able to restrict any given control, on any form, so that it is either invisible or disabled based on who is using the form. We decided to make the restrictions &#8220;roles-based&#8221; &#8211; that is &#8220;managers can click this button, users can see it, but to guests it is invisible.&#8221;<\/p>\n<p>We wanted to build an architecture that would allow us to add forms and controls to the application without deciding in advance which roles we would use, and without having to modify the forms or controls to meet the needs of the security architecture any more than absolutely necessary. The ideal security architecture would be<b> independent of the participating forms and controls<\/b>.<\/p>\n<p>In this article, I will review the approach I took, focusing on the nitty-gritty code used to make this work, and the challenges faced in creating such an application quickly (their budget for this was 4 days). This article takes you as far as saving the users, roles and the permissions those roles have for the various controls. It does <i>not<\/i> implement login and so it does not implement any of the checks to see if the logged in user should be restricted in access to the controls on any given page. All of that is left as a (dare I say?) fairly straight-forward exercise for the reader.<\/p>\n<p>The full source code for this article is available for download. Simply click on the <b>CODE DOWNLOAD<\/b> link in the box to the right of the article title. It is also available on the author&#8217;s <a href=\"http:\/\/www.jliberty.com\/\">website<\/a>.<\/p>\n<p><b>NOTE<\/b>:<br \/>\n<i>This article is targeted at .NET 2 programmers already familiar with C# and .NET Windows Forms. I do not explain how to create forms, or how event handling works, nor do I explain how to interact with a SQL database.<\/i><\/p>\n<h2>Users and roles<\/h2>\n<p>There are many security schemes that have evolved over time, but the one which has proven most successful, at least in the Windows world, is that of Access Control Lists (ACLs) now most commonly referred to as <b>Users<\/b> and <b>Roles<\/b>. We see this most cleanly and starkly implemented in ASP.NET, though it presents a bit more of a challenge with Windows Forms applications that will be used by a very large number of users. (One approach is to use the ASP.NET authentication support, accessing it through a web service.)<\/p>\n<p><b>NOTE<\/b>:<br \/>\n<em>See, for example, my articles on creating <\/em><a href=\"http:\/\/www.ondotnet.com\/pub\/a\/dotnet\/2004\/06\/14\/liberty_whidbey.html\"><em>users <\/em><\/a><em>and <\/em><a href=\"http:\/\/www.ondotnet.com\/pub\/a\/dotnet\/2004\/06\/28\/liberty_whidbey.html\"><em>roles <\/em><\/a><em>on O&#8217;Reilly Windows DevCenter (though today I would re-write these articles to use the Web Site Administration Tool accessed from Visual Studio under Website-&gt; ASP.NET -&gt; Configuration.<\/em><\/p>\n<p>My client had their own authentication system (as part of their larger in-house system) and to keep this article simple I&#8217;ll follow their lead and simply create a <b>Users<\/b> table and a <b>Roles<\/b> table and finesse the authentication.<\/p>\n<h2>Roles and controls<\/h2>\n<p>To decide if a user has &#8220;access&#8221; to a control (which will be defined as meaning the right to see a control or to invoke the control) we&#8217;ll create two additional objects:<\/p>\n<ul>\n<li>\n<div><b>ControlPermission<\/b> which will represent a given control on a given form, and<\/div>\n<\/li>\n<li>\n<div><b>PermissionToRole<\/b> which encapsulates the relationship between a given <b>ControlPermsision<\/b> and a given Role (the one to many relationship) and the permissions for that control by users in that role.<\/div>\n<\/li>\n<\/ul>\n<p><b>NOTE<\/b>:<br \/>\n<em>In a &#8220;real&#8221; application, I&#8217;d add a middle tier of business objects to represent the user, role, control and the relationships, but again, to keep this paper stripped down to the essence, the sample application will have the presentation layer talk directly to the persistence layer (a practice I generally recommend against! Please see <\/em><a href=\"http:\/\/www.simple-talk.com\/dotnet\/.net-framework\/.net-application-architecture-the-data-access-layer\/\"><em>this article<\/em><\/a><em> for more on this topic)<\/em>.<\/p>\n<h2>The database<\/h2>\n<p>The database (for this part of the application) is very straight forward. We&#8217;ll create five tables, as shown in the figure 1:<\/p>\n<p>NOTE:<br \/>\n<em>To make this article as accessible as possible, I created the database using SQLExpress, though I accessed and manipulated the database using the SQL Server Management Studio from SQL Server 2005.<\/em><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig001_DB-Diagram.gif\" alt=\"338-Fig001_DB-Diagram.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 1 Database diagram<\/em><\/p>\n<p>There are three data tables (<b>Users<\/b>, <b>Roles<\/b> and <b>Controls<\/b>) and two tables that create many-to-many relationships: <b>UsersToRoles<\/b> and <b>ControlsToRoles<\/b>. <b>Users<\/b> and <b>Roles<\/b> are more or less self-explanatory. The <b>Controls<\/b> table represents a control on a form, and <b>ControlsToRoles<\/b> is the heart of the control-based security approach; it represents the permissions of a given role for a given control on a given form, as is explained in detail below.<\/p>\n<h2>Application and control-security forms<\/h2>\n<p>The application may consist of any number of forms. To keep the explanation clear, we&#8217;ll draw a distinction between the two forms used for control-security, and all the other forms used for the application (which we&#8217;ll call &#8220;application forms.&#8221;).<\/p>\n<p>There are only two requirements for an application form to participate in control-based security:<\/p>\n<ol>\n<li>Each application form must include two <b>toolTip<\/b> controls (explained in detail below) which must be named <b>toolTip1<\/b> and <b>toolTip2<\/b>.<\/li>\n<li>Each application form must provide some means (typically a menu choice) of invoking the two control-security forms: <b>ManageRoles.cs<\/b> and <b>ManagePermissions.cs<\/b><\/li>\n<\/ol>\n<p>The application forms are free to use <b>toolTip1<\/b> in any way they choose (including ignoring it), but they must not use <b>toolTip2<\/b> at all, as its contents will be controlled by the Control-security forms.<\/p>\n<p><i>That&#8217;s it<\/i>, otherwise all application forms and their controls (including user controls and custom controls) remain unchanged.<\/p>\n<h2>Creating the application<\/h2>\n<p>Begin by creating a new Windows Forms application in Visual Studio 2005 (or your favorite alternative tool).<\/p>\n<p>Let&#8217;s assume, for the sake of this article, that the application you are building will be used by a Sales Representative to enter data about a request to Liberty Associates, Inc. for contract programming, training or writing. For illustration purposes I&#8217;ll create two application forms (<b>PotentialClient<\/b> and <b>NewContract<\/b>), but you can imagine an application with 20 (or 200) application forms. I&#8217;ll also create the two security forms: <b>ManageRoles<\/b> and <b>ManagePermissions<\/b>.<\/p>\n<p><b>PotentialClient<\/b> asks for basic demographic information as shown in figure 2:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig2_PotentialClientForm.gif\" alt=\"338-Fig2_PotentialClientForm.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 2 Potential Client Form<\/em><\/p>\n<p>And <b>NewContract<\/b> asks for details about the work requested, as shown in figure 3.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig3_New%20Contract.gif\" alt=\"338-Fig3_New%20Contract.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 3 Second form: New Contract<\/em><\/p>\n<p>I&#8217;ve intentionally made these application forms crude and simple to allow us to focus on the control-based security rather than on the form design. In any case, none of the data retrieved in these forms will be persisted, and I encourage you to create your own forms that more closely represent your own business needs.<\/p>\n<h3>Creating users and roles<\/h3>\n<p>As noted earlier, there are numerous ways to approach creating users and roles in a Windows application. The three that I personally find most appealing are:<\/p>\n<ol>\n<li>Use the Windows built in users and roles if the application needs and network needs are 100% isomorphic (a rare but not impossible scenario)<\/li>\n<li>Use a Web Service to leverage the ASP.NET forms-based security infrastructure created by Microsoft.<\/li>\n<li>Create your own simple database, and use a proprietary (existing) authenticating system.<\/li>\n<\/ol>\n<p>Again, to keep this paper focused, we&#8217;ll assume situation #3. Thus, we need only create a Windows form for adding users, adding roles and adding users to roles, and saving all of that to the database. This is easily accomplished by creating the form shown in Figure 4:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig4_Adding-Users-To-Roles.gif\" alt=\"338-Fig4_Adding-Users-To-Roles.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 4 Manage Roles Form <\/em><\/p>\n<p>You will want to bind the list boxes to data sources tied to your data tables. When the user clicks on <b>AddNew<\/b> you&#8217;ll get the name from the text box and create the new record for the database and for the list box:<\/p>\n<pre>private void AddNewRole_Click( object sender, EventArgse )\r\n{\r\n\u00a0\u00a0\u00a0 string newName = string.Empty;\r\n\u00a0\u00a0\u00a0 newName = NewRoleName.Text;\r\n\u00a0\u00a0\u00a0 NewRoleName.Text = string.Empty; \/\/ clear the control\r\n\r\n\u00a0\u00a0\u00a0 ControlSecurityDataSet.RolesRownewRolesRow;\r\n\u00a0\u00a0\u00a0 newRolesRow = controlSecurityDataSet.Roles.NewRolesRow();\r\n\u00a0\u00a0\u00a0 newRolesRow.RoleName = newName;\r\n\u00a0\u00a0\u00a0 this.controlSecurityDataSet.Roles.Rows.Add( newRolesRow );\r\n\r\n\u00a0\u00a0\u00a0 try\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.rolesTableAdapter.Update\r\n( this.controlSecurityDataSet.Roles );\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 catch ( Exceptionex )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.controlSecurityDataSet.Roles.Rows.\r\nRemove( newRolesRow );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MessageBox.Show( \"Unable to add role \"+ newName \r\n+ ex.Message,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"Unable to add role!\", MessageBoxButtons.OK,\r\nMessageBoxIcon.Error );\r\n\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0 RolesListBox.SelectedIndex = -1;\r\n\r\n}<\/pre>\n<p>When you add a user to a role (by clicking on the arrow key), you&#8217;ll make an entry in the <b>UsersToRoles<\/b> table (adding the <b>UserID<\/b> and <b>RoleID<\/b> and then updating the <b>TreeView<\/b> (far right)). To update the <b>TreeView<\/b>, you&#8217;ll retrieve the entries fro the database and iterate through the rows of the table crating a new parent node each time you come across a new user name (if the &#8220;Name&#8221; radio button is selected) or a new <b>RoleName<\/b> (if the Role radio button is pressed):<\/p>\n<pre>\r\nforeach ( DataRow row indt.Rows ) \r\n\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0 if( rbName.Checked )\r\n\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 subNode = new TreeNode( row[\"roleName\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ( currentName != row[\"Name\"].ToString() )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ParentNode = new TreeNode( row[\"Name\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 currentName = row[\"Name\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 UsersInRoles.Nodes.Add( ParentNode );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0 }<\/pre>\n<p>Since our focus is on the controls-based security, I won&#8217;t go into more detail here, though as mentioned earlier, the complete source is available for you to download and try out.<\/p>\n<h3>The Manage Permissions page<\/h3>\n<p>The second, and perhaps more important, page involved in managing the control-based security will display all the controls for a given page, and will display all the roles known to the application. Since the default is full access, the administrator need only indicate the restrictions to apply for any given control for any given role. The page will have two multi-select list boxes: one displaying all the controls from the page and one displaying all the roles. In addition, much as in the <b>ManageRoles<\/b> form, there will be a <b>Tree<\/b> control displaying the saved restrictions, either by role or by control, as shown in Figure 5:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig5_ManagePermissions.gif\" alt=\"338-Fig5_ManagePermissions.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 5 Manage Permissions<\/em><\/p>\n<p>To accomplish this, you&#8217;ll want to extend your data source to include the <b>Controls<\/b> and <b>ControlsToRoles<\/b> tables as shown in Figure 6:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig6_ControlSecurityDataSet.gif\" alt=\"338-Fig6_ControlSecurityDataSet.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 6: Control Security Data Set<\/em><\/p>\n<p>Once again you&#8217;ll populate the Roles list box by dragging the Roles table from the data sources view onto the list box, thus creating both a <b>BindingSource<\/b> and a <b>TableAdapter<\/b><\/p>\n<p>Populating the <b>Controls <\/b>permission list box is a bit trickier. To do this you need access to the list of controls for the form.<\/p>\n<p>The <b>ManagePermissions<\/b> constructor will take three arguments: a reference to a Form, and two references to ToolTip objects.). The constructor will stash these away in private member variables.<\/p>\n<pre>public partial class ManagePermissions : Form\r\n{\r\n\u00a0\u00a0\u00a0 private FormworkingForm;\r\n\u00a0\u00a0\u00a0 private ToolTip formToolTip1 = null;\r\n\u00a0\u00a0\u00a0 private ToolTip formToolTip2 = null;\r\n\r\n\u00a0\u00a0\u00a0 public ManagePermissions( Form f, ToolTip toolTip1,\r\nToolTiptoolTip2 )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 InitializeComponent();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 workingForm = f;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip1 = toolTip1;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip2 = toolTip2;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip1.Active = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip2.Active = true;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.Text += \" for page \" + f.Name;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ShowControls( f.Controls );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PopulatePermissionTree();\r\n\r\n\u00a0\u00a0\u00a0 }<\/pre>\n<p>Note that after it stashes away the references it sets the title to indicate which form (page) it is setting permissions for. The reason we pass in the two tool tips is to change the tooltips from whatever the programmer <i>was <\/i>using the tooltips for to now using the tooltips to indicate the name of every control on the page. This allows the administrator to identify the exact control to be restricted, as shown in figure 7:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig007_ToolTips3.gif\" alt=\"338-Fig007_ToolTips3.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 7 &#8211; Tool Tips used to name Controls<\/em><\/p>\n<p>In Figure 7 you can see that <i>when the permissions dialog is open<\/i> if the user hovers over any control on the page for which permissions are being set, the tool tip has been changed to indicate the name of the control. This corresponds directly to the name listed in the Control Permissions list box (which is sorted alphabetically).<\/p>\n<h4>Setting permissions<\/h4>\n<p>The administrator selects (or multi-selects) one or more controls in the Controls Permission list box, then selects (or multi-selects) one or more roles in the roles list box and then checks either or both of invisible and disabled. The handler for the <b>Save<\/b> button loops through each indicated control and each indicated role and calls a stored procedure to make a record in <b>ControlToRoles<\/b> (first ensuring there is a record for that control in Controls). All of this is done under transaction support, as explained in detail below).<\/p>\n<h4>Cleaning up<\/h4>\n<p>When the Control Permissions page closes, we reset the original tool tips (which we stashed away).<\/p>\n<h4>The nitty gritty<\/h4>\n<p>There are a number of juicy technical details, and the best way to see them is to walk through them step by step.<\/p>\n<h2>Implementing permissions &#8211; step-by-step<\/h2>\n<p>The following is an annotated walk through of adding restrictions to a control for a given role, as you might see it stepping through the debugger.<\/p>\n<p>&lt;<em>Rant<\/em>&gt;<br \/>\nProgrammers do not spend nearly enough time becoming proficient with their editor nor with their debugger. This seems crazy to me; on a par with race car drivers who do not know how the engine to their car works, or infantry who can&#8217;t break down their weapons. They put others at risk.<\/p>\n<p>Code should be self-commenting. Excessive comments (more than a few per page) are an admission of failure and each comment you add to your code greatly decreases the life expectancy of your code (comments rust). This second point is highly controversial and worth an opinion piece all its own (forthcoming).<\/p>\n<p>\nThe best way to learn how to program is (a) to buy a really good book with lots of exercises, (b) to expand those exercises and (c) [most important] to step through your exercises in the debugger to see what is really happening. The best way to learn new techniques is to step through code. Visual Studio has a great debugger, pay special attention to the watch and quick watch features.<br \/>\n&lt;\/<em>Rant<\/em>&gt;<\/p>\n<p>We&#8217;ll pick up the program where the user clicks on <b>ManagePermissions<\/b> from the <b>PotentialClient<\/b> Application Form, as shown in figure 8:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig8_InvokeManagePermissions.gif\" alt=\"338-Fig8_InvokeManagePermissions.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 8 Manage Permissions<\/em><\/p>\n<p>The code handler follows:<\/p>\n<pre>private void managePermissionsToolStripMenuItem_Click\r\n( object sender, EventArgse )\r\n{\r\n\u00a0\u00a0\u00a0 ManagePermissions dlg = new ManagePermissions( \r\n\u00a0\u00a0\u00a0 this, \r\n\u00a0\u00a0\u00a0 this.toolTip1, \r\n\u00a0\u00a0\u00a0 this.toolTip2 );\r\n\u00a0 dlg.Show();\r\n}<\/pre>\n<p>There are two things to note about this handler:<\/p>\n<ol>\n<li>As required, it passes in a reference to itself (the form) and to its two <b>ToolTip<\/b> objects to the constructor for the <b>ManagePermissions<\/b> form.<\/li>\n<li>Equally important, it calls <b>Show<\/b>, and not <b>ShowModal<\/b>, when invoking the dialog &#8211; this allows the user to return to the invoking form to give the form focus and hover over controls to see the name of the control in the tooltips.<\/li>\n<\/ol>\n<h3>ManagePermisssions constructor<\/h3>\n<p>Control now switches to the constructor of the <b>ManagePermissions<\/b> form, which was shown, slightly-excerpted, before. The ommission was that I hid the Dictionary named <b>oldMenuToolTips<\/b> that I was forced to create despite the fact that I hold on to <b>ToolTips1<\/b>. This dictionary is needed because tool tips work differently for menus than they do for other controls. This is a detail we&#8217;ll come to in a bit.<\/p>\n<p>Stepping into the <b>ManagePermissions<\/b> constructor, the class members are initialized and then the body of the constructor itself is called:<\/p>\n<pre>public partial class ManagePermissions : Form\r\n{\r\n\u00a0\u00a0\u00a0 private Dictionary&lt;string, string&gt; oldMenuToolTips = \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 new Dictionary&lt;string, string&gt;();\r\n\u00a0\u00a0\u00a0 private FormworkingForm;\r\n\u00a0\u00a0\u00a0 private ToolTip formToolTip1 = null;\r\n\u00a0\u00a0\u00a0 private ToolTip formToolTip2 = null;\r\n\r\n\u00a0\u00a0\u00a0 public ManagePermissions( Form f, ToolTip toolTip1,\r\nToolTiptoolTip2 )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 InitializeComponent();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 workingForm = f;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip1 = toolTip1;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip2 = toolTip2;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip1.Active = false;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 formToolTip2.Active = true;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 this.Text += \" for page \" + f.Name;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ShowControls( f.Controls );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PopulatePermissionTree();\r\n\r\n\u00a0\u00a0\u00a0 }<\/pre>\n<p><b>NOTE<\/b>:<br \/>\n<em>If you are stepping through, you&#8217;ll find yourself in the designer. Put a breakpoint on <b>ShowControls<\/b> in the constructor and hit F5 to jump there, as it is the next interesting piece to examine<\/em>.<\/p>\n<h3>ShowControls<\/h3>\n<p>After setting the title bar <b>ShowControls<\/b> is called, passing in the controls collection of the form. Stepping in you see the controls collection defined as a <b>Control.ControlCollection<\/b>. The trick with this method is that Controls themselves can contain other controls (e.g., a panel can contain controls) and so the method must be made recursive:<\/p>\n<pre>foreach ( Control c incontrolCollection )\r\n{\r\n\u00a0\u00a0\u00a0 string displayName = string.Empty;\r\n\u00a0\u00a0\u00a0 if ( c.Controls.Count &gt; 0 )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ShowControls( c.Controls );\r\n\u00a0\u00a0\u00a0 }<\/pre>\n<p>A bit nastier, menu strips handle their members differently, so if the control is a menu strip, you&#8217;ll need to call a different method:<\/p>\n<pre>if ( c is MenuStrip)\r\n{\r\n\u00a0\u00a0\u00a0 MenuStrip menuStrip = c as MenuStrip;\r\n\u00a0\u00a0\u00a0 ShowToolStipItems( menuStrip.Items );\r\n}<\/pre>\n<p>The <b>ShowtoolStipItems<\/b> method itself must be recursive as well:<\/p>\n<pre>private void ShowToolStipItems(ToolStripItemCollectiontoolStripItems)\r\n{\r\n\u00a0\u00a0\u00a0 foreach ( ToolStripMenuItem mi intoolStripItems )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 oldMenuToolTips.Add( mi.Name, mi.ToolTipText );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mi.ToolTipText = mi.Name;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ( mi.DropDownItems.Count &gt; 0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ShowToolStipItems( mi.DropDownItems );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PageControls.Items.Add( mi.Name );\r\n\u00a0\u00a0\u00a0 }\r\n}<\/pre>\n<p>Notice the last line; it is here that we add the menu items to the list of controls that might be restricted &#8211; that is we treat the menu items just like any other control for purposes of control-based security.<\/p>\n<p>Returning to the <b>ShowControls<\/b> method, we are now ready to see if the control is of a type that we might want to restrict (you are free to expand the list of types). If so, we&#8217;ll set its second tool tip to its name and we&#8217;ll add it to the list box of controls:<\/p>\n<pre>if ( c is Button || c is ComboBox || c is TextBox||\r\n\u00a0\u00a0\u00a0 c is ListBox || c is DataGridView || c is RadioButton|\r\n\u00a0\u00a0\u00a0 c is RichTextBox || c is TabPage )\r\n{\r\n\r\n\u00a0\u00a0\u00a0 formToolTip2.SetToolTip( c, c.Name );\r\n\u00a0\u00a0\u00a0 PageControls.Items.Add( c.Name );\r\n\r\n}<\/pre>\n<h3>Populate Permission Tree<\/h3>\n<p>Having populated the controls, and remembering that we bound the roles control box to the roles table (by dragging the roles table from the data source to the control in design view, thus setting up a <b>rolesBindingSource<\/b> and a <b>rolesTableAdapter<\/b> and letting them do the work), we are up to the last line in the constructor in which we invoke <b>PopulatePermissionTree<\/b>.<\/p>\n<p>This method is factored out because it is invoked from a number of places (which the debugger is happy to point out to you, just go to the method, right click and then click on &#8220;Find all references.&#8221;<\/p>\n<p><b>NOTE<\/b>:<br \/>\n<em>Here, as in many places in the code, I would normally remove all calls to the database to a business object (or at least to a helper object with static member methods). Once again, to focus on the task at hand, and to keep the code straight forward, I&#8217;ve put the data access code directly into the presentation layer code, whch gives me the willies but does make for an easier to follow example.<\/em><\/p>\n<p>The method begins by retrieving the connection string from the <b>AppSettings.cs<\/b> file, using the <b>ConfigurationManager<\/b> object (the new and preferred way to do so in 2.0 applications). Once this is obtained, a SQL connection is created and opened:<\/p>\n<pre>\u00a0 ConnectionStringSettingsCollectionconnectionStrings =\r\n\u00a0\u00a0 ConfigurationManager.ConnectionStrings;\r\n\r\n\u00a0stringconnString = connectionStrings[\r\n\u00a0\u00a0\u00a0 \"ControlBasedSecurity.Properties.Settings.\r\nControlSecurityConnectionString\"].\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ToString();\r\n\r\n\u00a0SqlConnection conn = new SqlConnection( connString );\r\n\u00a0conn.Open();<\/pre>\n<p>The <strong>queryString<\/strong> to obtain the controls we want is hard wired into the code (typically, we&#8217;d use a stored procedure) and the order clause is set by which radio button is chosen by the user. A hack, but an effective one:<\/p>\n<pre>string queryString = \"select controlID, Invisible, Disabled,\r\nRoleName \"+\r\n\"from ControlsToRoles ctr \"+\r\n\" join controls c on c.ControlID = ctr.FKControlID and c.Page =\r\nctr.FKPage \"+\r\n\" join roles r on r.RoleID = ctr.FKRole \";\r\n\r\nif( ByControlRB.Checked )\r\n{\r\n\u00a0\u00a0\u00a0 queryString += \" order by ControlID\";\r\n}\r\nelse\r\n{\r\n\u00a0\u00a0\u00a0 queryString += \" order by RoleName\";\r\n}<\/pre>\n<p>In the parallel method, in <b>ManageRoles<\/b>, I had an <b>if<\/b> statement to check which radio button was checked (by user or by role) and set the varaibles <b>subNode<\/b> and <b>parentNode<\/b> accordingly:<\/p>\n<pre>\u00a0\u00a0 if( rbName.Checked )\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 subNode = new TreeNode( row[\"roleName\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ( currentName != row[\"Name\"].ToString() )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parentNode = new TreeNode( row[\"Name\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 currentName = row[\"Name\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 UsersInRoles.Nodes.Add( parentNode );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0 }\r\n\u00a0\u00a0 else\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 subNode = new TreeNode( row[\"Name\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ( currentName != row[\"RoleName\"].ToString() )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parentNode = new TreeNode( row[\"RoleName\"].ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 currentName = row[\"RoleName\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 UsersInRoles.Nodes.Add( parentNode );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<\/pre>\n<p>In this method, I&#8217;ll use the C# ternary operator to consolidate this code:<\/p>\n<pre>subNode = new TreeNode( subNodeText );\r\n\r\nstringdataName = ByControlRB.Checked ? \r\n\u00a0\u00a0\u00a0\u00a0\u00a0 row[\"ControlID\"].ToString() : row[\"RoleName\"].ToString();\r\n\r\nif( currentName != dataName )\r\n{\r\n\u00a0\u00a0\u00a0 parentNode = new TreeNode( dataName );\r\n\u00a0\u00a0\u00a0 currentName = dataName;\r\n\u00a0\u00a0\u00a0 PermissionTree.Nodes.Add( parentNode );\r\n}<\/pre>\n<p><b>NOTE<\/b>:<br \/>\n<em>You read the ternary operator statement as follows &#8220;Is the radio button <b>ControlRB<\/b> checked? If so, assign what is in the column <b>ControlID<\/b> to the string <b>DataName<\/b>, otherwise assign what is in the column <b>RoleName<\/b> to that string<\/em>.<\/p>\n<p>This avoids duplicating the code in an else statement, and thus makes the code more robust.<\/p>\n<p><b>NOTE<\/b>:<br \/>\n<em>The opportunity to factor the two <b>PopulateTree<\/b> methods (from <b>ManagePermissions<\/b> and <b>ManageRoles<\/b>, into a single helper method is left as an exercise for the reader<\/em>.<\/p>\n<p>With the query string created, we can retrieve the permissions from the database into a dataset, and from that extract the <b>DataTable<\/b> whose <b>Rows<\/b> collection we&#8217;ll iterate through to get all the existing <b>controlsToRows<\/b> relations.<\/p>\n<pre>DataSet ds = new DataSet();SqlDataAdapter dataAdapter = null;DataTable dt = null;try{\u00a0\u00a0\u00a0 dataAdapter = new SqlDataAdapter( queryString, conn );\u00a0\u00a0\u00a0 dataAdapter.Fill( ds, \"controlsToRoles\" );\u00a0\u00a0\u00a0 dt = ds.Tables[0];}catch (Exception e){\u00a0\u00a0\u00a0 MessageBox.Show( \"Unable to retrieve permissions: \" + e.Message,\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"Error retrieving permissions\", \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MessageBoxButtons.OK, \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MessageBoxIcon.Error );}finally{\u00a0\u00a0\u00a0 conn.Close();<\/pre>\n<p>With the <b>DataTable<\/b> in hand, the next step is to prep the <b>TreeView<\/b> by calling <b>BeginUpdate<\/b> (which stops it from updating until we call <b>EndUpdate<\/b>) and by clearing all its existing nodes, so that we can add all the nodes from the database and not worry about duplication. We then iterate through each row, creating sub-nodes for each parent, and adding the parents to the <b>TreeView<\/b> as we find new parents. The parents are defined as a new control (when sorting by controls) or a new role (when sorting by roles).<\/p>\n<pre>PermissionTree.BeginUpdate();\r\n\u00a0PermissionTree.Nodes.Clear();\r\n\u00a0TreeNode parentNode = null;\r\n\u00a0TreeNode subNode = null;\r\n\r\n\u00a0string currentName = string.Empty;\r\n\u00a0foreach ( DataRow row indt.Rows )\r\n\u00a0{\r\n\u00a0\u00a0\u00a0\u00a0 stringsubNodeText = ByControlRB.Checked ? \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 row[\"RoleName\"].ToString() : row[\"ControlID\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0 subNodeText += \":\";\r\n\u00a0\u00a0\u00a0\u00a0 subNodeText += Convert.ToInt32( \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 row[\"Invisible\"] ) == 0 ? \" visible \" : \" not visible \";\r\n\u00a0\u00a0\u00a0\u00a0 subNodeText += \" and \";\r\n\u00a0\u00a0\u00a0\u00a0 subNodeText += Convert.ToInt32( \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 row[\"Disabled\"] ) == 0 ? \" enabled \" : \" disabled \";\r\n\r\n\u00a0\u00a0\u00a0\u00a0 subNode = new TreeNode( subNodeText );\r\n\u00a0\u00a0\u00a0\u00a0 stringdataName = ByControlRB.Checked ? \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 row[\"ControlID\"].ToString() : row[\"RoleName\"].ToString();\r\n\u00a0\u00a0\u00a0\u00a0 if( currentName != dataName )\r\n\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parentNode = new TreeNode( dataName );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 currentName = dataName;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 PermissionTree.Nodes.Add( parentNode );\r\n\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0 if ( parentNode != null )\r\n\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 parentNode.Nodes.Add( subNode );\r\n\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0}\r\n\u00a0PermissionTree.EndUpdate();<\/pre>\n<p>We&#8217;ve chosen to have the sub-nodes tell whether the control is invisible or disabled no matter what the view, allowing for displays as shown in figures 9 and 10:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig009_CurrentStatusByContr.gif\" alt=\"338-Fig009_CurrentStatusByContr.gif\" \/>\u00a0\u00a0\u00a0\u00a0\u00a0 <img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/338-Fig010_CurrentStatusByRole.gif\" alt=\"338-Fig010_CurrentStatusByRole.gif\" \/><\/p>\n<p class=\"caption\"><em>Figure 9 Current Status by Control\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Figure 10 Current Status By Role<\/em><\/p>\n<h3>Adding a new restriction to a control<\/h3>\n<p>At this point, all we&#8217;ve done (hard to believe!) is finish the constructor. The dialog is displayed with the name of the page we&#8217;re setting permissions for, the controls for that page are displayed, the roles are displayed, and any previously recorded restrictions are displayed. In addition, the application page&#8217;s ToolTip&#8217;s have been adjusted to display the name of each control when you hover over the control (all of this was shown in figure 7).<\/p>\n<p>Assume the administrator selects a control (e.g., <b>InternationalRB<\/b>) and then selects two roles (e.g., <b>User<\/b> and <b>Technician<\/b>) and checks <b>Disable<\/b> and <b>Save<\/b>. The user&#8217;s intent is to restrict all members in the <b>User<\/b> role and the <b>Technician<\/b> role from having the <b>International<\/b> radio button being enabled when they view the <b>Potential Client<\/b> application page.<\/p>\n<p>When the administrator clicks <b>Save<\/b>, control will jump to the <b>Save_Click<\/b> event handler. Once again we&#8217;ll retrieve the connection settings and open a connection. The code then iterates through each of the selected items in the <b>PageControls<\/b> list box extracting the string representing the <b>controlID<\/b> of the control that was selected (<b>InternatioanlRB<\/b>) and then within that loop iterates through each of the selected items in the <b>PermissionRoles<\/b> list box, retrieving the <b>DataRowViews<\/b> corresponding to the selected items (remember that the <b>PermissionRoles<\/b> list box was populated through data binding).<\/p>\n<p>With these in hand, you are ready to create records in <b>ControlsToRoles<\/b> which you will do by calling the stored procedure <b>spInsertNewControlToRole<\/b>, shown here:<\/p>\n<pre>PROCEDUREspInsertNewControlToRole\r\n@RoleID int,\r\n@PageName varchar(50),\r\n@ControlID varchar(50),\r\n@invisible int,\r\n@disabled int\r\nAS\r\nBEGIN\r\nBegin Transaction\r\n\r\n\u00a0\u00a0 if not exists (select * fromControls \r\n\u00a0\u00a0 where Page = @PageName and ControlID = @ControlID)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 Insert into Controls (Page, ControlID)\r\nvalues (@PageName, @ControlID)\r\n\u00a0\u00a0 if @@Error &lt;&gt; 0 gotoErrorHandler\r\n\r\n\u00a0\u00a0 insert into ControlsToRoles (FKRole, FKPage, FKControlID,\r\ninvisible, disabled)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 values (@RoleID, @PageName, @ControlID,\r\n@invisible, @disabled)\r\n\r\n\u00a0\u00a0 if @@Error &lt;&gt; 0 gotoErrorHandler\r\n\r\n\u00a0\u00a0 commit transaction\r\n\u00a0\u00a0 return\r\n\r\nErrorHandler:\r\n\u00a0\u00a0 rollback transaction\r\n\u00a0\u00a0 return\r\n\r\nEND<\/pre>\n<p>Note first that this stored procedure uses transactions to ensure that either the <b>Control<\/b> row is added (if needed) and the <b>ControlsToRoles<\/b> row is added, or neither is added. Second, the stored procedure checks whether the table already has an entry for this control\/page combination and only attempts to insert one if it does not already exist.<\/p>\n<p>The <b>ControlsToRoles<\/b> row does double duty; it manages the relation between a control and a role and it manages the state for that relationship (is invisible set? Is disabled set?). While this may be in some ways counterintuitive, it ensures that (1) a role can have only one relationship with any given control and (2) when you set a control invisible for two roles, and then set it visible for one of the roles you do not inadvertently set it visible for the other. That is, the relationship between a role and a control (and the state of that control) is atomic.<\/p>\n<p>The code to call this stored procedure is shown here:<\/p>\n<pre>private void Save_Click( object sender, EventArgse )\r\n{\r\n\r\n\u00a0\u00a0ConnectionStringSettingsCollectionconnectionStrings =\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ConfigurationManager.ConnectionStrings;\r\n\r\n\u00a0\u00a0SqlConnection conn = new SqlConnection( connString );\r\n\u00a0\u00a0conn.Open();\r\n\u00a0\u00a0SqlParameterparam;\r\n\r\n\u00a0\u00a0foreach ( String controlID inPageControls.SelectedItems )\r\n\u00a0\u00a0{\r\n\u00a0\u00a0\u00a0 foreach ( DataRowView roleRow inPermissionRoles.SelectedItems )\r\n\u00a0\u00a0\u00a0 {\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 int roleID = Convert.ToInt32( roleRow[\"RoleID\"] );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 try\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 SqlCommand cmd = new SqlCommand();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmd.Connection = conn;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmd.CommandText = \"spInsertNewControlToRole\";\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 cmd.CommandType = CommandType.StoredProcedure;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param = cmd.Parameters.Add( \"@RoleID\", SqlDbType.Int );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Value = roleID;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Direction = ParameterDirection.Input;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param = cmd.Parameters.Add( \"@PageName\",\r\nSqlDbType.VarChar, 50 );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Value = workingForm.Name.ToString();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Direction = ParameterDirection.Input;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param = cmd.Parameters.Add( \"@ControlID\",\r\nSqlDbType.VarChar, 50 );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Value = controlID;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Direction = ParameterDirection.Input;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param = cmd.Parameters.Add( \"@invisible\", SqlDbType.Int );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Value = InVisible.Checked ? 1 : 0;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Direction = ParameterDirection.Input;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param = cmd.Parameters.Add( \"@disabled\", SqlDbType.Int );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Value = Disabled.Checked ? 1 : 0;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 param.Direction = ParameterDirection.Input;\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 introwsInserted = cmd.ExecuteNonQuery();\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if( rowsInserted &lt; 1 || rowsInserted &gt; 2 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DisplayError( \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 controlID, roleID, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \"Rows inserted = \"+ \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 rowsInserted.ToString() );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 catch ( Exception ex )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0DisplayError( controlID, roleID, ex.Message );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n\u00a0 conn.Close();\r\n\u00a0 PopulatePermissionTree();\r\n}<\/pre>\n<p>Once the new row(s) is inserted, we call <b>PopulatePermissionTree<\/b> to repopulate the permission tree to reflect the change and give positive feedback to the user.<\/p>\n<h3>Clean up on exit<\/h3>\n<p>When the administrator is finished setting restrictions, the <b>ManagePermissions<\/b> page is closed. An event is fired as the page is closed (<b>FormClosing<\/b>) which we trap, providing us an opportunity to reset the Tooltips for the form that we were setting permissions for:<\/p>\n<pre>private voidManagePermissions_FormClosing(\r\n\u00a0\u00a0\u00a0 object sender, FormClosingEventArgse)\r\n{\r\n\u00a0\u00a0 foreach ( Control c inworkingForm.Controls )\r\n\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ( c is MenuStrip)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 MenuStrip ms = c as MenuStrip;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RestoreMenuStripToolTips(ms.Items);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0 formToolTip1.Active = true;\r\n\u00a0\u00a0 formToolTip2.Active = false;\r\n}<\/pre>\n<p>The menu item tool tips are restored through the recursive method <b>RestoreMenuStripToolTips<\/b>:<\/p>\n<pre>private void RestoreMenuStripToolTips( ToolStripItemCollection\r\ntoolStripItems )\r\n{\r\n\u00a0\u00a0\u00a0 foreach ( ToolStripMenuItem mi intoolStripItems )\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if( mi.DropDownItems.Count &gt; 0 )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 RestoreMenuStripToolTips( mi.DropDownItems );\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if( oldMenuToolTips.ContainsKey( mi.Name ) )\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mi.ToolTipText = <span class=\"UserInput\"><strong>oldMenuToolTips<\/strong><\/span>[mi.Name];\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 mi.ToolTipText = string.Empty;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ end else\r\n\u00a0\u00a0\u00a0 }\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ end foreach\r\n}\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \/\/ end RestoreMenuStripToolTips<\/pre>\n<p><b>RestoreMenuStripToolTips<\/b> recurses down to leaf menu items and then retrieves their value from the dictionary into which we stashed them in <b>ShowToolStipItems<\/b> which we called from <b>ShowControls<\/b> which was called from the constructor.<\/p>\n<p>Form closing then makes our special <b>ToolTips<\/b> object inactive and reactivates the normal <b>ToolTips<\/b> object and the form is back to normal. The database is fully updated and it is up to the form designer to check the <b>ControlsToRoles<\/b> table to ensure that the current user&#8217;s role does not prohibit displaying or enabling any given control.<\/p>\n<h2>The Debugger is your friend<\/h2>\n<p>Rather than creating the forms from scratch, an effective way to understand this project is to download the source and put it in the debugger. The focus is not on the two main forms (which are implemented only enough to provide a context for the control-based security) but rather on <b>ManagePermissions.cs<\/b> and <b>ManageRoles.cs<\/b>.<\/p>\n<p>The interaction between the two Management forms and the underlying database (<b>ControlSecurity<\/b>) is where all the action is, and understanding the code-behind for these pages is critical. (The database and its stored procedure can be created by running <b>ControlSecurity.sql<\/b>)<\/p>\n<p>Within <b>ManagePermissions.cs<\/b>, pay particular attention to the manipulation of the ToolTips and also to <b>ShowControls<\/b> and the invocation of the stored procedure <b>spInsertNewControlToRow<\/b>.<\/p>\n<p>Similarly, in <b>ManageRoles.cs<\/b>, pay particular attention to the invocation of <b>spInsertNewUserInRole<\/b> and make sure you are comfortable with how these relationships are created and what they do.<\/p>\n<h2>Wrap-up<\/h2>\n<p>The goal of this article was not to provide a complete solution, but rather, to demonstrate an approach that utilizes the ability to find all the controls on the page, to assist in finding the names of each control at run time by taking over the tool tips, and by storing the permissions in a database. This is an approach I&#8217;ve used with some success for clients who need control-by-control security in their applications.<\/p>\n<p>Since I&#8217;ve left you with work to do to create a full, working application, let me compensate by saying that I also leave you with support. Please post any questions you may, or difficulties that you encounter, in the comments at the end of this article and\/or on the support forum to <a href=\"http:\/\/www.jliberty.com\/\">my web site<\/a> (clicking on books, and then clicking on &#8220;Free Support Forum&#8221;).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Jesse Liberty demonstrates a role-based security architecture for Windows Forms applications that will allow you to restrict access to any given control, on any form, so that it is either invisible or disabled, based on who is using the form.&hellip;<\/p>\n","protected":false},"author":99241,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4620,4621,4622,4619,4179,4623,4316],"coauthors":[44569],"class_list":["post-215","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-controls-based-security","tag-permissions","tag-roles","tag-security","tag-source-control","tag-users","tag-windows-forms"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/215","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/99241"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=215"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/215\/revisions"}],"predecessor-version":[{"id":72890,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/215\/revisions\/72890"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=215"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=215"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=215"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=215"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}