While writing at the amount of code required to check a user's privileges, I've often thought that there must be an easier way. What I've always loathed is reading/writing code like this:
if (user.IsInRole(@"SOMEDOMAIN\SomeSG"))
{ txtName.Enabled = true;
txtPhone.Enabled = true;
txtAddressLine1.Enabled = true;
// ...
}
else
{ txtName.Enabled = false;
txtPhone.Enabled = false;
txtAddressLine1.Enabled = false;
//...
}
Of course you could have the amount by setting the Enabled property to false by default, but that's still not good enough.
While chatting with Mitch Denny the other day he passed on to me the idea of using an Extender Provider to let the developer specify the group at design time.
So, what is an Extender Provider?
Well, that's what MSDN is for... http://msdn2.microsoft.com/en-us/library/ms171836.aspx
In short, it adds properties to a control in the Visual Studio designer at design time. And to use MSDN's example, when a ToolTip component is added to a form it provides a property called ToolTip to each control on that form.
How do we accomplish the behaviour of the above ugly code with an Extender Provider?
1. Create a Component and implement IExtenderProvider. The IExtenderProvider interface provides the CanExtend method. This allows the VS designer at runtime to determine whether a control can use the provider.
public class PermissionExtenderProvider : Component, IExtenderProvider
{ public PermissionExtenderProvider(IContainer parent)
{ parent.Add(this);
}
#region IExtenderProvider Members
bool IExtenderProvider.CanExtend(object extendee)
{ if (extendee is Control)
return true;
else
return false;
}
#endregion
}
2. Add a ProviderProperty attribute to the class. This tells the designer the name of the property to add to the list.
[ProvideProperty("EnabledRole", typeof(Control))]public class PermissionExtenderProvider : Component, IExtenderProvider
{ // ...
}
3. Create a list of controls to manage the values that have been assigned to them. This is required because there is one provider that determines what value the property will have for each control, which means that this provider needs to keep the role assigned to each control.
private Dictionary<Control, string> m_ControlRoles = new Dictionary<Control, string>();
4. Create Get & Set methods for the new property. Once again, because one instance of the provider manages all the controls that use it, the get/set methods need to accept the control as a parameter. The get method should be decorated with the Description and Category attributes. Using the Description attribute will mean the designer will display a message describing the property. Using the Category attribute will mean the designer will place the property under a certain category.
[Description("The role required by the user for the control to be enabled")][Category("Behavior")]public string GetEnabledRole(Control control)
{ if (m_ControlPermissions.ContainsKey(control))
return m_ControlRoles[control];
else
return string.Empty;
}
public void SetEnabledRole(Control control, string value)
{ ControlPermission permission;
if (m_ControlRoles.ContainsKey(control))
m_ControlRoles[control] = value;
else
m_ControlRoles.Add(control, value);
}
5. Implement ISupportInitialize to set the the enabled property depending on whether the user is in the required role. Using this interface means that the settings won't take effect until the form's InitializeComponents method calls the provider's EndInit method.
#region ISupportInitialize Members
void ISupportInitialize.BeginInit() { }void ISupportInitialize.EndInit(){ if (!DesignMode)
{ foreach (KeyValuePair<Control, string> role in m_ControlRoles)
{ Control control = role.Key;
if (string.IsNullOrEmpty(role.Value))
{ control.Enabled = true;
}
else
{ WindowsIdentity user = System.Security.Principal.WindowsIdentity.GetCurrent();
if (user.IsInRole(role.Value))
control.Enabled = true;
else
control.Enabled = false;
}
}
}
}
#endregion
Now, after adding the component to your toolbar you can just drag it on to your form and set role required for multiple controls to be enabled at design time.

This solution is also easy to customise if you want to use a GenericPricipal instead of a WindowsIdentity. By implementing a credential provider, you can replace the call to WindowsIdentity.GetCurrent() with something like Provider.GetCurrentUserPrincipal().