My MVC application makes use of a User's Role in multiple places during individual page requests. My question is whether the default SqlRoleProvider caches the current User's Roles for the lifetime of a page-request?
For example, I make use of Roles in attributes on Controller methods:
[Authorize(Roles = "Admin")]
and custom code
if (user.IsInRole(MembershipRole.Admin))
{
// Do something
}
else if (user.IsInRole(MembershipRole.Printer))
{
// Do something else
}
If the Role Provider does not cache roles, is the best solution to write a custom Role Provider that inherits from the default one, and override the methods to get the Roles once and cache them for the Request duration? Can this be done in a way that both the Authorize attribute and my own code will make use of the cached roles?
(In case you were wondering, I don't want to use the cacheRolesInCookie web.config option to cache the roles in cookies).
Thanks in advance for any suggestions.
[Edit to include details triggered from Joe's answer]
I decompiled System.Web.Mvc.AuthorizeAttribute and the AuthorizeCore method calls the following method for each role to be checked:
httpContext.User.IsInRole
Then peering into System.Web.Security.RolePrincipal (which is what "User" is above) both the methods below do indeed use a cached copy of the User's roles (or populates the cache if empty):
public string[] GetRoles()
public bool IsInRole(string role)
The cache is stored as a field on User, so its lifetime is for the duration of the request.
The methods find the roles using:
Roles.Providers[this._ProviderName].GetRolesForUser(this.Identity.Name)
so will use whatever role provider you have chosen for the application (default or custom).
If you use a RoleProvider
in an ASP.NET or ASP.NET MVC application, then HttpContext.User
will reference a RolePrincipal
which does cache roles for the lifetime of the request.
However, in a WCF service that uses ASP.NET roles:
<behavior ...>
<serviceAuthorization principalPermissionMode ="UseAspNetRoles"
roleProviderName ="MyRoleProvider" />
</behavior>
this is not true: instead HttpContext.User
will reference the internal class System.ServiceModel.Security.RoleProviderPrincipal
, which does not cache roles: instead it always calls RoleProvider.IsUserInRole
.
The out-of-the-box RoleProviders don't do any caching, so this can result in repeated connections to the underlying data store. It seems like a deficiency to me: it would have been easy to cache the roles on first access.
is the best solution to write a custom Role Provider that inherits from the default one, and override the methods to get the Roles once and cache them for the Request duration?
Not necessary for ASP.NET or ASP.NET MVC, but could be envisaged for WCF. Caching for the Request duration will presumably use HttpContext.Items
, so will introduce a dependency on the existence of HttpContext
, but that's not necessarily a problem except for making unit testing harder.
Can this be done in a way that both the Authorize attribute and my own code will make use of the cached roles?
If you configure your custom RoleProvider
in web.config there's nothing more you need to do so that the Authorize
attribute will use it.