Custom registration of method cache using PostSharp during run time - c#

I started using PostSharp recently, mainly because I want to implement Method Cache. I found few examples of how to do it, but all these examples are based on implementing a cache method attribute and decorating methods which results should be cached:
http://www.postsharp.net/blog/post/SOLID-Caching
http://doc.postsharp.net/example-cache
I would like to implement the cache in a way to be able to set some additional perameters for each method in run time based on configuration. For example I would like to register a particular method cache with individual expiration time, but I do not know that value during code compilation.
The thing I do not how to do is how to do it during runtime, without decorating the methods.

This shall be simple, though you need to code the details you need, following are the important details, pasting my code, so some variables are our custom, modify them accordingly. You need to apply [ViewrCache] attribute to the relevant method post creating the underneath code and it will the modify the method IL accordingly at runtime
Create a custom caching attribute extended from PostSharp.Aspects.MethodInterceptionAspect as follows:
[Serializable]
public sealed class ViewrCacheAttribute : MethodInterceptionAspect
Class Variables:
// Cache Expiration from Config file
private static readonly int CacheTimeOut = Convert.ToInt32(WebConfigurationManager.AppSettings[CacheSettings.CacheTimeOutKey]);
// Parameters to be ignored from the Cache Key
private string[] IgnoreParameters { get; set; }
Override the method OnInvoke(MethodInterceptionArgs args) as follows:
// My custom implementation that you may want to change
public override void OnInvoke(MethodInterceptionArgs args)
{
// Fetch standard Cache
var cache = MemoryCache.Default;
// Fetch Cache Key using the Method arguments
string cacheKey = GetCacheKey(args); // Code pasted below
var cacheValue = cache.Get(cacheKey);
if (cacheValue != null)
{
args.ReturnValue = cacheValue;
return;
}
ReloadCache(cacheKey, args);
}
// Get Cache Key method
private string GetCacheKey(MethodInterceptionArgs args)
{
//need to exclude paging Parameters from Key
var builder = new StringBuilder();
var returnKey = new ViewrCacheKey { CallingMethodFullName = args.Method.ToString() };
// Loop through the Call arguments / parameters
foreach (var argument in args.Arguments)
{
if (argument != null)
{
if ((IgnoreParameters == null) ||
(IgnoreParameters != null && !IgnoreParameters.Contains(argument.ToString())))
builder.Append(JsonConvert.SerializeObject(argument));
}
}
returnKey.ControllerHash = builder.ToString();
return JsonConvert.SerializeObject(returnKey);
}
// Reload Cache
private Object ReloadCache(string cacheKey, MethodInterceptionArgs args)
{
//call the actual Method
base.OnInvoke(args);
//Save result into local cache
InsertCache(cacheKey, args.ReturnValue);
return args.ReturnValue;
}
// Insert Cache
private void InsertCache(string key, object value)
{
// Setting Cache Item Policy
var policy = new CacheItemPolicy
{
SlidingExpiration = new TimeSpan(0, 0, CacheTimeOut)
};
// sliding Expiration Timeout in Seconds
ObjectCache cache = MemoryCache.Default;
// Set the key,value in the cache
cache.Set(key, value, policy);
}
// ViewR Cache Key
public class ViewrCacheKey
{
/// <summary>
///
/// </summary>
public string CallingMethodFullName { get; set; }
/// <summary>
///
/// </summary>
public string ControllerHash { get; set; }
}

Related

How can I hook into all property setters of derived classes from a base class?

I have a .net web application that for all intents and purposes of this question is CRUD with many different domain objects.
A common theme across theses objects is the need to know which value properties have been modified as well as child domain model properties. Currently we have two different systems in place for this.
The value properties is the one I am trying to sort out with this question.
Right now the models all inherit from the PersistableModel base that has these fields and methods of note:
private readonly List<string> _modifiedProperties = new List<string>();
public virtual ModelState State { get; set; }
public IEnumerable<string> ModifiedProperties { get { return _modifiedProperties; } }
protected bool HasModifiedProperties { get { return 0 < _modifiedProperties.Count; } }
public bool WasModified(string propertyName)
{
return _modifiedProperties.Contains(propertyName);
}
public void WasModified(string propertyName, bool modified)
{
if (modified)
{
if (!WasModified(propertyName)) _modifiedProperties.Add(propertyName);
}
else
{
_modifiedProperties.Remove(propertyName);
}
}
Then within each individual model whenever a property is set we also need to call WasModified with a string of the property name and a boolean value.
Obviously this is very tedious and error prone, what I want to do is redesign this base class to automatically add entries to the dictionary when a derived class's property is set.
In my research the closest I've been able to get is to use PostSharp which is out of the question.
While working on a different project I came up with a solution that gets most of the way towards my original goal.
Note that this solution is reliant upon the Dev Express ViewModelBase as its base class, but it wouldn't be hard to make a new base class with the features being used for non Dev Express projects:
Edit: I found an issue with the reset state logic, this update removes that issue.
public abstract class UpdateableModel : ViewModelBase
{
private static readonly MethodInfo GetPropertyMethod;
private static readonly MethodInfo SetPropertyMethod;
private readonly bool _trackingEnabled;
private readonly Dictionary<string, Tuple<Expression, object>> _originalValues;
private readonly List<string> _differingFields;
static UpdateableModel()
{
GetPropertyMethod = typeof(UpdateableModel).GetMethod("GetProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
SetPropertyMethod = typeof(UpdateableModel).GetMethod("SetProperty");
}
protected UpdateableModel(bool isNewModel)
{
_originalValues = new Dictionary<string, Tuple<Expression, object>>();
_differingFields = new List<string>();
if (isNewModel) return;
State = ModelState.Unmodified;
_trackingEnabled = true;
}
public ModelState State
{
get { return GetProperty(() => State); }
set { base.SetProperty(() => State, value); }
}
public new bool SetProperty<T>(Expression<Func<T>> expression, T value)
{
var wasUpdated = base.SetProperty(expression, value);
if (_trackingEnabled && wasUpdated)
{
UpdateState(expression, value);
}
return wasUpdated;
}
/// <summary>
/// Reset State is meant to be called when discarding changes, it will reset the State value to Unmodified and set all modified values back to their original value.
/// </summary>
public void ResetState()
{
if (!_trackingEnabled) return;
foreach (var differingField in _differingFields)
{
var type = GetFuncType(_originalValues[differingField].Item1);
var genericPropertySetter = SetPropertyMethod.MakeGenericMethod(type);
genericPropertySetter.Invoke(this, new[]{_originalValues[differingField].Item2});
}
}
/// <summary>
/// Update State is meant to be called after changes have been persisted, it will reset the State value to Unmodified and update the original values to the new values.
/// </summary>
public void UpdateState()
{
if (!_trackingEnabled) return;
foreach (var differingField in _differingFields)
{
var type = GetFuncType(_originalValues[differingField].Item1);
var genericPropertySetter = GetPropertyMethod.MakeGenericMethod(type);
var value = genericPropertySetter.Invoke(this, new object[] { _originalValues[differingField].Item1 });
var newValue = new Tuple<Expression, object>(_originalValues[differingField].Item1,value);
_originalValues[differingField] = newValue;
}
_differingFields.Clear();
State = ModelState.Unmodified;
}
private static Type GetFuncType(Expression expr)
{
var lambda = expr as LambdaExpression;
if (lambda == null)
{
return null;
}
var member = lambda.Body as MemberExpression;
return member != null ? member.Type : null;
}
private void UpdateState<T>(Expression<Func<T>> expression, T value)
{
var propertyName = GetPropertyName(expression);
if (!_originalValues.ContainsKey(propertyName))
{
_originalValues.Add(propertyName, new Tuple<Expression,object>(expression, value));
}
else
{
if (!Compare(_originalValues[propertyName].Item2, value))
{
_differingFields.Add(propertyName);
}
else if (_differingFields.Contains(propertyName))
{
_differingFields.Remove(propertyName);
}
State = _differingFields.Count == 0
? ModelState.Unmodified
: ModelState.Modified;
}
}
private static bool Compare<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
Another quick note, the IsNewModel constructor flag serves to differentiate objects create on the UI level which don't need any kind of state tracking, and objects generated from the data access level which will need the state tracking.

Caching WebAPI 2

EDIT: For each request, a new instance of controller is created. However, this is not true with Attribute classes. Once they are created, it is used for multiple requests. I hope it helps.
I wrote my own WebAPI (using latest version of WebAPI and .net framework) caching action filter. I am aware about CacheCow & this. However, i wanted mine anyways.
However, there is some issue with my code because i don't get exepected output when i use it in my project on live server. On local machine everything works fine.
I used below code in my blog RSS generator and i cache the data for each category. There are around 5 categories (food, tech, personal etc).
Issue: When i navigate to say api/GetTech it returns me the rss feed items from personal blog category. When i navigate to say api/GetPersonal , it returns me api/Food
I am not able to find the root cause but I think this is due to use of static method/variable. I have double checked that my _cachekey has unique value for each category of my blog.
Can someone point out any issues with this code esp when we have say 300 requests per minute ?
public class WebApiOutputCacheAttribute : ActionFilterAttribute
{
// Cache timespan
private readonly int _timespan;
// cache key
private string _cachekey;
// cache repository
private static readonly MemoryCache _webApiCache = MemoryCache.Default;
/// <summary>
/// Initializes a new instance of the <see cref="WebApiOutputCacheAttribute"/> class.
/// </summary>
/// <param name="timespan">The timespan in seconds.</param>
public WebApiOutputCacheAttribute(int timespan)
{
_timespan = timespan;
}
public override void OnActionExecuting(HttpActionContext ac)
{
if (ac != null)
{
_cachekey = ac.Request.RequestUri.PathAndQuery.ToUpperInvariant();
if (!_webApiCache.Contains(_cachekey)) return;
var val = (string)_webApiCache.Get(_cachekey);
if (val == null) return;
ac.Response = ac.Request.CreateResponse();
ac.Response.Content = new StringContent(val);
var contenttype = (MediaTypeHeaderValue)_webApiCache.Get("response-ct") ?? new MediaTypeHeaderValue("application/rss+xml");
ac.Response.Content.Headers.ContentType = contenttype;
}
else
{
throw new ArgumentNullException("ac");
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
if (_webApiCache.Contains(_cachekey)) return;
var body = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
if (actionExecutedContext.Response.StatusCode == HttpStatusCode.OK)
{
lock (WebApiCache)
{
_wbApiCache.Add(_cachekey, body, DateTime.Now.AddSeconds(_timespan));
_webApiCache.Add("response-ct", actionExecutedContext.Response.Content.Headers.ContentType, DateTimeOffset.UtcNow.AddSeconds(_timespan));
}
}
}
}
The same WebApiOutputCacheAttribute instance can be used to cache multiple simultaneous requests, so you should not store cache keys on the instance of the attribute. Instead, regenerate the cache key during each request / method override. The following attribute works to cache HTTP GET requests.
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Newtonsoft.Json;
// based on strathweb implementation
// http://www.strathweb.com/2012/05/output-caching-in-asp-net-web-api/
public class CacheHttpGetAttribute : ActionFilterAttribute
{
public int Duration { get; set; }
public ILogExceptions ExceptionLogger { get; set; }
public IProvideCache CacheProvider { get; set; }
private bool IsCacheable(HttpRequestMessage request)
{
if (Duration < 1)
throw new InvalidOperationException("Duration must be greater than zero.");
// only cache for GET requests
return request.Method == HttpMethod.Get;
}
private CacheControlHeaderValue SetClientCache()
{
var cachecontrol = new CacheControlHeaderValue
{
MaxAge = TimeSpan.FromSeconds(Duration),
MustRevalidate = true,
};
return cachecontrol;
}
private static string GetServerCacheKey(HttpRequestMessage request)
{
var acceptHeaders = request.Headers.Accept;
var acceptHeader = acceptHeaders.Any() ? acceptHeaders.First().ToString() : "*/*";
return string.Join(":", new[]
{
request.RequestUri.AbsoluteUri,
acceptHeader,
});
}
private static string GetClientCacheKey(string serverCacheKey)
{
return string.Join(":", new[]
{
serverCacheKey,
"response-content-type",
});
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext == null) throw new ArgumentNullException("actionContext");
var request = actionContext.Request;
if (!IsCacheable(request)) return;
try
{
// do NOT store cache keys on this attribute because the same instance
// can be reused for multiple requests
var serverCacheKey = GetServerCacheKey(request);
var clientCacheKey = GetClientCacheKey(serverCacheKey);
if (CacheProvider.Contains(serverCacheKey))
{
var serverValue = CacheProvider.Get(serverCacheKey);
var clientValue = CacheProvider.Get(clientCacheKey);
if (serverValue == null) return;
var contentType = clientValue != null
? JsonConvert.DeserializeObject<MediaTypeHeaderValue>(clientValue.ToString())
: new MediaTypeHeaderValue(serverCacheKey.Substring(serverCacheKey.LastIndexOf(':') + 1));
actionContext.Response = actionContext.Request.CreateResponse();
// do not try to create a string content if the value is binary
actionContext.Response.Content = serverValue is byte[]
? new ByteArrayContent((byte[])serverValue)
: new StringContent(serverValue.ToString());
actionContext.Response.Content.Headers.ContentType = contentType;
actionContext.Response.Headers.CacheControl = SetClientCache();
}
}
catch (Exception ex)
{
ExceptionLogger.Log(ex);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
try
{
var request = actionExecutedContext.Request;
// do NOT store cache keys on this attribute because the same instance
// can be reused for multiple requests
var serverCacheKey = GetServerCacheKey(request);
var clientCacheKey = GetClientCacheKey(serverCacheKey);
if (!CacheProvider.Contains(serverCacheKey))
{
var contentType = actionExecutedContext.Response.Content.Headers.ContentType;
object serverValue;
if (contentType.MediaType.StartsWith("image/"))
serverValue = actionExecutedContext.Response.Content.ReadAsByteArrayAsync().Result;
else
serverValue = actionExecutedContext.Response.Content.ReadAsStringAsync().Result;
var clientValue = JsonConvert.SerializeObject(
new
{
contentType.MediaType,
contentType.CharSet,
});
CacheProvider.Add(serverCacheKey, serverValue, new TimeSpan(0, 0, Duration));
CacheProvider.Add(clientCacheKey, clientValue, new TimeSpan(0, 0, Duration));
}
if (IsCacheable(actionExecutedContext.Request))
actionExecutedContext.ActionContext.Response.Headers.CacheControl = SetClientCache();
}
catch (Exception ex)
{
ExceptionLogger.Log(ex);
}
}
}
Just replace the CacheProvider with your MemoryCache.Default. In fact, the code above uses the same by default during development, and uses azure cache when deployed to a live server.
Even though your code resets the _cachekey instance field during each request, these attributes are not like controllers where a new one is created for each request. Instead, the attribute instance can be repurposed to service multiple simultaneous requests. So don't use an instance field to store it, regenerate it based on the request each and every time you need it.

How to correctly update partially submitted (to a REST API) objects with LINQ to SQL

Our C# application communicates with a database through a LINQ-to-SQL Database Model, specifically using the MVC4 libraries.
I've been assigned the task of implementing a RESTful API. We thought it would be a good idea to version the API. That way, changes to the API can be introduced in a new version, and existing API clients won't break. To support this, every version of the API has its own set of Data Transfer Objects (DTOs) that it exposes and accepts. Some mapping is done (using AutoMapper) to translate between the API and Database Model.
Currently I'm working on updating and creating functionality. That is, if a client POSTs an Item object to the ItemsController, and the Item does not exist in the database yet (given its unique identifier), a new Item should be created. If the identifier is already present in the database, the existing data should be updated. So far so good.
Now, I'm converting a legacy code base to communicate with the RESTful API instead of with the database directly. Some parts of this codebase update a single property on a resource, and send just the identifier and the new value of that single property. The rest of the object should remain as it is in the database.
I'm having trouble implementing this using LINQ-to-SQL, specifically because of the DTO layer. This is the controller method:
[HttpPut]
[HttpPost]
public void UpdateOrCreateItem(ItemDTO data)
{
Item submittedItem = Map(data);
ItemRepository.UpdateOrCreateItem(submittedItem);
}
Now, instead of receiving a fully filled data object, only the identifier and one other property are filled. When the LINQ-to-SQL processes this data as follows:
public static void UpdateOrCreateItem(Item submittedItem)
{
if (submittedItem.Id > 0)
{
// update
using (DatabaseAccessor db = new DatabaseAccessor())
{
db.context.Items.Attach(submittedItem);
db.context.Refresh(RefreshMode.KeepCurrentValues, submittedItem);
db.context.SubmitChanges();
}
} else {
// create
// omitted...
}
}
Refreshing marks all the empty (missing) properties as changed, and its all saved to the database. Instead, only the properties that were submitted at the REST API level should be stored. What would be an elegant solution to this problem?
In the end I wrote some code to accept json-patch requests (see https://www.rfc-editor.org/rfc/rfc6902).
You need to add the media type "application/json-patch" to the collection of accepted formats.
You need to accept an identifier and an array of JsonPatchOperation objects as input to a HTTP PATCH method on your ApiController
The ApiController method:
[HttpPatch]
public void UpdatePartially(int id, JsonPatchOperation[] patchOperations)
{
if (id > 0)
{
// DatabaseAccessor is just a wrapper around my DataContext object
using (DatabaseAccessor db = new DatabaseAccessor())
{
SetDataLoadOptions(db); // optional of course
var item = db.context.Items.Single(i => i.id == id);
foreach (JsonPatchOperation patchOperation in patchOperations)
{
// when you want to set a foreign key identifier, LINQ-to-SQL throw a ForeignKeyReferenceAlreadyHasValueException
// the patchOperation will then use GetForeignKeyObject to fetch the object that it requires to set the foreign key object instead
patchOperation.GetForeignKeyObject = (PropertyInfo property, object identifier) =>
{
// this is just example code, make sure to correct this for the possible properties of your object...
if (property == typeof(Item).GetProperty("JobStatus", typeof(JobStatus)))
{
return db.context.JobStatus.Single(js => js.StatusId == (int)identifier);
}
else if (property == typeof(Item).GetProperty("User", typeof(User)))
{
return db.context.Users.Single(u => u.UserId == (Guid)identifier);
}
throw new ArgumentOutOfRangeException("property", String.Format("Missing getter for property '{0}'.", property.Name));
};
patchOperation.ApplyTo(item);
}
db.context.SubmitChanges();
}
}
}
And here are the dependencies of the above method:
/// <summary>
/// Add this to the global configuration Formatters collection to accept json-patch requests
/// </summary>
public class JsonPatchMediaTypeFormatter : JsonMediaTypeFormatter
{
public JsonPatchMediaTypeFormatter() : base()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json-patch"));
}
}
/// <summary>
/// All possible values for the "op" property of a json-patch object
/// docs: https://www.rfc-editor.org/rfc/rfc6902#section-4
/// </summary>
public enum JsonPatchOperationType
{
add,
remove,
replace,
move,
copy,
test
}
/// <summary>
/// json-patch is a partial update format for HTTP PATCH requests
/// docs: https://www.rfc-editor.org/rfc/rfc6902
/// </summary>
public class JsonPatchOperation
{
public string op { get; set; }
public string from { get; set; }
public string path { get; set; }
public string value { get; set; }
public Func<PropertyInfo, object, object> GetForeignKeyObject { get; set; }
public JsonPatchOperationType Operation
{
get
{
return (JsonPatchOperationType)Enum.Parse(typeof(JsonPatchOperationType), op);
}
}
public void ApplyTo(object document)
{
switch (Operation)
{
case JsonPatchOperationType.add:
Add(document, path, value);
break;
case JsonPatchOperationType.remove:
Remove(document, path);
break;
case JsonPatchOperationType.replace:
Replace(document, path, value);
break;
case JsonPatchOperationType.move:
Move(document, path, from);
break;
case JsonPatchOperationType.copy:
Copy(document, path, from);
break;
case JsonPatchOperationType.test:
Test(document, path, value);
break;
}
}
private void Add(object document, string path, string value)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
object convertedValue = ConvertToType(value, pathInfo.PropertyInfo.PropertyType);
pathInfo.PropertyInfo.SetValue(document, convertedValue, pathInfo.Indexes);
}
private void Replace(object document, string path, string value)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
object convertedValue = ConvertToType(value, pathInfo.PropertyInfo.PropertyType);
try
{
pathInfo.PropertyInfo.SetValue(document, convertedValue, pathInfo.Indexes);
}
// gnarly hack for setting foreign key properties
catch (TargetInvocationException tie)
{
if (tie.InnerException is ForeignKeyReferenceAlreadyHasValueException)
{
PropertyInfo matchingProperty = documentType.GetProperties().Single(p => p.GetCustomAttributes(typeof(AssociationAttribute), true).Any(attr => ((AssociationAttribute)attr).ThisKey == pathInfo.PropertyInfo.Name));
matchingProperty.SetValue(document, GetForeignKeyObject(matchingProperty, convertedValue), null);
}
else
{
throw tie;
}
}
}
private void Remove(object document, string path)
{
Type documentType = document.GetType();
PathInfo pathInfo = GetPathInfo(documentType, path);
pathInfo.PropertyInfo.SetValue(document, GetDefaultValue(pathInfo.PropertyInfo.PropertyType), pathInfo.Indexes);
}
private void Copy(object document, string path, string from)
{
throw new NotImplementedException();
}
private void Move(object document, string path, string from)
{
throw new NotImplementedException();
}
private void Test(object document, string path, string value)
{
throw new NotImplementedException();
}
#region Util
private class PathInfo
{
public PropertyInfo PropertyInfo { get; set; }
public object[] Indexes { get; set; }
}
private PathInfo GetPathInfo(Type documentType, string path)
{
object[] indexes = null;
PropertyInfo propertyInfo = documentType.GetProperty(path);
return new PathInfo { PropertyInfo = propertyInfo, Indexes = indexes };
}
private object GetDefaultValue(Type t)
{
if (t.IsValueType)
return Activator.CreateInstance(t);
return null;
}
private object ConvertToType(string value, Type type)
{
TypeConverter typeConverter = TypeDescriptor.GetConverter(type);
return typeConverter.ConvertFromString(value);
}
#endregion
}
It should be obvious that this is not finished, mature or elegant. But it works.

Caching in a console application

I need to cache a generic list so I dont have to query the databse multiple times. In a web application I would just add it to the httpcontext.current.cache . What is the proper way to cache objects in console applications?
Keep it as instance member of the containing class. In web app you can't do this since page class's object is recreated on every request.
However .NET 4.0 also has MemoryCache class for this purpose.
In a class-level variable. Presumably, in the main method of your console app you instantiate at least one object of some sort. In this object's class, you declare a class-level variable (a List<String> or whatever) in which you cache whatever needs caching.
Here is a very simple cache class I use in consoles that has self clean up and easy implementation.
The Usage:
return Cache.Get("MyCacheKey", 30, () => { return new Model.Guide().ChannelListings.BuildChannelList(); });
The Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
namespace MyAppNamespace
{
public static class Cache
{
private static Timer cleanupTimer = new Timer() { AutoReset = true, Enabled = true, Interval = 60000 };
private static readonly Dictionary<string, CacheItem> internalCache = new Dictionary<string, CacheItem>();
static Cache()
{
cleanupTimer.Elapsed += Clean;
cleanupTimer.Start();
}
private static void Clean(object sender, ElapsedEventArgs e)
{
internalCache.Keys.ToList().ForEach(x => { try { if (internalCache[x].ExpireTime <= e.SignalTime) { Remove(x); } } catch (Exception) { /*swallow it*/ } });
}
public static T Get<T>(string key, int expiresMinutes, Func<T> refreshFunction)
{
if (internalCache.ContainsKey(key) && internalCache[key].ExpireTime > DateTime.Now)
{
return (T)internalCache[key].Item;
}
var result = refreshFunction();
Set(key, result, expiresMinutes);
return result;
}
public static void Set(string key, object item, int expiresMinutes)
{
Remove(key);
internalCache.Add(key, new CacheItem(item, expiresMinutes));
}
public static void Remove(string key)
{
if (internalCache.ContainsKey(key))
{
internalCache.Remove(key);
}
}
private struct CacheItem
{
public CacheItem(object item, int expiresMinutes)
: this()
{
Item = item;
ExpireTime = DateTime.Now.AddMinutes(expiresMinutes);
}
public object Item { get; private set; }
public DateTime ExpireTime { get; private set; }
}
}
}
// Consider this psuedo code for using Cache
public DataSet GetMySearchData(string search)
{
// if it is in my cache already (notice search criteria is the cache key)
string cacheKey = "Search " + search;
if (Cache[cacheKey] != null)
{
return (DataSet)(Cache[cacheKey]);
}
else
{
DataSet result = yourDAL.DoSearch(search);
Cache[cacheKey].Insert(result); // There are more params needed here...
return result;
}
}
Ref: How do I cache a dataset to stop round trips to db?
You might be able to just use a simple Dictionary. The thing that makes the Cache so special in the web environment is that it persists and is scoped in such a way that many users can access it. In a console app, you don't have those issues. If your needs are simple enough, the dictionary or similar structures can be used to quickly lookup values you pull out of a database.
There a many ways to implement caches, depending of what exactly you are doing. Usually you will be using a dictionary to hold cached values. Here is my simple implementation of a cache, which caches values only for a limited time:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CySoft.Collections
{
public class Cache<TKey,TValue>
{
private readonly Dictionary<TKey, CacheItem> _cache = new Dictionary<TKey, CacheItem>();
private TimeSpan _maxCachingTime;
/// <summary>
/// Creates a cache which holds the cached values for an infinite time.
/// </summary>
public Cache()
: this(TimeSpan.MaxValue)
{
}
/// <summary>
/// Creates a cache which holds the cached values for a limited time only.
/// </summary>
/// <param name="maxCachingTime">Maximum time for which the a value is to be hold in the cache.</param>
public Cache(TimeSpan maxCachingTime)
{
_maxCachingTime = maxCachingTime;
}
/// <summary>
/// Tries to get a value from the cache. If the cache contains the value and the maximum caching time is
/// not exceeded (if any is defined), then the cached value is returned, else a new value is created.
/// </summary>
/// <param name="key">Key of the value to get.</param>
/// <param name="createValue">Function creating a new value.</param>
/// <returns>A cached or a new value.</returns>
public TValue Get(TKey key, Func<TValue> createValue)
{
CacheItem cacheItem;
if (_cache.TryGetValue(key, out cacheItem) && (DateTime.Now - cacheItem.CacheTime) <= _maxCachingTime) {
return cacheItem.Item;
}
TValue value = createValue();
_cache[key] = new CacheItem(value);
return value;
}
private struct CacheItem
{
public CacheItem(TValue item)
: this()
{
Item = item;
CacheTime = DateTime.Now;
}
public TValue Item { get; private set; }
public DateTime CacheTime { get; private set; }
}
}
}
You can pass a lambda expression to the Get method, which retrieves values from a db for instance.
Use Singleton Pattern.
http://msdn.microsoft.com/en-us/library/ff650316.aspx

How to clear MemoryCache?

I have created a cache using the MemoryCache class. I add some items to it but when I need to reload the cache I want to clear it first. What is the quickest way to do this? Should I loop through all the items and remove them one at a time or is there a better way?
Dispose the existing MemoryCache and create a new MemoryCache object.
The problem with enumeration
The MemoryCache.GetEnumerator() Remarks section warns: "Retrieving an enumerator for a MemoryCache instance is a resource-intensive and blocking operation. Therefore, the enumerator should not be used in production applications."
Here's why, explained in pseudocode of the GetEnumerator() implementation:
Create a new Dictionary object (let's call it AllCache)
For Each per-processor segment in the cache (one Dictionary object per processor)
{
Lock the segment/Dictionary (using lock construct)
Iterate through the segment/Dictionary and add each name/value pair one-by-one
to the AllCache Dictionary (using references to the original MemoryCacheKey
and MemoryCacheEntry objects)
}
Create and return an enumerator on the AllCache Dictionary
Since the implementation splits the cache across multiple Dictionary objects, it must bring everything together into a single collection in order to hand back an enumerator. Every call to GetEnumerator executes the full copy process detailed above. The newly created Dictionary contains references to the original internal key and value objects, so your actual cached data values are not duplicated.
The warning in the documentation is correct. Avoid GetEnumerator() -- including all of the answers above that use LINQ queries.
A better and more flexible solution
Here's an efficient way of clearing the cache that simply builds on the existing change monitoring infrastructure. It also provides the flexibility to clear either the entire cache or just a named subset and has none of the problems discussed above.
// By Thomas F. Abraham (http://www.tfabraham.com)
namespace CacheTest
{
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Caching;
public class SignaledChangeEventArgs : EventArgs
{
public string Name { get; private set; }
public SignaledChangeEventArgs(string name = null) { this.Name = name; }
}
/// <summary>
/// Cache change monitor that allows an app to fire a change notification
/// to all associated cache items.
/// </summary>
public class SignaledChangeMonitor : ChangeMonitor
{
// Shared across all SignaledChangeMonitors in the AppDomain
private static event EventHandler<SignaledChangeEventArgs> Signaled;
private string _name;
private string _uniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
public override string UniqueId
{
get { return _uniqueId; }
}
public SignaledChangeMonitor(string name = null)
{
_name = name;
// Register instance with the shared event
SignaledChangeMonitor.Signaled += OnSignalRaised;
base.InitializationComplete();
}
public static void Signal(string name = null)
{
if (Signaled != null)
{
// Raise shared event to notify all subscribers
Signaled(null, new SignaledChangeEventArgs(name));
}
}
protected override void Dispose(bool disposing)
{
SignaledChangeMonitor.Signaled -= OnSignalRaised;
}
private void OnSignalRaised(object sender, SignaledChangeEventArgs e)
{
if (string.IsNullOrWhiteSpace(e.Name) || string.Compare(e.Name, _name, true) == 0)
{
Debug.WriteLine(
_uniqueId + " notifying cache of change.", "SignaledChangeMonitor");
// Cache objects are obligated to remove entry upon change notification.
base.OnChanged(null);
}
}
}
public static class CacheTester
{
public static void TestCache()
{
MemoryCache cache = MemoryCache.Default;
// Add data to cache
for (int idx = 0; idx < 50; idx++)
{
cache.Add("Key" + idx.ToString(), "Value" + idx.ToString(), GetPolicy(idx));
}
// Flush cached items associated with "NamedData" change monitors
SignaledChangeMonitor.Signal("NamedData");
// Flush all cached items
SignaledChangeMonitor.Signal();
}
private static CacheItemPolicy GetPolicy(int idx)
{
string name = (idx % 2 == 0) ? null : "NamedData";
CacheItemPolicy cip = new CacheItemPolicy();
cip.AbsoluteExpiration = System.DateTimeOffset.UtcNow.AddHours(1);
cip.ChangeMonitors.Add(new SignaledChangeMonitor(name));
return cip;
}
}
}
From http://connect.microsoft.com/VisualStudio/feedback/details/723620/memorycache-class-needs-a-clear-method
The workaround is:
List<string> cacheKeys = MemoryCache.Default.Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
MemoryCache.Default.Remove(cacheKey);
}
var cacheItems = cache.ToList();
foreach (KeyValuePair<String, Object> a in cacheItems)
{
cache.Remove(a.Key);
}
If performance isn't an issue then this nice one-liner will do the trick:
cache.ToList().ForEach(a => cache.Remove(a.Key));
It seems that there is a Trim method.
So to clear all contents you'd just do
cache.Trim(100)
EDIT:
after digging some more, it seems that looking into Trim is not worth your time
https://connect.microsoft.com/VisualStudio/feedback/details/831755/memorycache-trim-method-doesnt-evict-100-of-the-items
How do I clear a System.Runtime.Caching.MemoryCache
Ran across this, and based on it, wrote a slightly more effective, parallel clear method:
public void ClearAll()
{
var allKeys = _cache.Select(o => o.Key);
Parallel.ForEach(allKeys, key => _cache.Remove(key));
}
You could also do something like this:
Dim _Qry = (From n In CacheObject.AsParallel()
Select n).ToList()
For Each i In _Qry
CacheObject.Remove(i.Key)
Next
You can dispose the MemoryCache.Default cache and then re-set the private field singleton to null, to make it recreate the MemoryCache.Default.
var field = typeof(MemoryCache).GetField("s_defaultCache",
BindingFlags.Static |
BindingFlags.NonPublic);
field.SetValue(null, null);
I was only interested in clearing the cache and found this as an option, when using the c# GlobalCachingProvider
var cache = GlobalCachingProvider.Instance.GetAllItems();
if (dbOperation.SuccessLoadingAllCacheToDB(cache))
{
cache.Clear();
}
a bit improved version of magritte answer.
var cacheKeys = MemoryCache.Default.Where(kvp.Value is MyType).Select(kvp => kvp.Key).ToList();
foreach (string cacheKey in cacheKeys)
{
MemoryCache.Default.Remove(cacheKey);
}
This discussion is also being done here:
https://learn.microsoft.com/en-us/answers/answers/983399/view.html
I wrote an answer there and I'll transcribe it here:
using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using ServiceStack;
public static class IMemoryCacheExtensions
{
static readonly List<object> entries = new();
/// <summary>
/// Removes all entries, added via the "TryGetValueExtension()" method
/// </summary>
/// <param name="cache"></param>
public static void Clear(this IMemoryCache cache)
{
for (int i = 0; i < entries.Count; i++)
{
cache.Remove(entries[i]);
}
entries.Clear();
}
/// <summary>
/// Use this extension method, to be able to remove all your entries later using "Clear()" method
/// </summary>
/// <typeparam name="TItem"></typeparam>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool TryGetValueExtension<TItem>(this IMemoryCache cache, object key, out TItem value)
{
entries.AddIfNotExists(key);
if (cache.TryGetValue(key, out object result))
{
if (result == null)
{
value = default;
return true;
}
if (result is TItem item)
{
value = item;
return true;
}
}
value = default;
return false;
}
}

Categories

Resources