Reputation: 129
I would really appreciate some advices in order to help me with object caching. Here is my needs : I want to filters some actions of my controllers accordingly to user roles stored in SQL database.
What i'm doing now is : When first request of current user is done i generate an object with some properties initiliazed by SQL queries. I'm storing this object into session using an custom provider and i'm using custom role provider to handle authorization filters tags. If session expire user is regenarated.. It is something like that (simplified) :
User class
Public class User
public property Login as string
public property IsAdmin as boolean
public sub Init(byval pLogin as string)
Login = pLogin
//Do some logic on database to provides roles....
IsAdmin = dbReturnIsAdmin
end sub
public readonly property RolesList as string()
Get
Return New String() {If(IsAdmin, "UserIsAdmin", "")}
End get
end property
End class
Session user provider
Public Class SessionProvider
Private Const SESSION_USER As String = "SESSION_USER"
Public Shared Sub ReloadUser()
//'This instruction initiate user and load roles into an User class type object
HttpContext.Current.Session(SESSION_USER) = StructureService.GetInitializedUser(My.User.Name, UowProvider.StructureUow)
End Sub
Public Shared ReadOnly Property User() As Application.User
Get
//'If user doesn't exist so we create an user
If HttpContext.Current.Session(SESSION_USER) Is Nothing Then ReloadUser()
//'Return user
Return CType(HttpContext.Current.Session(SESSION_USER), Application.User)
End Get
End Property
End Class
Custom role provider
Public Class AuthentifiedRoleProvider
Inherits RoleProvider
//Implement base role provider....
Public Overrides Function GetRolesForUser(username As String) As String()
return SessionProvider.User.RolesList
End Function
End Class
Implementation - WebConfig
<system.web> .....
<roleManager cacheRolesInCookie="false" defaultProvider="DefaultRoleProvider" enabled="true">
<providers>
<clear />
<add name="DefaultRoleProvider" type="AuthentifiedRoleProvider" />
</providers>
</roleManager>
</system.web>
Implementation - Controller
<Authorize(Roles:="UserIsAdmin")>
Public Function List_Items() As ActionResult
Return View()
End Function
It is working...
But, i wonder if it's really a good way to achieve that. As i have a sitemap on my app that refers to controllers actions, sessionprovider user (and http session by the way) is requested 4 or 5 times each menu load.
So, my questions are :
Many thanks !
Upvotes: 1
Views: 1013
Reputation: 129
Thanks to advices i've changed my session user provider to use caching. It must be extended but for now i can see the change !
Session user provider
Public Class UserProvider
Private Const USER_CACHE_PREFIX As String = "User|"
Private Shared Sub AddUserToCache(ByVal pLogin As String, ByVal pUser As Application.User)
Dim objCache As ObjectCache = MemoryCache.Default
objCache.Add(USER_CACHE_PREFIX & pLogin, pUser, New CacheItemPolicy With {.SlidingExpiration = TimeSpan.FromSeconds(20)})
End Sub
Private Shared Function GetUserFromCache(ByVal pLogin As String) As Application.User
Dim objCache As ObjectCache = MemoryCache.Default
//Return cache if exists
If objCache.Contains(USER_CACHE_PREFIX & pLogin) Then
Return CType(objCache.GetCacheItem(USER_CACHE_PREFIX & pLogin).Value, Application.User)
Else
Return Nothing
End If
End Function
Public Shared Function ReloadUser(ByVal pLogin As String) As Application.User
Dim objCache As ObjectCache = MemoryCache.Default
Dim tmpLogin As String = My.User.Name
//Clear cache
If objCache.Contains(USER_CACHE_PREFIX & tmpLogin) Then objCache.Remove(USER_CACHE_PREFIX & pLogin)
Dim tmpUser As Application.User = StructureService.GetInitializedUser(pLogin, UowProvider.StructureUow)
AddUserToCache(tmpLogin, tmpUser)
return tmpUser
End Function
Public Shared ReadOnly Property User() As Application.User
Get
Dim tmpLogin As String = My.User.Name
//Try to get user from cache
Dim tmpUser As Application.User = GetUserFromCache(tmpLogin)
//If user is null then init and cache
If tmpUser Is Nothing Then
tmpUser = StructureService.GetInitializedUser(tmpLogin, UowProvider.StructureUow)
AddUserToCache(tmpLogin, tmpUser)
End If
//return user
Return tmpUser
End Get
End Property
End Class
Upvotes: 0
Reputation: 56849
Anyway, do you think this implementation is a good to achieve my goals ?
Probably not.
MvcSiteMapProvider
does not rely on Session State. It uses a shared cache to store the nodes in RAM, and it uses AuthorizeAttribute
to determine which nodes to show/hide per request.If you find you are requesting the same data multiple times per request, you should try to take advantage of the request cache (HttpContextBase.Items
) by using a standard cache retrieval pattern similar to this:
Public Function GetSomeData() As ISomeData
Dim key As String = "SomeDataKey"
' Me.cache refers to HttpContextBase.Items injected through
' the constructor of this class and stored in a private field
Dim someData As ISomeData = TryCast(Me.cache(key), ISomeData)
If someData Is Nothing Then
' The data is not cached, so look it up and populate the cache
someData = GetDataFromExternalSource()
Me.cache(key) = someData
End If
Return someData
End Function
Putting a method like this into a service that is shared between components means you don't have to worry about retrieving the same data multiple times in the request - the first time it will hit the external source, and each additional time it will use the cache.
Also, per MSDN:
There are two primary reasons for creating a custom role provider.
- You need to store role information in a data source that is not supported by the role providers included with the .NET Framework, such as a FoxPro database, an Oracle database, or other data sources.
- You need to manage role information using a database schema that is different from the database schema used by the providers that ship with the .NET Framework. A common example of this would be role data that already exists in a SQL Server database for a company or Web site.
So, if you are not doing either of these things, that might not be the right choice either. Most modern applications can use/extend ASP.NET Identity for user authentication and all MVC applications should use the AuthorizeAttribute (or a subclass of it) for authorization.
System.Runtime.Cache.ObjectCache
(and MemoryCache
) are good ways to cache data that is generally not shared between users. You can use it for user data if you design your cache key to include a unique identifier for the user along with a delimiter that makes the key unique...
Dim key As String = UserId & "|UserProfile"
That said, you should be aware that caching this way won't scale to multiple servers.
Anyway, I recommend you follow the advice in think twice about using Session State. MVC has freed us from having to use Session state in many cases - and we should not use it unless it is absolutely required.
Upvotes: 1