We can access session data in controllers and views like this:
Session["SessionKey1"]
How do you access Session values from a class other than a controller or view?
I'd use dependency injection and pass the instance of the HttpContext (or just the session) to the class that needs access to the Session. The other alternative is to reference HttpContext.Current, but that will make it harder to test since it's a static object.
public ActionResult MyAction()
{
var foo = new Foo( this.HttpContext );
...
}
public class Foo
{
private HttpContextBase Context { get; set; }
public Foo( HttpContextBase context )
{
this.Context = context;
}
public void Bar()
{
var value = this.Context.Session["barKey"];
...
}
}
You just need to call it through the HttpContext like so:
HttpContext.Current.Session["MyValue"] = "Something";
Here is my version of a solution for this problem. Notice that I also use a dependency injection as well, the only major difference is that the "session" object is accessed through a Singleton
private iSession _Session;
private iSession InternalSession
{
get
{
if (_Session == null)
{
_Session = new SessionDecorator(this.Session);
}
return _Session;
}
}
Here is the SessionDecorator class, which uses a Decorator pattern to wrap the session around an interface :
public class SessionDecorator : iSession
{
private HttpSessionStateBase _Session;
private const string SESSIONKEY1= "SESSIONKEY1";
private const string SESSIONKEY2= "SESSIONKEY2";
public SessionDecorator(HttpSessionStateBase session)
{
_Session = session;
}
int iSession.AValue
{
get
{
return _Session[SESSIONKEY1] == null ? 1 : Convert.ToInt32(_Session[SESSIONKEY1]);
}
set
{
_Session[SESSIONKEY1] = value;
}
}
int iSession.AnotherValue
{
get
{
return _Session[SESSIONKEY2] == null ? 0 : Convert.ToInt32(_Session[SESSIONKEY2]);
}
set
{
_Session[SESSIONKEY2] = value;
}
}
}`
Hope this helps :)
Haven't done it myself, but this sample from Chad Meyer's blog might help (from this post: http://www.chadmyers.com/Blog/archive/2007/11/30/asp.net-webforms-and-mvc-in-the-same-project.aspx)
[ControllerAction]
public void Edit(int id)
{
IHttpSessionState session = HttpContext.Session;
if (session["LoggedIn"] == null || ((bool)session["LoggedIn"] != true))
RenderView("NotLoggedIn");
Product p = SomeFancyDataAccess.GetProductByID(id);
RenderView("Edit", p);
}
I would also wrap all session variables into a single class file. That way you can use intelliSense to select them. This cuts down on the number of paces in code where you need to specify the "strings" for the session.
I have read lots of information about page caching and partial page caching in a MVC application. However, I would like to know how you would cache data.
In my scenario I will be using LINQ to Entities (entity framework). On the first call to GetNames (or whatever the method is) I want to grab the data from the database. I want to save the results in cache and on the second call to use the cached version if it exists.
Can anyone show an example of how this would work, where this should be implemented (model?) and if it would work.
I have seen this done in traditional ASP.NET apps , typically for very static data.
Here's a nice and simple cache helper class/service I use:
using System.Runtime.Caching;
public class InMemoryCache: ICacheService
{
public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
{
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
}
return item;
}
}
interface ICacheService
{
T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}
Usage:
cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));
Cache provider will check if there's anything by the name of "cache id" in the cache, and if there's not, it will call a delegate method to fetch data and store it in cache.
Example:
var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
string[] names = Cache["names"] as string[];
if(names == null) //not in cache
{
names = DB.GetNames();
Cache["names"] = names;
}
return names;
}
A bit simplified but I guess that would work. This is not MVC specific and I have always used this method for caching data.
I'm referring to TT's post and suggest the following approach:
Reference the System.Web dll in your model and use System.Web.Caching.Cache
public string[] GetNames()
{
var noms = Cache["names"];
if(noms == null)
{
noms = DB.GetNames();
Cache["names"] = noms;
}
return ((string[])noms);
}
You should not return a value re-read from the cache, since you'll never know if at that specific moment it is still in the cache. Even if you inserted it in the statement before, it might already be gone or has never been added to the cache - you just don't know.
So you add the data read from the database and return it directly, not re-reading from the cache.
For .NET 4.5+ framework
add reference: System.Runtime.Caching
add using statement:
using System.Runtime.Caching;
public string[] GetNames()
{
var noms = System.Runtime.Caching.MemoryCache.Default["names"];
if(noms == null)
{
noms = DB.GetNames();
System.Runtime.Caching.MemoryCache.Default["names"] = noms;
}
return ((string[])noms);
}
In the .NET Framework 3.5 and earlier versions, ASP.NET provided an in-memory cache implementation in the System.Web.Caching namespace. In previous versions of the .NET Framework, caching was available only in the System.Web namespace and therefore required a dependency on ASP.NET classes. In the .NET Framework 4, the System.Runtime.Caching namespace contains APIs that are designed for both Web and non-Web applications.
More info:
https://msdn.microsoft.com/en-us/library/dd997357(v=vs.110).aspx
https://learn.microsoft.com/en-us/dotnet/framework/performance/caching-in-net-framework-applications
Steve Smith did two great blog posts which demonstrate how to use his CachedRepository pattern in ASP.NET MVC. It uses the repository pattern effectively and allows you to get caching without having to change your existing code.
http://ardalis.com/Introducing-the-CachedRepository-Pattern
http://ardalis.com/building-a-cachedrepository-via-strategy-pattern
In these two posts he shows you how to set up this pattern and also explains why it is useful. By using this pattern you get caching without your existing code seeing any of the caching logic. Essentially you use the cached repository as if it were any other repository.
I have used it in this way and it works for me.
https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx
parameters info for system.web.caching.cache.add.
public string GetInfo()
{
string name = string.Empty;
if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
{
name = GetNameMethod();
System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
}
else
{
name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
}
return name;
}
AppFabric Caching is distributed and an in-memory caching technic that stores data in key-value pairs using physical memory across multiple servers. AppFabric provides performance and scalability improvements for .NET Framework applications. Concepts and Architecture
Extending #Hrvoje Hudo's answer...
Code:
using System;
using System.Runtime.Caching;
public class InMemoryCache : ICacheService
{
public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
{
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
{
string cacheKey = string.Format(cacheKeyFormat, id);
TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
if (item == null)
{
item = getItemCallback(id);
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
}
return item;
}
}
interface ICacheService
{
TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}
Examples
Single item caching (when each item is cached based on its ID because caching the entire catalog for the item type would be too intensive).
Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);
Caching all of something
IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);
Why TId
The second helper is especially nice because most data keys are not composite. Additional methods could be added if you use composite keys often. In this way you avoid doing all sorts of string concatenation or string.Formats to get the key to pass to the cache helper. It also makes passing the data access method easier because you don't have to pass the ID into the wrapper method... the whole thing becomes very terse and consistant for the majority of use cases.
Here's an improvement to Hrvoje Hudo's answer. This implementation has a couple of key improvements:
Cache keys are created automatically based on the function to update data and the object passed in that specifies dependencies
Pass in time span for any cache duration
Uses a lock for thread safety
Note that this has a dependency on Newtonsoft.Json to serialize the dependsOn object, but that can be easily swapped out for any other serialization method.
ICache.cs
public interface ICache
{
T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}
InMemoryCache.cs
using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;
public class InMemoryCache : ICache
{
private static readonly object CacheLockObject = new object();
public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
{
string cacheKey = GetCacheKey(getItemCallback, dependsOn);
T item = MemoryCache.Default.Get(cacheKey) as T;
if (item == null)
{
lock (CacheLockObject)
{
item = getItemCallback();
MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
}
}
return item;
}
private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
{
var serializedDependants = JsonConvert.SerializeObject(dependsOn);
var methodType = itemCallback.GetType();
return methodType.FullName + serializedDependants;
}
}
Usage:
var order = _cache.GetOrSet(
() => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
, new { id = orderId }
, new TimeSpan(0, 10, 0)
);
public sealed class CacheManager
{
private static volatile CacheManager instance;
private static object syncRoot = new Object();
private ObjectCache cache = null;
private CacheItemPolicy defaultCacheItemPolicy = null;
private CacheEntryRemovedCallback callback = null;
private bool allowCache = true;
private CacheManager()
{
cache = MemoryCache.Default;
callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);
defaultCacheItemPolicy = new CacheItemPolicy();
defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
defaultCacheItemPolicy.RemovedCallback = callback;
allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
}
public static CacheManager Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new CacheManager();
}
}
}
return instance;
}
}
public IEnumerable GetCache(String Key)
{
if (Key == null || !allowCache)
{
return null;
}
try
{
String Key_ = Key;
if (cache.Contains(Key_))
{
return (IEnumerable)cache.Get(Key_);
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
public void ClearCache(string key)
{
AddCache(key, null);
}
public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
{
if (!allowCache) return true;
try
{
if (Key == null)
{
return false;
}
if (cacheItemPolicy == null)
{
cacheItemPolicy = defaultCacheItemPolicy;
}
String Key_ = Key;
lock (Key_)
{
return cache.Add(Key_, data, cacheItemPolicy);
}
}
catch (Exception)
{
return false;
}
}
private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
{
String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
LogManager.Instance.Info(strLog);
}
}
I use two classes. First one the cache core object:
public class Cacher<TValue>
where TValue : class
{
#region Properties
private Func<TValue> _init;
public string Key { get; private set; }
public TValue Value
{
get
{
var item = HttpRuntime.Cache.Get(Key) as TValue;
if (item == null)
{
item = _init();
HttpContext.Current.Cache.Insert(Key, item);
}
return item;
}
}
#endregion
#region Constructor
public Cacher(string key, Func<TValue> init)
{
Key = key;
_init = init;
}
#endregion
#region Methods
public void Refresh()
{
HttpRuntime.Cache.Remove(Key);
}
#endregion
}
Second one is list of cache objects:
public static class Caches
{
static Caches()
{
Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
{
using (var context = new WordsContext())
{
return context.Languages.ToList();
}
});
}
public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}
I will say implementing Singleton on this persisting data issue can be a solution for this matter in case you find previous solutions much complicated
public class GPDataDictionary
{
private Dictionary<string, object> configDictionary = new Dictionary<string, object>();
/// <summary>
/// Configuration values dictionary
/// </summary>
public Dictionary<string, object> ConfigDictionary
{
get { return configDictionary; }
}
private static GPDataDictionary instance;
public static GPDataDictionary Instance
{
get
{
if (instance == null)
{
instance = new GPDataDictionary();
}
return instance;
}
}
// private constructor
private GPDataDictionary() { }
} // singleton
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
You can also try and use the caching built into ASP MVC:
Add the following attribute to the controller method you'd like to cache:
[OutputCache(Duration=10)]
In this case the ActionResult of this will be cached for 10 seconds.
More on this here
I would like to cache strings in memory from the database so that I do not have to access the database every time. I tried using System.Runtime.Caching, but it does not seem to work.
On the local site, all the data is cached but the user has to be authenticated on a secondary site. Once the user is authenticated, they are brought back to the local site but all the data that was cached is gone.
Is there a way to fix the above issue? Below is part of my code:
using System.Runtime.Caching;
ObjectCache cache = MemoryCache.Default;
public bool CacheIsSet(string key)
{
return cache.Contains(key);
}
public object CacheGet(string key)
{
return cache.Get(key);
}
public void CacheSet(string key, object value)
{
CacheItemPolicy policy = new CacheItemPolicy();
cache.Set(key, value, policy);
}
Thanks very much.
You should be referencing the HttpRuntime.Cache object. I created a wrapper around this, similar to what you have referenced in your question. Feel free to use it:
using System.Web.Caching;
public class CachingService
{
protected Cache Cache
{
get;
set;
}
public int CacheDurationMinutes
{
get;
set;
}
public CachingService()
{
Cache = HttpRuntime.Cache;
CacheDurationMinutes = 60;
}
public virtual object Get(string keyname)
{
return Cache[keyname];
}
public virtual T Get<T>(string keyname)
{
T item = (T)Cache[keyname];
return item;
}
public virtual void Insert(string keyname, object item)
{
Cache.Insert(keyname, item, null, DateTime.UtcNow.AddMinutes(CacheDurationMinutes), Cache.NoSlidingExpiration);
}
public virtual void Insert(string keyname, object item, CacheDependency dependency)
{
Cache.Insert(keyname, item, dependency);
}
public virtual void Remove(string keyname)
{
Cache.Remove(keyname);
}
}
Here is a sample use case. The function LoadPosts is supposed to load blog posts to display on the site. The function will first see if the posts are cached, if not it will load the posts from the database, and then cache them:
public IEnumerable<BlogPost> LoadPosts()
{
var cacheService = new CachingService();
var blogPosts = cacheService.Get<IEnumerable<BlogPost>>("BlogPosts");
if (blogPosts == null)
{
blogPosts = postManager.LoadPostsFromDatabase();
cacheService.Insert("BlogPosts", blogPosts);
}
return blogPosts;
}
The first time this function is run, the cache will return null, because we didn't add anything to the BlogPosts key yet. The second time the function is called the posts will be in the cache and the code in the if block will not run, saving us a trip to the database.
I have ASP.Net application with a simple cache helper.
Under VS web server it works fine.
Under IIS 6.0 cache doesn't work- object, was saved previos, doesn't returns after a minute (with no exception).
What could be wrong?
public static class CacheHelper
{
public static string Share<T>(T #object, TimeSpan period)
{
var uniqueKey = Guid.NewGuid().ToString();
HttpContext.Current.Cache.Add(uniqueKey, #object, null, Cache.NoAbsoluteExpiration,
period, CacheItemPriority.BelowNormal, null);
return uniqueKey;
}
public static void ShareViaCookie<T>(string key, T #object, TimeSpan period)
{
var cachedObject = GetFromCookie<T>(key);
if (ReferenceEquals(cachedObject, null))
{
var uniqueKey = Share(#object, period);
HttpContext.Current.Response.Cookies.Set(new HttpCookie(key, uniqueKey)
{
Expires = DateTime.Now.AddYears(1)
});
}
else
{
HttpContext.Current.Cache[GetKeyFromCookie(key)] = #object;
}
}
public static T GetShared<T>(string key)
{
string uniqueKey = HttpContext.Current.Request.QueryString[key];
return !string.IsNullOrEmpty(uniqueKey) ? (T)HttpContext.Current.Cache.Get(uniqueKey) : GetFromCookie<T>(key);
}
private static T GetFromCookie<T>(string key)
{
string uniqueKey = GetKeyFromCookie(key);
return !string.IsNullOrEmpty(uniqueKey) ? (T)HttpContext.Current.Cache.Get(uniqueKey) : default(T);
}
private static string GetKeyFromCookie(string key)
{
return HttpContext.Current.Request.Cookies[key]
.IIf(it => it != null, it => it.Value, it => null);
}
}
Maybe nothing is technically wrong. A cache does not mean that any object will be returned the moment after it is persisted.
If your site is caching a lot of items and the cache is not big enough it may constantly be looking for objects to remove from the cache. In this situations, sometimes the object that has only just been cached can be a good candidate to be removed. If you had room for 100 objects and the cache was full with items that had been accessed at least once, then it may never cache your new object that "has never been accessed". This does actually happen on the odd occasion.
I have a Session that stores an int. I usually do the following...
if(Session["test"] == null)
Now that I am comparing...
public ActionResult NumbersGame(int myNum)
{
if(Session["test"] != myNum)...
How do I do that?
(int)Session["test"] will fail if that session variable is either null or not a number. Use this instead...
var myNumber = Convert.ToInt32(Session["test"]);
myNumber will be 0 if 'test' is null or not a number
Cast it to an int:
if ((int) Session["test"] != myNum) ...
Another way of checking and using the value stored in your Session() object involves using the TryParse feature.
int intTest;
if (int.TryParse(Session["test"].ToString(), out intTest))
{
// intTest will have the value in Session["Test"] stored as an integer
}
I like it because it's compact and simple.
Simple overview of how I would do it:
It may solve other problems too:
First we define interface:
public interface ISessionWrapper
{
int? SomeInteger { get; set; }
}
Then we make HttpContext implementation:
public class HttpContextSessionWrapper : ISessionWrapper
{
private T GetFromSession<T>(string key)
{
return (T) HttpContext.Current.Session[key];
}
private void SetInSession(string key, object value)
{
HttpContext.Current.Session[key] = value;
}
public int? SomeInteger
{
get { return GetFromSession<int?>("SomeInteger"); }
set { SetInSession("SomeInteger", value); }
}
}
Then we define our base controller:
public class BaseController : Controller
{
public ISessionWrapper SessionWrapper { get; set; }
public BaseController()
{
SessionWrapper = new HttpContextSessionWrapper();
}
}
Finally:
public ActionResult NumbersGame(int myNum)
{
if (SessionWrapper.SomeInteger == myNum)
//Do what you want;
}
No need to cast here!! And if you wanted to test your controller, you have no problem with Session. You just Mock ISessionWrapper and pass it to SessionWrapper variable.
I would test for null (to detect Session expiry), then cast:
object value = Session["test"];
if (value == null)
{
// The value is not in Session (e.g. because the session has expired)
// Deal with this in an application-specific way, e.g. set to a default,
// reload the Session variable from the database, redirect to a home page, ...
...
}
else
{
myNumber = (int) value;
}
...
The problem with using Convert.ToInt32 is that it will simply return 0 if your session has expired, which may not be desirable depending on your application.