Anpassa cachen i ASP.NET 4.0

När ASP.NET 2.0 kom följde en del providers med för Membership, Personalization och annat. Sedan har det stått stilla på den fronten fram tills nu. I ASP.NET 4.0 introduceras OutputCacheProvider som ger oss möjligheten att enkelt anpassa output-cachen i ASP.NET. Tack vare detta kan vi antingen skriva helt anpassade providers för cachen, eller använda oss utav till exempel AppFabric Cache.

Det jag kommer att göra här är att skapa en ny enkel provider för cache. Det kommer inte att bli någon alltför snygg och stabil lösning, utan syftet är bara att visa ett exempel på en fungerande implementation av providern.

Jag kommer börja med att skapa ett ASP.NET MVC-projekt (.NET 4.0), för att sedan skapa en klass som använder singleton pattern för att vi enkelt ska kunna dela med oss utav instansen. Jag kommer sedan att ha en till klass för själva providern.

OutputCacheProvider är en abstrakt klass och har fyra metoder som vi kommer att implementera:

  • Add
  • Get
  • Remove
  • Set

Providern i sig blir väldigt enkel:

using System;
using System.Web.Caching;
 
namespace CustomOutputCacheProvider
{
    public class CustomCacheProvider : OutputCacheProvider
    {
        CustomCache _cache = CustomCache.Instance;
 
        public override object Add(string key, object entry, DateTime expires)
        {
            return _cache.Add(key, entry, expires);
        }
 
        public override object Get(string key)
        {
            return _cache.Get(key);
        }
 
        public override void Remove(string key)
        {
            _cache.Remove(key);
        }
 
        public override void Set(string key, object entry, DateTime expires)
        {
            _cache.Set(key, entry, expires);
        }
    }
}

Det vi gör här är att vi plockar fram instansen av CustomCache, vilket blir den klassen som innehåller all logik. Vi vidarebefordrar sedan alla värden direkt till den klassen då vi vill spara cachen i en gemensam collection.

Vad gäller CustomCache-klassen så kommer jag att börja med att klistra in all kod och sedan förklara den.

using System;
using System.Collections.Generic;
 
namespace CustomOutputCacheProvider
{
    public sealed class CustomCache
    {
        private Dictionary<string, CacheItem> _items = new Dictionary<string, CacheItem>();
 
        CustomCache()
        {
        }
 
        public static CustomCache Instance
        {
            get
            {
                return GetInstance.instance;
            }
        }
 
        class GetInstance
        {
            static GetInstance()
            {
            }
 
            internal static readonly CustomCache instance = new CustomCache();
        }
 
        internal object Add(string key, object entry, DateTime expires)
        {
            if (_items.ContainsKey(key))
            {
                return _items[key].Item;
            }
            else
            {
                _items.Add(key, new CacheItem()
                {
                    Item = entry,
                    Expires = expires
                });
            }
 
            return Get(key);
        }
 
        internal object Get(string key)
        {
            if (!_items.ContainsKey(key))
                return null;
 
            CacheItem item = _items[key];
 
            if (item.Item == null || item.Expires.ToUniversalTime() < DateTime.Now.ToUniversalTime())
            {
                Remove(key);
                return null;
            }
 
            return item.Item;
        }
 
        internal void Remove(string key)
        {
            _items.Remove(key);
        }
 
        internal void Set(string key, object entry, DateTime expires)
        {
            if (_items.ContainsKey(key))
            {
                CacheItem item = _items[key];
                item.Item = entry;
                item.Expires = expires;
 
                _items[key] = item;
            }
            else
            {
                _items.Add(key, new CacheItem()
                {
                    Item = entry,
                    Expires = expires
                });
            }
        }
 
        class CacheItem
        {
            public object Item { get; set; }
            public DateTime Expires { get; set; }
        }
    }
}

Här sker all lagring av värdena. Alla värden sparas i en dictionary där värdet är av typen CacheItem och innehåller värdet och tidpunkten då cachen går ut.

Vad de olika metoderna gör är ganska självförklarande. Vad gäller Add-metoden så är det viktigt att tänka på att vi bara ska spara värdena om de inte redan finns. Om det finns värden sparade så skall dessa returneras och de nya skall kastas. I Set-metoden skall dock gamla värden ersättas med de nya.

För att vi ska kunna använda providern så måste vi ange det i web.config.

<caching>
    <outputCache defaultProvider="CustomCache">
        <providers>
            <add name="CustomCache"
                    type="CustomOutputCacheProvider.CustomCacheProvider,
                    CustomOutputCacheProvider" />
        </providers>
    </outputCache>
</caching>

Det här gör att CustomCacheProvider blir standard-provider för cachen, men det är även möjligt att i global.asax aktivera en annan provider under runtime om man så önskar.

För att vi nu ska kunna testa om det fungerar så kan vi använda oss utav våra färdiga sidor som kom med när vi skapade projektet. Gå in i HomeController och ändra raden där rubriken på sidan i Index-metoden sätts till:

ViewData["Message"] = DateTime.Now.ToLongTimeString();

Vi behöver även aktivera cache för Action-metoden:

[OutputCache(VaryByParam="*",Duration=5)]
public ActionResult Index()

Om vi nu besöker sidan och uppdaterar den så kan vi se att tiden inte uppdateras oftare än var femte sekund. För att vara säkra på att vår provider körs så kan vi sätta en breakpoint i de olika metoderna, vilket även låter oss se exakt vad som sparas.

Det här var ett väldigt enkelt exempel, och andra typer av cache som skulle kunna implementeras kan vara i filsystemet eller i en databas.

1 Comment

Comments have been disabled for this content.