Sunday, May 12, 2013

Alphanumeric Lucene Analyzer for RavenDB

RavenDB's full text indexing uses Lucene.Net

RavenDB is a second generation document database. This means that you can to throw typeless documents into a data store, but the only way to query them is by indexes that are built with Lucene.Net. RavenDB is a wonderful product that's primary strength is it's simplicity and easy of use. In keeping with that theme, even when you need to customize RavenDB, it makes it relatively easy to do.

So, let's talk about customizing your Lucene.Net analyzer in RavenDB!

Available Analyzers

RavenDB comes equipped with all of the analyzers that are built into Lucene.Net. For the vast majority of use cases, these will do the job! Here are some examples:

  • "The fox jumped over the lazy dogs, Bob@hotmail.com 123432."
  • StandardAnalyzer, which is Lucene's default, will produce the following tokens:
    [fox] [jumped] [over] [lazy] [dog] [bob@hotmail.com] [123432]
  • SimpleAnalyzer will tokenize on all non-alpha characters, and will make all the tokens lowercase:
    [the] [fox] [jumped] [over] [the] [lazy] [dogs] [bob] [hotmail] [com]
  • WhitespaceAnalyzer will just tokenize on white spaces:
    [The] [fox] [jumped] [over] [the] [lazy] [dogs,] [Bob@hotmail.com]
    [123432.]

In order to resolve an issue with indexing file names (details below), I found myself in need of an Alphanumeric analyzer. This analyzer would be similar to the SimpleAnalyzer, but would still respect numeric values.

  • AlphanumericAnalyzer will tokenize on the .NET framework's Char.IsDigitOrLetter:
    [fox] [jumped] [over] [lazy] [dogs] [bob] [hotmail] [com] [123432]

Lucene.Net's base classes made this pretty easy to build...

How to Implement a Custom Analyzer

Grab all the code and more from GitHub:

Raven.Extensions.AlphanumericAnalyzer on GitHub

A lucene analyzer is made of two basic parts, 1) a tokenizer, and 2) a series of filters. The tokenizer does the lions share of the work and splits the input apart, then the filters run in succession making additional tweaks to the tokenized output.

To create the Alphanumeric Analyzer we need only create two classes, an analyzer and a tokenizer. After that the analyzer can use reuse the existing LowerCaseFilter and StopFilter classes.

AlphanumericAnalyzer

public sealed class AlphanumericAnalyzer : Analyzer
{
    public AlphanumericAnalyzer(Version matchVersion, ISet<string> stopWords)
    {
        _enableStopPositionIncrements = StopFilter
            .GetEnablePositionIncrementsVersionDefault(matchVersion);
        _stopSet = stopWords;
    }
 
    public override TokenStream TokenStream(String fieldName, TextReader reader)
    {
        TokenStream tokenStream = new AlphanumericTokenizer(reader);
        tokenStream = new LowerCaseFilter(tokenStream);
        tokenStream = new StopFilter(
            _enableStopPositionIncrements, 
            tokenStream, 
            _stopSet);
 
        return tokenStream;
    }

AlphanumericTokenizer

public class AlphanumericTokenizer : CharTokenizer
{
    protected override bool IsTokenChar(char c)
    {
        return Char.IsLetterOrDigit(c);
    }

How to Install Plugins in RavenDB

Installing a custom plugin to RavenDB is unbelievably easy. Just compile your assembly, and then drop it into the Plugins folder at the root of your RavenDB server. You may then reference the analyzers in your indexes by using their fully assembly qualified names.

Again, you can grab all of the code and more over on GitHub:

Raven.Extensions.AlphanumericAnalyzer on GitHub

Shout it

Enjoy,
Tom

Friday, April 26, 2013

String Extensions: Split Qualified

Splitting on a string is easy.
Respecting qualified (quoted) strings can be hard.
Identifying escaped characters in qualified strings is very tricky.
Splitting on a qualified string that takes escape characters into account is really difficult!

Unit Tests

[Theory]
[InlineData(null,                   new string[0])]
[InlineData("",                     new string[0])]
[InlineData("hello world",          new[] { "hello", "world" })]
[InlineData("hello   world",        new[] { "hello", "world" })]
[InlineData("\"hello world\"",      new[] { "\"hello world\"" })]
[InlineData("\"hello  world\"",     new[] { "\"hello  world\"" })]
[InlineData("hello \"goodnight moon\" world", new[]
{
    "hello", 
    "\"goodnight moon\"", 
    "world", 
})]
[InlineData("hello \"goodnight \\\" moon\" world", new[]
{
    "hello", 
    "\"goodnight \\\" moon\"", 
    "world", 
})]
[InlineData("hello \"goodnight \\\\\" moon\" world", new[]
{
    "hello", 
    "\"goodnight \\\\\"", 
    "moon\"", 
    "world", 
})]
public void SplitQualified(string input, IList<string> expected)
{
    var actual = input
        .SplitQualified(' ', '"')
        .ToList();
 
    Assert.Equal(expected.Count, actual.Count);
 
    for (var i = 0; i < actual.Count; i++)
        Assert.Equal(expected[i], actual[i]);
}

String Extension Methods

public static IEnumerable<string> SplitQualified(
    this string input, 
    char separator, 
    char qualifier, 
    StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries, 
    char escape = '\\')
{
    if (String.IsNullOrWhiteSpace(input))
        return new string[0];
 
    var results = SplitQualified(input, separator, qualifier, escape);
 
    return options == StringSplitOptions.None
        ? results
        : results.Where(r => !String.IsNullOrWhiteSpace(r));
}
 
private static IEnumerable<string> SplitQualified(
    string input, 
    char separator, 
    char qualifier, 
    char escape)
{
    var separatorIndexes = input
        .IndexesOf(separator)
        .ToList();
 
    var qualifierIndexes = input
        .IndexesOf(qualifier)
        .ToList();
 
    // Remove Escaped Qualifiers
    for (var i = 0; i < qualifierIndexes.Count; i++)
    {
        var qualifierIndex = qualifierIndexes[i];
        if (qualifierIndex == 0)
            continue;
 
        if (input[qualifierIndex - 1] != escape)
            continue;
 
        // Watch out for a series of escaped escape characters.
        var escapeResult = false;
        for (var j = 2; qualifierIndex - j > 0; j++)
        {
            if (input[qualifierIndex - j] == escape)
                continue;
 
            escapeResult = j % 2 == 1;
            break;
        }
 
        if (qualifierIndex > 1 && escapeResult)
            continue;
 
        qualifierIndexes.RemoveAt(i);
        i--;
    }
 
    // Remove Qualified Separators
    if (qualifierIndexes.Count > 1)
        for (var i = 0; i < separatorIndexes.Count; i++)
        {
            var separatorIndex = separatorIndexes[i];
 
            for (var j = 0; j < qualifierIndexes.Count - 1; j += 2)
            {
                if (separatorIndex <= qualifierIndexes[j])
                    continue;
 
                if (separatorIndex >= qualifierIndexes[j + 1])
                    continue;
 
                separatorIndexes.RemoveAt(i);
                i--;
            }
        }
 
    // Split String On Separators
    var previousSeparatorIndex = 0;
    foreach (var separatorIndex in separatorIndexes)
    {
        var startIndex = previousSeparatorIndex == 0
            ? previousSeparatorIndex
            : previousSeparatorIndex + 1;
 
        var endIndex = separatorIndex == input.Length - 1
            || previousSeparatorIndex == 0
            ? separatorIndex - previousSeparatorIndex
            : separatorIndex - previousSeparatorIndex - 1;
 
        yield return input.Substring(startIndex, endIndex);
 
        previousSeparatorIndex = separatorIndex;
    }
 
    if (previousSeparatorIndex == 0)
        yield return input;
    else
        yield return input.Substring(previousSeparatorIndex + 1);
}
 
public static IEnumerable<int> IndexesOf(
    this string input, 
    char value)
{
    if (!String.IsNullOrWhiteSpace(input))
    {
        var index = -1;
        do
        {
            index++;
            index = input.IndexOf(value, index);
 
            if (index > -1)
                yield return index;
            else
                break;
        }
        while (index < input.Length);
    }
}
Shout it

Enjoy,
Tom

Saturday, April 13, 2013

Report Unhandled Errors from JavaScript

Logging and aggregating error reports is one of the most important things you can do when building software: 80% of customer issues can be solved by fixing 20% of the top-reported bugs.

Almost all websites at least has some form of error logging on their servers, but what about the client side of those websites? People have a tendency to brush over best practices for client side web development because "it's just some scripts." That, is, WRONG! Your JavaScript is your client application, it is how users experience your website, and as such it needs the proper attention and maintenance as any other rich desktop application.

So then, how do you actually know when your users are experiencing errors in their browser? If you are like the vast majority of websites out there...

You don't know about JavaScript errors, and it's time to fix that!

window.onerror

Browsers do offer a way to get notified of all unhandled exceptions, that is the window.onerror event handler. You can wire a listener up to this global event handler and get back three parameters: the error message, the URL of the file in which the script broke, and the line number where the exception was thrown.

window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
  // TODO: Something with this exception!
  // Just let default handler run.
  return false;
}

StackTrace.js

JavaScript can throw exceptions like any other language; browser debugging tools often show you a full stack trace for unhandled exceptions, but gathering that information programmatically is a bit more tricky. To learn a bit more about the JavaScript language and how to gather this information yourself, I suggest taking a look at this article by Helen Emerson. However in practice I would strongly suggest you use a more robust tool...

StackTrace.js is a very powerful library that will build a fully detailed stack trace from an exception object. It has a simple API, cross browser support, it handles fringe cases, and is very light weight and unobtrusive to your other JS libraries.

try {
    // error producing code
} catch(error) {
   // Returns stacktrace from error!
   var stackTrace = printStackTrace({e: error});
}

Two Big Problems

  1. The window.onerror callback does not contain the actual error object.

This is a big problem because without the error object you cannot rebuild the stack trace. The error message is always useful, but file names and line numbers will be completely useless once you have minified your code in production. Currently, the only way you can bring additional information up to the onerror callback is to try catch any exceptions that you can and store the error object in a closure or global variable.

  1. If you globally try catch event handlers it will be harder to use a debugger.

It would not be ideal to wrap every single piece of code that you write in an individual try catch block, and if you try to wrap your generic event handling methods in try catches then those catch blocks will interrupt your debugger when you are working with code in development.

Currently my suggestion is to go with the latter option, but only deploy those interceptors with your minified or production code.

jQuery Solution

This global error handling implementation for jQuery and ASP.NET MVC is only 91 lines of JavaScript and 62 lines of C#.

Download JavaScriptErrorReporter from GitHub

To get as much information as possible, you need to wire up to three things:
(Again, I suggest that you only include this when your code is minified!)

  1. window.onerror
  2. $.fn.ready
  3. $.event.dispatch

Here is the meat of those wireups:

var lastStackTrace,
    reportUrl = null,
    prevOnError = window.onerror,
    prevReady = $.fn.ready,
    prevDispatch = $.event.dispatch;
 
// Send global methods with our wrappers.
window.onerror = onError;
$.fn.ready = readyHook;
$.event.dispatch = dispatchHook;
 
function onError(error, url, line) {
    var result = false;
    try {
        // If there was a previous onError handler, fire it.
        if (typeof prevOnError == 'function') {
            result = prevOnError(error, url, line);
        }
        // If the report URL is not loaded, load it.
        if (reportUrl === null) {
            reportUrl = $(document.body).attr('data-report-url') || false;
        }
        // If there is a rport URL, send the stack trace there.
        if (reportUrl !== false) {
            var stackTrace = getStackTrace(error, url, line, lastStackTrace);
            report(error, stackTrace);
        }
    } catch (e) {
        // Something went wrong, log it.
        if (console && console.log) {
            console.log(e);
        }
    } finally {
        // Clear the wrapped stack so it does get reused.
        lastStackTrace = null;
    }
    return result;
}
 
function readyHook(fn) {
    // Call the original ready method, but with our wrapped interceptor.
    return prevReady.call(this, fnHook);
 
    function fnHook() {
        try {
            fn.apply(this, arguments);
        } catch (e) {
            lastStackTrace = printStackTrace({ e: e });
            throw e;
        }
    }
}
 
function dispatchHook() {
    // Call the original dispatch method.
    try {
        prevDispatch.apply(this, arguments);
    } catch (e) {
        lastStackTrace = printStackTrace({ e: e });
        throw e;
    }
}

Identifying Duplicate Errors

One last thing to mention is that when your stack trace arrives on the server it will contain file names and line numbers. The inconsistency of these numbers will make it difficult to identify duplicate errors. I suggest that you "clean" the stack traces by removing this extra information when trying to create a unique error hash.

private static readonly Regex LineCleaner
    = new Regex(@"\([^\)]+\)$", RegexOptions.Compiled);
 
private int GetUniqueHash(string[] stackTrace)
{
    var sb = new StringBuilder();
 
    foreach (var stackLine in stackTrace)
    {
        var cleanLine = LineCleaner
            .Replace(stackLine, String.Empty)
            .Trim();
 
        if (!String.IsNullOrWhiteSpace(cleanLine))
            sb.AppendLine(cleanLine);
    }
 
    return sb
        .ToString()
        .ToLowerInvariant()
        .GetHashCode();
}

Integration Steps

This article was meant to be more informational than tutorial; but if you are interested in trying to apply this to your site, here are the steps that you would need to take:

  1. Download JavaScriptErrorReporter from GitHub.
  2. Include StackTrace.js as a resource in your website.
  3. Include ErrorReporter.js as a resource in your website.
    • Again, to prevent it interfering with your JavaScript debugger, I suggest only including this resource when your scripts are being minified.
  4. Add a report error action to an appropriate controller. (Use the ReportError action on the HomeController as an example.)
  5. Add a "data-report-url" attribute with the fully qualified path to your report error action to the body tag of your pages.
  6. Log any errors that your site reports!
Shout it

Enjoy,
Tom

Thursday, March 28, 2013

Run Multiple Tasks in a Windows Service

ServicesBase.Run(ServiceBase[] services) ...that sure made me think you could run multiple implementations of ServiceBase in a single Windows Service; but that is not how it works.

One Service, Multiple Tasks

I often have a very simple use case: I needed to run multiple tasks in a single windows service. For example, one task to poll some email notifications, and one task to listen to HTTP requests that might trigger those same notifications.

I like the basic setup of OnStart, Run, and OnStop, but after authoring several windows services I got tired of writing the same code over and over again. Thus I created the WindowServiceTasks library. Now whenever I need to do another task in a service, I just implement the WindowsServiceTaskBase or WindowsServiceLoopBase class, and my code is all ready to go!

WindowsServiceTasks

Run multiple tasks in a single WindowsService, and let the base library do all of the work setup and tear down for you. The WindowsServiceTasks NuGet package is simple, flexible, and extremely light weight.

Example

public static class Program
{
    public static void Main(params string[] args)
    {
        var logger = new Logger("Demo.log");
        var emailTask = new EmailServiceTask(logger);
        var failTask = new FailServiceTask(logger);
 
        var service = new Service1(logger, emailTask, failTask);
        service.Start(args);
    }
}
public class EmailServiceTask : WindowsServiceLoopBase
{
    private readonly ILogger _logger;
 
    public EmailServiceTask(ILogger logger)
    {
        _logger = logger;
    }
 
    protected override int LoopMilliseconds
    {
        get { return 2000; }
    }
 
    protected override void HandleException(Exception exception)
    {
        _logger.Log(exception);
    }
 
    protected override void RunLoop()
    {
        // TODO Send Email!
        _logger.Log("EmailServiceTask.RunLoop - Sending an email");
    }
 
    public override void OnStart(string[] args)
    {
        _logger.Log("EmailServiceTask.OnStart");
    }
 
    public override void OnStop()
    {
        _logger.Log("EmailServiceTask.OnStop");
    }
 
    protected override void DisposeResources()
    {
    }
}
Shout it

Enjoy,
Tom

Sunday, March 17, 2013

Castlevania 3DS - Demo Review

TL;DR - After only 5 minutes, it's pretty terrible.

I had the privilege of attending E3 last year and seeing the premier footage of Castlevania: Lords of Shadow - Mirror of Fate. At the time, was I excited! Enjoy a good Castlevania game, my favorites being Dracula's Curse and Symphony of the Night. These were games that classic hack and slash platforms; they were engaging, colorful, and just down right fun to play.

Before I review the demo, I want to comment on the title, of which this game has three: 1) Castlevania 2) Lords of Shadow 3) Mirror of Fate. This game is a spin off of a reboot of the Castlevania franchise. This name made me wary because it seemed, at least to me, to imply a lack of identity or originality in this game itself.

...but enough hearsay, let's get to the demo!

Starting The Demo

I launched the demo and my first thought of the title screen just looked generic, so I immediately launched the game. First the loading screen popped up and showed me the controls for the game, which seems a like a large number of buttons/abilities to dump on the users one second into the game. Also, it did nothing to help set the atmosphere or tone of the game. On the other hand, I like that there was no distracting, interrupting, or hand holding tutorial.

Once the game loads the then camera zooms around our protagonist in front of a giant castle and then you are left to run across a bridge. There was no dialog or exposition, which could be fine, but then I'm not sure who I am or even if this is Dracula's castle. Some headings or subtitles could have been useful to help set the scene.

Slow Clunky Combat

Once I ran I immediately had to fight some skeletons, one of which had a shield. I took a few swings with my whip, which felt slow and clunky, and it seemed to do very little to no damage to the enemies. I then moved to jump away from their attacks only to find that my character could not move until his attack animation was done, making him get clobbered by the shielded skeleton.

After mindlessly whacking away at the skeletons for a while, and taking a decent amount of damage while I was at it, I finally manged to kill one; and then a big game pausing modal popped up telling me that I had killed an enemy and earned experience points. This completely sucked me out of the immersion of the game, I stopped fighting and had to read a completely useless piece of ugly text telling me something that I already knew...not a good start!

An Under Powered Protagonist

I finally managed to kill other two skeletons and proceed forward. I then killed two more little skeletons, each taking 4 or 5 hits to take down. This was also bothersome because it made my main character feel extremely weak. I am a vampire hunter single-handedly charging into what I can only assume is Dracula's castle, and it takes me 5 attacks to kill a single entry level skeleton? That is not at all impressive.

So now, after struggling to kill 5 skeletons, the first mini boss appears. It is a big fat unmemorable armored guy with a wreaking ball flail, at whom I proceed to swing my whip. Each time that I hit him he flashes red but I there are no damage animations or pauses, nor is there a health meter anyone on the HUD; so even after a dozen attacks I still have no idea how much damage I am doing to him, if any at all. However a tool tip did appear on the screen telling me to press R to hold the enemy, but every time I pressed R absolutely nothing happened.

...and so I died on the first mini boss.
...multiple times.

A Second Opinion

At this point I was very disappointed and frustrated by the demo because, despite any particular complaint I had with the game, I was simply not having fun. My girlfriend was waking up around this time and asked me what I was doing, and so without making any comments or explanations I had passed her my 3DS XL and had her try to demo.

She is usually better than me at most games, and she died on the first set of skeletons! She complained numerous times that she could not jump away from them when they attacked, and she also noted that the tool tip telling her to press R did absolutely nothing. Of course I then explained that I had experienced the exact same problems while I was playing the game, and we both agreed that this was a pretty shitty game,or at least a pretty shitty demo.

(Hours later I had a friend of mine try this demo and got very similar reactions from him. His solution to the shielded skeleton was to jump over it and run away, because he did not think that it could be killed.)

I should have stopped there, but I really wanted to give Castlevania another chance to win me over.

The Boring Boomerang

I finally beat the first mini boss and proceeded into the next room in the castle. Visual this room was bland and boring, and made me run across it for seemly no reason. However at the end of the room, I got my first secondary weapon: the boomerang. This got me excited, that was by far the best secondary weapon in Castlevania 3, and I was immediately stoked about trying it out. Right on queue, some bats broke through a window in the background and volunteered themselves for boomerang target practice.

When I threw the boomerang at the bats, nothing happened. I mean the boomerang left my hand a few into the bat, but the bat did not react! The boomerang just stuck out of him with no auditory or visual queues as to the bat being injured, let alone killed. I had to throw three boomerangs into one bat to kill it, and then three more into another, until finally I was out of boomerangs and had to kill the final bat with my stupid whip.

When you first get the boomerang in Castlevania 3, you use it to fight zombies and a single boomerang rips through and destroys dozens of zombies at a time! It was powerful, it was cool, it was infinitely better than this latest iteration's piece of crap.

Final Verdict

Gameplay - 3/10
The controls are sluggish, the weapons are weak, and ammo is limited. This game does not feel like a fun fast paced Castlevania, it plays like a slow boring iOS clone.

Graphics - 6/10
The game looks pretty good, but it uses a very dark and drab color pallet and none of the art itself stands out in any way. Also, there are no impressive spectacles in the intro, just rooms with flat backgrounds.

Music - 0/10
This soundtrack is so bland and forgettable that I had to go back and replay the demo to just see if there was any music at all. This is elevator music, not what a Castlevania game should sound like.

Story - N/A
I like it when games start out quick and do not get bogged down in complex exposition, but Castlevania: Lords of Shadow - Mirror of Fate -- Demo Edition --- We Love Titles ---- On the 3DS eShop has the opposite problem; I am not even sure what castle I was in.

I really hope that this demo is not indicative of the final game, unfortunately it is a pretty safe bet that it is.

Game on,
Tom

Thursday, February 28, 2013

Running a Windows Service as a Console App

This is a prerequisite post before I can talk about the WindowsServiceTasks library that I recently released.

How do you test windows services?

I hope your first response was unit tests, but even if it was we both know that you still have to integration test the whole application. You could install your service into windows and then attach your debugger to it's process, but that is a lot of work. Why not just run your service as a console application? Then you can test it by just pressing play in Visual Studio!

How do you run a windows service as a console application?

Easy! Just go into your windows service project's settings and set the Output Type to Console Application. This will allow the executable to launch as a console app, but it is still completely executable as a service!

Inside of your code, you can use the Environment.UserInteractive property to determine which mode you are in. With that you can do cool things to help you debug, such as register an alternate logger with your IOC container that will write your long lines to console instead of (or as well as) to file.

static class Program
{
    static void Main(params string[] args)
    {
        var service = new Service1();
 
        if (!Environment.UserInteractive)
        {
            var servicesToRun = new ServiceBase[] { service };
            ServiceBase.Run(servicesToRun);
            return;
        }
 
        Console.WriteLine("Running as a Console Application");
        Console.WriteLine(" 1. Run Service");
        Console.WriteLine(" 2. Other Option");
        Console.WriteLine(" 3. Exit");
        Console.Write("Enter Option: ");
        var input = Console.ReadLine();
 
        switch (input)
        {
            case "1":
                service.Start(args);
                Console.WriteLine("Running Service - Press Enter To Exit");
                Console.ReadLine();
                break;
 
            case "2":
                // TODO!
                break;
        }
 
        Console.WriteLine("Closing");
    }
}
 
public partial class Service1 : ServiceBase
{
    public Service1() { InitializeComponent(); }
 
    public void Start(string[] args) { OnStart(args); }
 
    protected override void OnStart(string[] args) { }
 
    protected override void OnStop() { }
}

What other uses does this offer?

You can then also use your windows services as management utilities. I have worked with applications before where when I needed to make a configuration change to a system I was told to go run windows services with random undiscoverable command line arguments or magic number configuration settings. By launching your service as a console app you can inject simple menu systems to allow users easy access to admin functionality.

Shout it

Enjoy,
Tom

Sunday, February 17, 2013

Cache Repository for MVC

First and foremost, the CacheRepository is NOT web specific!

The CacheRepository library contains the abstract CacheRepositoryBase class, which can be implemented by any caching system that you choose. The CacheRepository.Web NuGet package includes a WebCacheRepository implementation that leverages System.Web.Caching.Cache

CacheRepository.Web

Get all of the source code, unit tests, and a complete MVC4 sample application (preconfigured to include dependency injection with Unity) from GitHub. Or to just jump in and start using it, grab the CacheRepository.Web package from NuGet.

ICacheRepository

The ICacheRepository interface has all the standard Get, Set, Remove and Clear methods. Additionally, these methods have been expanded to include enum parameters to group manage cache expiration.

Best of all, it includes GetOrSet methods. These methods will try to get the value of the specified key, and when it can not find that value it will then be loaded via the passed in Func. A key feature here is the fact that the setter of the GetOrSet will lock on load to prevent redundant data loads. (More details about this below.)

public interface ICacheRepository
{
    object Get(string key);
    T Get<T>(string key);
 
    T GetOrSet<T>(string key, Func<T> loader);
    T GetOrSet<T>(string key, Func<T> loader, DateTime expiration);
    T GetOrSet<T>(string key, Func<T> loader, TimeSpan sliding);
    T GetOrSet<T>(string key, Func<T> loader, CacheExpiration expiration);
    T GetOrSet<T>(string key, Func<T> loader, CacheSliding sliding);
 
    void Set<T>(string key, T value);
    void Set<T>(string key, T value, DateTime expiration);
    void Set<T>(string key, T value, TimeSpan sliding);
    void Set<T>(string key, T value, CacheExpiration expiration);
    void Set<T>(string key, T value, CacheSliding sliding);
 
    void Remove(string key);
    void ClearAll();
}

ThreadSafe GetOrSet

As any good cache should, the CacheRepository is thread safe and can be treated as a singleton.

Even better, the GetOrSet methods lock on set. This means that 10 threads could be trying to load the same cache value simultaneously, but only one will actually trigger a load and set. This helps redundant resource loads and database calls and keep your application running optimally.

private T GetOrSet<T>(string key, Func<T> loader,
    DateTime? expiration, TimeSpan? sliding)
{
    // Get It
    T value;
    var success = TryGet(key, out value);
 
    // Got It or No Loader
    if (loader == null || success)
        return value;
 
    // Load It
    return LockedInvoke(key, () =>
    {
        // Get It (Again)
        success = TryGet(key, out value);
 
        if (!success)
        {
            // Load It (For Real)
            value = loader();
 
            // Set It
            if (value != null)
                Set(key, value, expiration, sliding);
        }
 
        return value;
    });
}

Configurable Expiration Enums

Instead of being forced to set each of your cache expirations separately, you now have the option to use an enum value to group your cache expirations. Enums are available for both absolute expiration and sliding expiration.

You may override the duration of each cache key in your app config; this is really useful for when you want to uniformly modify your cache expirations in your QA or testing environments. Additionally, you may override the CacheRepositoryBase.GetConfigurationValue method to pull this configuration for anywhere you want, not just the ConfigurationManager.

public enum CacheExpiration
{
    VeryShort = 10,     // Ten Seconds
    Short = 60,         // One Minute
    Medium = 300,       // Five Minutes
    Long = 3600,        // One Hour
    VeryLong = 86400    // One Day
}

<configuration>
  <appSettings>
    <add key="CacheExpiration.VeryShort" value="15" />
    <add key="CacheExpiration.Short" value="90" />

ByType Extensions

Transparent cache key management is offered through a series of ICacheRepository extension methods. This makes caching even easier, as you don't need to worry about cache key collisions.

Using these extension methods, you many cache a Cat object with an Id of 1 and a Dog object with an Id of 1 in the same manner. The extension methods will create a key prefix based on the type and then use the specified identified as the suffix.

[Fact]
public void SameKeyDifferentType()
{
    var setCat = new Cat { Name = "Linq" };
    CacheRepository.SetByType(1, setCat);
 
    var setDog = new Dog { Name = "Taboo" };
    CacheRepository.SetByType(1, setDog);
 
    var getCat = CacheRepository.GetByType<Cat>(1);
    Assert.Equal(setCat.Name, getCat.Name);
 
    var getDog = CacheRepository.GetByType<Dog>(1);
    Assert.Equal(setDog.Name, getDog.Name);
}

Web Implementation & Dependency Injection via Unity

The CacheRepository.Web NuGet package includes a WebCacheRepository implementation that leverages System.Web.Caching.Cache. This is a very simple, but very effective, production ready implementation of the CacheRepositoryBase.

It should also be noted that this implementation is not only useful in ASP.NET, the System.Web.Caching.Cache is located in the System.Web library, but it still available outside of a web context. This means that this implementation can be used in other applications, including but not limited to Windows Services.

public class WebCacheRepository : CacheRepositoryBase
{
    private readonly Cache _cache;
        
    public WebCacheRepository()
    {
        _cache = HttpContext.Current == null
            ? HttpRuntime.Cache
            : HttpContext.Current.Cache;
    }

Wiring the cache repository to be injected into your controllers via Unity is also very easy. Just register the WebCacheRepository for the ICacheRepository interface, and be sure to provide a ContainerControlledLifetimeManager for optimal performance.

To use the implementation below, you will need to include the Unity.MVC3 NuGet Package to gain access to the UnityDependencyResolver.

public static void Initialise()
{
    var container = BuildUnityContainer();
    
    DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
 
private static IUnityContainer BuildUnityContainer()
{
    var container = new UnityContainer();
 
    container.RegisterType<ICacheRepository, WebCacheRepository>(
        new ContainerControlledLifetimeManager());           
 
    return container;
}

If you missed the links above, here they are again:

Shout it

Enjoy,
Tom