I have a ASP.NET MVC application with the following controller action:
public ActionResult Upload(HttpPostedFileBase fileData)
{
Dictionary<string, int> mappings = new Dictionary<string, int>();
Byte[] destination = new Byte[fileData.ContentLength];
fileData.InputStream.Position = 0;
fileData.InputStream.Read(destination, 0, fileData.ContentLength);
var returnValue = GUIDGenerator(destination);
mappings.Add(fileData.FileName, returnValue);
return Json(new { success = true });
}
Every time the user selects a file and uploads it, I want to add its name and GUID into a Dictionary type "mappings". But With every upload, the mappings re-initializes itself. How do I persists values in it?
if you really want it persistend and scalable you will have to do this in a database. But using the Session-object or other cachich-mechanisms may work for you as well.
BTW: moving the dictionary into the controller won't work as well because the controller is created everytime a method on it is called (yeah you can change this, but this is the default behaviour).
I would consider using a database.
I suppose you could make a static variable at the top of the class:
private static Dictionary<string, int> _mappings = new Dictionary<string, int>();
But that will only live with that session on the server that code ran in. You might be better off saving and reloading that data from a database.
Related
I'm looking for a real simple example of how to add an object to cache, get it back out again, and remove it.
The second answer here is the kind of example I'd love to see...
List<object> list = new List<Object>();
Cache["ObjectList"] = list; // add
list = ( List<object>) Cache["ObjectList"]; // retrieve
Cache.Remove("ObjectList"); // remove
But when I try this, on the first line I get:
'Cache' is a type, which is not valid in the given context.
And on the third line I get:
An object method is required for the non-static field blah blah blah
So, let's say I have a List<T>...
var myList = GetListFromDB()
And now I just wanna add myList to the cache, get it back out, and remove it.
.NET provides a few Cache classes
System.Web.Caching.Cache - default caching mechanizm in ASP.NET. You can get instance of this class via property Controller.HttpContext.Cache also you can get it via singleton HttpContext.Current.Cache. This class is not expected to be created explicitly because under the hood it uses another caching engine that is assigned internally.
To make your code work the simplest way is to do the following:
public class AccountController : System.Web.Mvc.Controller{
public System.Web.Mvc.ActionResult Index(){
List<object> list = new List<Object>();
HttpContext.Cache["ObjectList"] = list; // add
list = (List<object>)HttpContext.Cache["ObjectList"]; // retrieve
HttpContext.Cache.Remove("ObjectList"); // remove
return new System.Web.Mvc.EmptyResult();
}
}
System.Runtime.Caching.MemoryCache - this class can be constructed in user code. It has the different interface and more features like update\remove callbacks, regions, monitors etc. To use it you need to import library System.Runtime.Caching. It can be also used in ASP.net application, but you will have to manage its lifetime by yourself.
var cache = new System.Runtime.Caching.MemoryCache("MyTestCache");
cache["ObjectList"] = list; // add
list = (List<object>)cache["ObjectList"]; // retrieve
cache.Remove("ObjectList"); // remove
Here is the way that I've done it in the past:
private static string _key = "foo";
private static readonly MemoryCache _cache = MemoryCache.Default;
//Store Stuff in the cache
public static void StoreItemsInCache()
{
List<string> itemsToAdd = new List<string>();
//Do what you need to do here. Database Interaction, Serialization,etc.
var cacheItemPolicy = new CacheItemPolicy()
{
//Set your Cache expiration.
AbsoluteExpiration = DateTime.Now.AddDays(1)
};
//remember to use the above created object as third parameter.
_cache.Add(_key, itemsToAdd, cacheItemPolicy);
}
//Get stuff from the cache
public static List<string> GetItemsFromCache()
{
if (!_cache.Contains(_key))
StoreItemsInCache();
return _cache.Get(_key) as List<string>;
}
//Remove stuff from the cache. If no key supplied, all data will be erased.
public static void RemoveItemsFromCache(_key)
{
if (string.IsNullOrEmpty(_key))
{
_cache.Dispose();
}
else
{
_cache.Remove(_key);
}
}
EDIT: Formatting.
BTW, you can do this with anything. I used this in conjunction with serialization to store and retrieve a 150K item List of objects.
If your using MemoryCache here is a very simple example:
var cache = MemoryCache.Default;
var key = "myKey";
var value = "my value";
var policy = new CacheItemPolicy { SlidingExpiration = new TimeSpan(2, 0, 0) };
cache.Add(key, value, policy);
Console.Write(cache[key]);
I wrote LazyCache to make this as simple and painless as possible, while also making sure you only execute your cacheable function calls once, even if two threads try and cache them at the same time.
Run the following command in the Package Manager Console
PM> Install-Package LazyCache
Add the namespace at the top of you class
using LazyCache;
and now cache stuff:
// Create the cache - (in constructor or using dependency injection)
IAppCache cache = new CachingService();
// Get products from the cache, or if they are not
// cached then get from db and cache them, in one line
var products = cache.GetOrAdd("get-products", () => dbContext.Products.ToList());
// later if you want to remove them
cache.Remove("get-products");
See more on the cache aside pattern or in the the LazyCache docs
Try this third party cache: CacheCrow, it is a simple LFU based cache.
Install using powershell command in visual studio: Install-Package CacheCrow
Code Snippet:
// initialization of singleton class
ICacheCrow<string, string> cache = CacheCrow<string, string>.Initialize(1000);
// adding value to cache
cache.Add("#12","Jack");
// searching value in cache
var flag = cache.LookUp("#12");
if(flag)
{
Console.WriteLine("Found");
}
// removing value
var value = cache.Remove("#12");
For more information you can visit: https://github.com/RishabKumar/CacheCrow
private readonly Dictionary<Credentials, Data> _dataToCredentialMap =
new Dictionary<Credentials, Data>();
Credentials contains SqlCredentials and ExchangeCredentials properties.
Data have SqlData and ExchangeData and other properties.
I use the following method to update _dataToCredentialMap each minute
private void UpdateData()
{
try
{
foreach (var credential in _dataToCredentialMap.Keys)
{
var data = GetData(credential);
_dataToCredentialMap[credential] = data;
}
}
catch (Exception ex)
{
_logger.WarnException(ex, "Failed to update agent metadata");
}
}
But when I go through the _dataToCredentialMap.Keys I can have the following request situation:
first element in the dictionary contains credential1, sqlcredential2 inside key and some Data value, the second one can contains sqlcredential2, credential3 key and some Data value. So when I call GetData(credential) inside loop I call it with the same sqlcredential2 twice and collect the same Data.SqlData. I don't want to do that.
I think about adding two additional dictionary's
private readonly Dictionary<SqlCredentials, SqlData> _sqlDictionary;
private readonly Dictionary<ExchangeCredentials, ExchangeData> _exchangeDictionary;
and update it inside loop, because I have GetSqlData(credential) and GetExchengeData(credential) inside GetData(). But I dont now how to combine the result.
I believe what's happening here is that, since you are modifying the dictionary as you are going through it, its causing an error. If you fix that, you shouldn't need the try/catch statement, then it should work as expected.
You can create a new dictionary called tempDict, like so:
var tempDict;
copyDictionary(_dataToCredentialMap, tempDict);
foreach(var crediental in tempDict)
{
var data = GetData(crediental);
_dataToCredentialMap[crediental] = data;
}
Not sure if this is what you meant, the exact problem is unclear.
Edit: Added the copying of the old dictionary into the temporary one.
I thought TempData was supposed to become null after one refresh or page redirect. It takes two refreshes of my page to clear the data though which isn't what I want, how do I make it go null after 1 refresh/redirect?
#using (Html.BeginForm())
{
<div class="form-group">
<button class="btn btn-default" type="submit">test</button>
</div>
}
public void test()
{
List<int> integers = new List<int>();
integers.Add(10);
integers.Add(20);
//First Refresh and myList still has values when I want it to be null
List<int> myList = (List<int>)TempData["test"]; // Take the value from the current data variable
if (myList == null) // Not yet stored in session, create a new list and store it as a session variable
{
myList = new List<int>();
TempData.Add("test", myList);
}
myList.AddRange(integers); // Add a new entry
}
I recently went through its source code to find out how TempData works.
So the lifespan of tempdata is rather unusual as it is one request only. In order to achieve this it maintains 2 HashSets to manage keys as well as the data dictionary:
private Dictionary<string, object> _data;
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
When you read some data using an indexer or TryGetValue method it removes that key from _initalKeys collection.
public bool TryGetValue(string key, out object value)
{
_initialKeys.Remove(key);
return _data.TryGetValue(key, out value);
}
The actual dictionary that holds the data is intact at this point. That's why we can read same data consecutively without any issues. It only removes the key from _initialKeys collection, basically marking it to be deleted when the data is persisted.
If you want your values in TempData last longer you can use Peek and Keep methods. What Peek does is return the value without removing it from the _initialKeys:
public object Peek(string key)
{
object value;
_data.TryGetValue(key, out value);
return value;
}
Alternatively, you can call Keep method. Similarly it doesn't manipulate the data directly but just marks the key to be persisted by adding it to the _retainedKeys collection.
public void Keep(string key)
{
_retainedKeys.Add(key);
}
And it finally saves the data (to Session by default) by calling provider's Save method:
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
_data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
{
string key = entry.Key;
return !tempData._initialKeys.Contains(key)
&& !tempData._retainedKeys.Contains(key);
}, this);
tempDataProvider.SaveTempData(controllerContext, _data);
}
So only the data that remained in the _initialKeys collection (unread) and the ones that are specifically instructed to retain (the keys in the _retainedKeys collection) will be saved. Hence the lifespan!
Reference: TempDataDictionary source code
In Asp.Net MVC we have different techniques for state management like Viewbag,ViewData and TempData.
TempData is something special in the sense that it can hold the value even for multiple successive requests depending upon how the value is being Read in the view.
If it is a normal read then the value will become null for next request.
2.If it is a Peek read , like if you have used Peek() method of TempData then value will be retain for the next request.
3.If it is a Keep read, meaning you have used Keep() method of TempData then also the Value will be available for next request.
4.If you have not read the value in the view then the value will be retain untill it is not read.
TempData in MVC with example
If you want to keep value in TempData object after request completion, you need to call Keep method with in the current action.
tutorial
I am getting users and their data from external webservice. I cache those items because I don't want to hit web service every time. Now, If user update any of their information, I am saving it through webservice. But I don't want to get the latest data from web service as it takes lot of time. Instead I want to update my cache. Can I do that ? If so, what would be the best way ? Here is my Code
List<User> users = appSecurity.SelectUsers();
var CacheKey = string.Format("GetUserList_{0}", currentUser);
CacheFactory.AddCacheItem(CacheKey, users, 300);
CacheFactory is a class where I handle Adding, Clearing and Removing cache. Below is the code
public static void RemoveCacheItem(string key)
{
Cache.Remove(key);
}
public static void ClearCache()
{
System.Collections.IDictionaryEnumerator enumerator = Cache.GetEnumerator();
while (enumerator.MoveNext())
{
RemoveCacheItem(enumerator.Key.ToString());
}
}
public static void AddCacheItem<T>(string key, T value, double timeOutInSeconds)
{
var Item = GetCacheItem<T>(key);
if (Item != null)
{
RemoveCacheItem(key);
Item = value;
}
Cache.Insert(key, value, null, DateTime.Now.AddSeconds(timeOutInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
}
The answer is yes, it can be done. It can also be done in many different ways depending on what you want to solve. At the basic level you can create a cache by using a List<T> or Dictionary<T,T> to store your data.
When you get information from the external web-service, you push the data into your List or Dictionary. You can then use that data throughout your application. When you need to update that cache, you update the value in the List/Dictionary.
You can update your dictonary like so
Dictionary<string, int> list = new Dictionary<string, int>();
then you can set the value for the key "test" as follows
list["test"] = list["test"] + 1;
When you are ready to push the updated data to the external source. All you need to do is properly parse that data into the format the source is expecting and send away.
Like I said there are many different ways to do this, but this is a basic sample way to accomplishing it. You can use this example to build off and go from there.
Am modifying the complex function which is already written where they are using the below code :
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
// where TestData() hits the DB and returns corresponding result
Values() is called multiple places in the same file where this will return by hitting the DB TestData() first time and from next calls it will directly return from ViewBag.
Is this is a good approach ?
What are all the alternative approach we have in MVC to handle this scenario ?As DB hit is a costly call we need to use some other techniques.
Thanks
You could either keep your data in session like this:
Session['*your session key*'] = TestData();
And then retrieve it like this:
var myData = Session['*your session key*'] as YourObject //cast it to an object if you need to.
Or you could use caching:
System.Web.HttpRuntime.Cache[cacheKey] = TestData
And retrieving:
var myData =System.Web.HttpRuntime.Cache[cacheKey] as YourObject
That code should ensure that you only touch the database the first time the method is invoked.
If the same data is used on multiple pages you could also have a lot at the Cache- or Session class.
If size of the data retrieved from database is not very big then you can use Cache
Otherwise you can store data in Session as well.
You have the options to keep the data like Session, Cache.
[OutputCache(Duration = 60)] // Caches for 60 seconds
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
MVC Model Binder
See Sample code