Save Process Object Information for use after its exit - c#

On my C# Widows form, I want to make use of the Process object information even after its exit, but I am getting the exception “System process has exited, so the requested information is not available”.
What I tried thus far is to save it as a var and tag it with my ListView item, but it still throws the same exception.
//ListView Parameters (omitted redundant ones)
Process currentProcess = Process.GetProcessById(Convert.ToInt32(processStringID);
newListViewItem.Tag = currentProcess;
listView.Items.Add(newListViewItem);
I have an event of selected index changed, so when the user clicks on the ListView Item, it should show information about the process that was tagged with the item even if it has already exited.
private void listView_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
Process processItem = (Process)listView.SelectedItems[0].Tag;
//Sample of getting process information (Error happens here)
MessageBox.Show(processItem.Name + processItem.VersionInfo);
}
catch (Exception ex)
{
throw ex;
}
}
Tldr; I need a way to save the entire Process object so I can get it's information later even if the process has exited. I am open to ideas as to how this can be implemented. Please assist me, as I am unable to think of any solution with my current understanding on programming.

Store it in a Session,
To store data:
Session["process"] = processItem;
To pull data from session:
var process = (Process)Session["process"]; // Don't forget to cast back to it's original type
Data is available even if you navigate to other pages unless you manually remove it.
Update:
Since question is not clear at first.
Create a global variable using static classes
public static class GlobalVar
{
/// <summary>
/// Global variable that is constant.
/// </summary>
public const string GlobalString = "Important Text";
/// <summary>
/// Static value protected by access routine.
/// </summary>
static int _globalValue;
/// <summary>
/// Access routine for global variable.
/// </summary>
public static int GlobalValue
{
get
{
return _globalValue;
}
set
{
_globalValue = value;
}
}
/// <summary>
/// Global static field.
/// </summary>
public static bool GlobalBoolean;
}
See this post : http://www.dotnetperls.com/global-variable

Related

Preserving reference in properties assigned to parameters

I am putting together a wizard that has multiple pages that the user is shown. I need one page to be able access data from a user choice on a previous page. My idea was to just pass in a parameter by reference into the constructors of both pages, and then assign a property to that parameter, but the change isn't persisting between pages. I'm assuming it means I am incorrectly using ref.
I cannot pass data directly to the methods themselves as they are controlled by the wizard host.
Host initialization:
WizardHost host = new WizardHost();
using (host)
{
host.Text = Migration.Properties.Resources.AppName;
host.ShowFirstButton = false;
host.ShowLastButton = false;
host.WizardCompleted += new WizardHost.WizardCompletedEventHandler(this.Host_WizardCompleted);
Reference<DBManip> dbControllerRef = new Reference<DBManip>();
bool exportPathActive = false;
host.WizardPages.Add(1, new Page1());
host.WizardPages.Add(2, new Page2(dbControllerRef));
host.WizardPages.Add(3, new Page3(dbControllerRef, ref exportPathActive));
host.WizardPages.Add(4, new Page4(dbControllerRef, ref exportPathActive));
host.WizardPages.Add(5, new Page5());
host.LoadWizard();
host.ShowDialog();
Page where ref is linked with property:
public Page3(Reference<DBManip> dbControllerRef, ref bool exportPathActive)
{
this.InitializeComponent();
this.DBControllerRef = dbControllerRef;
this.Page3Body.Text = Migration.Properties.Resources.Page3Body;
this.ExportPathActiveRef = exportPathActive;
}
public Reference<DBManip> DBControllerRef
{
get;
private set;
}
If I modify exportPathActive in the constructor that modification is preserved in the next page, but a property that is assigned to the passed parameter doesn't preserve the reference. I'm pretty new to C#, so this is probably something silly I'm missing, but I can't find it on Google or looking around SO.
I have decided on just making a class called PersistentData with a property called ExportPathActive, and then passing that. It works well and I can expand it to hold more data if needed. I'll wait to approve this in case a more elegant approach is posted.
The Class:
/// <summary>
/// A store to pass data between pages.
/// </summary>
public class PersistentData
{
/// <summary>
/// Initializes a new instance of the <see cref="PersistentData"/> class.
/// </summary>
public PersistentData()
{
this.ExportPathActive = false;
}
/// <summary>
/// Gets or sets a value indicating whether [export path active].
/// </summary>
/// <value>
/// <c>true</c> if [export path active]; otherwise, <c>false</c>.
/// </value>
public bool ExportPathActive { get; set; }
}

Mutex doesn't appear to release even after I release it

I have a number of services that read xml files. To make sure that there is no collisions I use a mutex. For whatever reason, if all of my services are run by the same user there is no problem. However, if there are different users running these services, even after one service has released the mutex, the other gets the following exception when calling enter route mutex "Unhandled Exception: System.TypeInitializationException: The type initializer for 'createMutex.Program' threw an exception. ---> System.UnauthorizedAccessException: Access to the path 'RETEST_MUTEX' is denied."
public static readonly String ROUTE_MUTEX_STRING = "RETEST_MUTEX";
private static Mutex _routeMutex = new Mutex(false, ROUTE_MUTEX_STRING);
/// <summary>
/// Thin wrapper around the static routeMutex WaitOne method
/// Always call ExitRouteMutex when done in protected area
/// </summary>
/// <param name="millis_timeout"></param>
/// <returns>true if signaled, like WaitOne</returns>
public static bool EnterRouteMutex(int millis_timeout)
{
try
{
return _routeMutex.WaitOne(millis_timeout, false);
}
catch (AbandonedMutexException ame)
{
// swallow this exception - don't want to depend on other apps being healthy - like pre .NET 2.0 behavior
// data integrity will be checked
return _routeMutex.WaitOne(millis_timeout, false);
}
}
public static void ExitRouteMutex()
{
try
{
_routeMutex.ReleaseMutex();
}
catch (ApplicationException)
{
// swallow, reduce complexity to client
}
}
static void Main(string[] args)
{
Console.WriteLine("Start");
bool get = EnterRouteMutex(1000);
System.Console.WriteLine("Mutex created Press enter " + get.ToString());
Console.ReadLine();
ExitRouteMutex();
Console.WriteLine("Mutex Release");
System.Console.WriteLine("Press enter");
Console.ReadLine();
}
Here is an example of doing a cross-process Mutex.
http://msdn.microsoft.com/en-us/library/c41ybyt3.aspx
It handles the use of Mutex.OpenExisting and also demonstrates the security aspect mentioned by cdhowie.

Thread-safe enumeration of shared memory that can be updated or deleted

I have a shared object between threads that is used to hold file state information. The object that holds the information is this class:
/// <summary>
/// A synchronized dictionary class.
/// Uses ReaderWriterLockSlim to handle locking. The dictionary does not allow recursion by enumeration. It is purly used for quick read access.
/// </summary>
/// <typeparam name="T">Type that is going to be kept.</typeparam>
public sealed class SynchronizedDictionary<U,T> : IEnumerable<T>
{
private System.Threading.ReaderWriterLockSlim _lock = new System.Threading.ReaderWriterLockSlim();
private Dictionary<U, T> _collection = null;
public SynchronizedDictionary()
{
_collection = new Dictionary<U, T>();
}
/// <summary>
/// if getting:
/// Enters read lock.
/// Tries to get the value.
///
/// if setting:
/// Enters write lock.
/// Tries to set value.
/// </summary>
/// <param name="key">The key to fetch the value with.</param>
/// <returns>Object of T</returns>
public T this[U key]
{
get
{
_lock.EnterReadLock();
try
{
return _collection[key];
}
finally
{
_lock.ExitReadLock();
}
}
set
{
Add(key, value);
}
}
/// <summary>
/// Enters write lock.
/// Removes key from collection
/// </summary>
/// <param name="key">Key to remove.</param>
public void Remove(U key)
{
_lock.EnterWriteLock();
try
{
_collection.Remove(key);
}
finally
{
_lock.ExitWriteLock();
}
}
/// <summary>
/// Enters write lock.
/// Adds value to the collection if key does not exists.
/// </summary>
/// <param name="key">Key to add.</param>
/// <param name="value">Value to add.</param>
private void Add(U key, T value)
{
_lock.EnterWriteLock();
if (!_collection.ContainsKey(key))
{
try
{
_collection[key] = value;
}
finally
{
_lock.ExitWriteLock();
}
}
}
/// <summary>
/// Collection does not support iteration.
/// </summary>
/// <returns>Throw NotSupportedException</returns>
public IEnumerator<T> GetEnumerator()
{
throw new NotSupportedException();
}
/// <summary>
/// Collection does not support iteration.
/// </summary>
/// <returns>Throw NotSupportedException</returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
throw new NotSupportedException();
}
}
I call this dictionary like this:
SynchronizedDictionary _cache = new SynchronizedDictionary();
Other threads can be spawned and use the thread like this:
_cache["key"];
The dictionary can be modified at runtime. I see no problem here. Or am I wrong?
The problem, in my eyes, lies in the enumerator, because I want to make an enumerator that iterates over the collection. How do I do this? I have thought of these three solutions:
Making a Enumerator like this:
http://www.codeproject.com/Articles/56575/Thread-safe-enumeration-in-C
(but using ReaderWriterLockSlim)
Expose the lock object, like SyncRoot does (but with
ReaderWriterLockSlim), so a caller calls the enter and exit read methods.
Use a database (SQLite fx) instead, holding the information.
The problem with number 1) is:
it uses the contructor to entry read mode. What if the
GetEnumerator() is call manually, not using the foreach? And forget
calling dispose.
I do not know if this is a good coding style. Even though I like the
code.
If the caller uses a foreach, I do not know what the caller might do
between the instantiation of the enumerator and the call to dispose.
If I have understood the documentation I have read correct this can
end up blocking the writer as long as there is one reader left doing
some heavy work.
The problem with number 2) is:
I do not like exposing this. I know that the .NET API does it, but
do not like it.
It is up to the caller to enter and exit properly
There is no problem with 3) I my eyes. But I am doing this small project as a spare time project and I want to learn more about multi-threading and reflection, so I want to keep this as a last option.
The reason why I want to iterate over the collection at runtime is that I want to find the values, that matches some criteria.
Maybe it is just me that have invented a problem?
I know of ConcurrentDictionary, but I do not want to use this. I am using this project as a playground. Playing with threading and reflection.
EDIT
I have been asked what it is that I am reading and writing. And I am going to tell this in this edit. I am reading and writing this class:
public class AssemblyInformation
{
public string FilePath { get; private set; }
public string Name { get; private set; }
public AssemblyInformation(string filePath, string name)
{
FilePath = filePath;
Name = name;
}
}
I am doing alot of reads, and almost no writes at runtime. Maybe I will do 2000 and 1 write. There is not going to be alot of object either, maybe 200.
I'll treat your questions as a request for feedback which helps you learn. Let me address the three solutions you have already identified:
Yes, this is why such a design should never be exposed as an API to a 3rd-party (or even other developers). It is tricky to use correctly. This codeproject article has some nasty advice.
Much better because this model would be explicit about locking, not implicit. However this violates separation of concerns in my opinion.
Not sure what you mean here. You could have a Snapshot() method on your dictionary which does a read-only copy which can be safely passed around and read. This is a different trade-off than solution 1.
There is a different solution entirely: Use an immutable dictionary. Such a dictionary could be passed around, read and enumerated safely even under concurrent write access. Such dictionaries/maps are commonly implemented using trees.
I'll elaborate more on a key point: You need to think about the concurrent system as a whole. You cannot make you app correct by making all components thread-safe (in your case a dictionary). You need to define, what you are using the dictionary for.
You say:
The reason why I want to iterate over the collection at runtime is
that I want to find the values, that matches some criteria.
You you have concurrent writes happening to the data and want to get a consistent snapshot atomically from the dictionary (maybe to shot some progress report in the UI?). Now that we know this goal, we can devise a solution:
You could add a Clone method to your dictionary which clones all data while taking the read-lock. This will give the caller a fresh object which it can then enumerate over independently. This would be a clean and safely exposable API.
Instead of implementing IEnumerable directly I would add a Values property (like Dictionary.Values):
public IEnumerable<T> Values {
get {
_lock.EnterReadLock();
try {
foreach (T v in _collection.Values) {
yield return v;
}
} finally {
_lock.ExitReadLock();
}
}
}

Is there something incorrect with this code? I don't believe I should have to create a new instance of object to modify collection

I am experiencing some weird behavior that disappears/reappears based on whether this dictionary is a new instance of the object, or the old instance of the object. Let me provide all the code first.
/// <summary>
/// Removes a control from monitoring/Session/Database based on ID.
/// </summary>
public static void Remove<T>(ICormantControl<T> control)
{
_logger.InfoFormat("Removing {0}", control.ID);
SerializableDictionary<string, T> states = new SerializableDictionary<string,T>(GetStates<SerializableDictionary<string, T>>());
((IDictionary)states).Remove(control.ID);
SetStates(states);
}
/// <summary>
/// Retrieves information on an object. If the object is cached to Session then the
/// cached object is retrieved. Else, it is retrieved from the database.
/// </summary>
/// <typeparam name="T"> The type of object expected to get back.</typeparam>
/// <returns> Collection of data for the specific object type.</returns>
public static T GetStates<T>() where T : new()
{
T states = new T();
string stateName = GetStateNameFromType(typeof(T));
if (!Equals(SessionRepository.Instance.GetSession(stateName), null))
{
states = (T)SessionRepository.Instance.GetSession(stateName);
}
else
{
XmlSerializer serializer = new XmlSerializer(states.GetType());
string data = DatabaseRepository.Instance.GetWebLayoutData(stateName);
if (!string.IsNullOrEmpty(data))
{
byte[] dataAsArray = Convert.FromBase64String(data);
MemoryStream stream = new MemoryStream(dataAsArray);
states = (T)serializer.Deserialize(stream);
}
SessionRepository.Instance.SetSession(stateName, states);
}
return states;
}
public static void SetStates<T>(T states) where T : new()
{
string stateName = GetStateNameFromType(typeof(T));
SessionRepository.Instance.SetSession(stateName, states);
if (shouldWriteToDatabase) DatabaseRepository.Instance.SaveToDatabase(stateName, states);
}
/// <summary>
/// Recreates the page state recursively by creating a control and looking for its known children.
/// </summary>
/// <param name="pane"> Pane having children added to it.</param>
private void RegeneratePaneChildren(CormantRadPane pane)
{
_logger.InfoFormat("Initializing paneToResize children for paneToResize {0}", pane.ID);
foreach (var splitterState in StateManager.GetStates<SerializableDictionary<string, RadSplitterSetting>>())
{
RadSplitterSetting splitterSetting = splitterState.Value;
if (!splitterSetting.ParentID.Contains(pane.ID)) continue;
CormantRadSplitter splitter = new CormantRadSplitter(splitterSetting);
pane.UpdatePanel.ContentTemplateContainer.Controls.AddAt(0, splitter); //Visibility will fight with splitter if you don't re-add like this.
RegenerateSplitterChildren(splitter);
}
}
/// <summary>
/// Recreates the page state recursively by creating a control and looking for its known children.
/// </summary>
/// <param name="splitter"> Splitter having children added to it. </param>
public void RegenerateSplitterChildren(RadSplitter splitter)
{
_logger.InfoFormat("Initializing splitter children for splitter {0}", splitter.ID);
foreach (var paneState in StateManager.GetStates<SerializableDictionary<string, RadPaneSetting>>()
.Where(paneState => paneState.Value.ParentID.Contains(splitter.ID)))
{
RadPaneSetting paneSetting = paneState.Value;
CormantRadPane pane = new CormantRadPane(paneSetting);
StyledUpdatePanel updatePanel = pane.CreateUpdatePanel(paneSetting.UpdatePanelID);
pane.Controls.Add(updatePanel);
splitter.Controls.Add(pane);
RegeneratePaneChildren(pane);
InsertSplitBar(splitter);
}
}
The key line to look at in all of this is: SerializableDictionary<string, T> states = new SerializableDictionary<string,T>(GetStates<SerializableDictionary<string, T>>());
If this line of code is modified such that it does not create a new instance of states (instead using the object saved in Session) my code gets 'desynched' and I experience odd behavior with my Regeneration methods. An object that is supposed to have 'ObjectA' as a parent instead has 'ObjectB' as a parent.
There's a lot of collection-modification going on... I'm removing a control from states and re-saving it...but I can't see where I do anything explicitly incorrect in this code. Yet, I still feel that I should be able to express the above line of code without creating a new instance of the object.
If anyone sees an obvious blunder I'd love to hear it. Thanks.

Caching best practices - Single object or multiple entries?

Does anyone have any advice on which method is better when caching data in a C# ASP.net application?
I am currently using a combination of two approaches, with some data (List, dictionaries, the usual domain-specific information) being put directly into the cache and boxed when needed, and some data being kept inside a globaldata class, and retrieved through that class (i.e. the GlobalData class is cached, and it's properties are the actual data).
Is either approach preferable?
I get the feeling that caching each item separately would be more sensible from a concurrency point of view, however it creates a lot more work in the long run with more functions that purely deal with getting data out of a cache location in a Utility class.
Suggestions would be appreciated.
Generally the cache's performance is so much better than the underlying source (e.g. a DB) that the performance of the cache is not a problem. The main goal is rather to get as high cache-hit ratio as possible (unless you are developing at really large scale because then it pays off to optimize the cache as well).
To achieve this I usually try to make it as straight forward as possible for the developer to use cache (so that we don't miss any chances of cache-hits just because the developer is too lazy to use the cache). In some projects we've use a modified version of a CacheHandler available in Microsoft's Enterprise Library.
With CacheHandler (which uses Policy Injection) you can easily make a method "cacheable" by just adding an attribute to it. For instance this:
[CacheHandler(0, 30, 0)]
public Object GetData(Object input)
{
}
would make all calls to that method cached for 30 minutes. All invocations gets a unique cache-key based on the input data and method name so if you call the method twice with different input it doesn't get cached but if you call it >1 times within the timout interval with the same input then the method only gets executed once.
Our modified version looks like this:
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Contexts;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
namespace Middleware.Cache
{
/// <summary>
/// An <see cref="ICallHandler"/> that implements caching of the return values of
/// methods. This handler stores the return value in the ASP.NET cache or the Items object of the current request.
/// </summary>
[ConfigurationElementType(typeof (CacheHandler)), Synchronization]
public class CacheHandler : ICallHandler
{
/// <summary>
/// The default expiration time for the cached entries: 5 minutes
/// </summary>
public static readonly TimeSpan DefaultExpirationTime = new TimeSpan(0, 5, 0);
private readonly object cachedData;
private readonly DefaultCacheKeyGenerator keyGenerator;
private readonly bool storeOnlyForThisRequest = true;
private TimeSpan expirationTime;
private GetNextHandlerDelegate getNext;
private IMethodInvocation input;
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest)
{
keyGenerator = new DefaultCacheKeyGenerator();
this.expirationTime = expirationTime;
this.storeOnlyForThisRequest = storeOnlyForThisRequest;
}
/// <summary>
/// This constructor is used when we wrap cached data in a CacheHandler so that
/// we can reload the object after it has been removed from the cache.
/// </summary>
/// <param name="expirationTime"></param>
/// <param name="storeOnlyForThisRequest"></param>
/// <param name="input"></param>
/// <param name="getNext"></param>
/// <param name="cachedData"></param>
public CacheHandler(TimeSpan expirationTime, bool storeOnlyForThisRequest,
IMethodInvocation input, GetNextHandlerDelegate getNext,
object cachedData)
: this(expirationTime, storeOnlyForThisRequest)
{
this.input = input;
this.getNext = getNext;
this.cachedData = cachedData;
}
/// <summary>
/// Gets or sets the expiration time for cache data.
/// </summary>
/// <value>The expiration time.</value>
public TimeSpan ExpirationTime
{
get { return expirationTime; }
set { expirationTime = value; }
}
#region ICallHandler Members
/// <summary>
/// Implements the caching behavior of this handler.
/// </summary>
/// <param name="input"><see cref="IMethodInvocation"/> object describing the current call.</param>
/// <param name="getNext">delegate used to get the next handler in the current pipeline.</param>
/// <returns>Return value from target method, or cached result if previous inputs have been seen.</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
lock (input.MethodBase)
{
this.input = input;
this.getNext = getNext;
return loadUsingCache();
}
}
public int Order
{
get { return 0; }
set { }
}
#endregion
private IMethodReturn loadUsingCache()
{
//We need to synchronize calls to the CacheHandler on method level
//to prevent duplicate calls to methods that could be cached.
lock (input.MethodBase)
{
if (TargetMethodReturnsVoid(input) || HttpContext.Current == null)
{
return getNext()(input, getNext);
}
var inputs = new object[input.Inputs.Count];
for (int i = 0; i < inputs.Length; ++i)
{
inputs[i] = input.Inputs[i];
}
string cacheKey = keyGenerator.CreateCacheKey(input.MethodBase, inputs);
object cachedResult = getCachedResult(cacheKey);
if (cachedResult == null)
{
var stopWatch = Stopwatch.StartNew();
var realReturn = getNext()(input, getNext);
stopWatch.Stop();
if (realReturn.Exception == null && realReturn.ReturnValue != null)
{
AddToCache(cacheKey, realReturn.ReturnValue);
}
return realReturn;
}
var cachedReturn = input.CreateMethodReturn(cachedResult, input.Arguments);
return cachedReturn;
}
}
private object getCachedResult(string cacheKey)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
if (cacheKey == null)
{
return null;
}
object cachedValue = !storeOnlyForThisRequest ? HttpRuntime.Cache.Get(cacheKey) : HttpContext.Current.Items[cacheKey];
var cachedValueCast = cachedValue as CacheHandler;
if (cachedValueCast != null)
{
//This is an object that is reloaded when it is being removed.
//It is therefore wrapped in a CacheHandler-object and we must
//unwrap it before returning it.
return cachedValueCast.cachedData;
}
return cachedValue;
}
private static bool TargetMethodReturnsVoid(IMethodInvocation input)
{
var targetMethod = input.MethodBase as MethodInfo;
return targetMethod != null && targetMethod.ReturnType == typeof (void);
}
private void AddToCache(string key, object valueToCache)
{
if (key == null)
{
//When the method uses input that is not serializable
//we cannot create a cache key and can therefore not
//cache the data.
return;
}
if (!storeOnlyForThisRequest)
{
HttpRuntime.Cache.Insert(
key,
valueToCache,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
expirationTime,
CacheItemPriority.Normal, null);
}
else
{
HttpContext.Current.Items[key] = valueToCache;
}
}
}
/// <summary>
/// This interface describes classes that can be used to generate cache key strings
/// for the <see cref="CacheHandler"/>.
/// </summary>
public interface ICacheKeyGenerator
{
/// <summary>
/// Creates a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
string CreateCacheKey(MethodBase method, object[] inputs);
}
/// <summary>
/// The default <see cref="ICacheKeyGenerator"/> used by the <see cref="CacheHandler"/>.
/// </summary>
public class DefaultCacheKeyGenerator : ICacheKeyGenerator
{
private readonly LosFormatter serializer = new LosFormatter(false, "");
#region ICacheKeyGenerator Members
/// <summary>
/// Create a cache key for the given method and set of input arguments.
/// </summary>
/// <param name="method">Method being called.</param>
/// <param name="inputs">Input arguments.</param>
/// <returns>A (hopefully) unique string to be used as a cache key.</returns>
public string CreateCacheKey(MethodBase method, params object[] inputs)
{
try
{
var sb = new StringBuilder();
if (method.DeclaringType != null)
{
sb.Append(method.DeclaringType.FullName);
}
sb.Append(':');
sb.Append(method.Name);
TextWriter writer = new StringWriter(sb);
if (inputs != null)
{
foreach (var input in inputs)
{
sb.Append(':');
if (input != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = input as DateTime?;
if (inputDateTime.HasValue)
{
sb.Append(inputDateTime.Value.Ticks);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(writer, input);
}
}
}
}
return sb.ToString();
}
catch
{
//Something went wrong when generating the key (probably an input-value was not serializble.
//Return a null key.
return null;
}
}
#endregion
}
}
Microsoft deserves most credit for this code. We've only added stuff like caching at request level instead of across requests (more useful than you might think) and fixed some bugs (e.g. equal DateTime-objects serializing to different values).
Under what conditions do you need to invalidate your cache? Objects should be stored so that when they are invalidated repopulating the cache only requires re-caching the items that were invalidated.
For example if you have cached say a Customer object that contains the delivery details for an order along with the shopping basket. Invalidating the shopping basket because they added or removed an item would also require repopulating the delivery details unnecessarily.
(NOTE: This is an obteuse example and I'm not advocating this just trying to demonstrate the principle and my imagination is a bit off today).
Ed, I assume those lists and dictionaries contain almost static data with low chances of expiration. Then there's data that gets frequent hits but also changes more frequently, so you're caching it using the HttpRuntime cache.
Now, you should think of all that data and all of the dependencies between diferent types. If you logically find that the HttpRuntime cached data depends somehow on your GlobalData items, you should move that into the cache and set up the appropriate dependencies in there so you'll benefit of the "cascading expiration".
Even if you do use your custom caching mechanism, you'd still have to provide all the synchronization, so you won't save on that by avoiding the other.
If you need (preordered) lists of items with a really low frequency change, you can still do that by using the HttpRuntime cache. So you could just cache a dictionary and either use it to list your items or to index and access by your custom key.
How about the best (worst?) of both worlds?
Have the globaldata class manage all the cache access internally. The rest of your code can then just use globaldata, meaning that it doesn't need to be cache-aware at all.
You could change the cache implementation as/when you like just by updating globaldata, and the rest of your code won't know or care what's going on inside.
There's much more than that to consider when architecting your caching strategy. Think of your cache store as if it were your in-memory db. So carefully handle dependencies and expiration policy for each and every type stored in there. It really doesn't matter what you use for caching (system.web, other commercial solution, rolling your own...).
I'd try to centralize it though and also use some sort of a plugable architecture. Make your data consumers access it through a common API (an abstract cache that exposes it) and plug your caching layer at runtime (let's say asp.net cache).
You should really take a top down approach when caching data to avoid any kind of data integrity problems (proper dependecies like I said) and then take care of providing synchronization.

Categories