• Stars
    star
    882
  • Rank 51,756 (Top 2 %)
  • Language
    C#
  • License
    Other
  • Created over 12 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

ASP.NET Web API CacheOutput - library to allow you to cache the output of ApiControllers

πŸ“’ This repo is no longer maintained πŸ“’

First of all, I apologize it took so long to make an announcement, but I think it has been clear to everyone from the update frequency that this repo is no longer maintained.

I would therefore like to officially (re)state that, indeed, this repo and the corresponding library is no longer maintained. I will archive it soon.

It served its purpose back in the ASP.NET Web API days, and I hope it helped some people build their products. The ASP.NET landscape is obviously much different now, and, given many other involvements, I do not have the capacity or energy to keep this legacy thing alive. I am sorry if anyone feels let down by this. If you are using it you and still need it, you are of course free to fork and make any changes you wish.

I would like to thank almost 1k people who starred this repo and everyone who contributed to over 3 million downloads on Nuget. Thank you all.

ASP.NET Web API CacheOutput

A small library bringing caching options, similar to MVC's "OutputCacheAttribute", to Web API actions.

CacheOutput will take care of server side caching and set the appropriate client side (response) headers for you.

You can specify the following properties:

  • ClientTimeSpan (corresponds to CacheControl MaxAge HTTP header)
  • MustRevalidate (corresponds to MustRevalidate HTTP header - indicates whether the origin server requires revalidation of a cache entry on any subsequent use when the cache entry becomes stale)
  • ExcludeQueryStringFromCacheKey (do not vary cache by querystring values)
  • ServerTimeSpan (time how long the response should be cached on the server side)
  • AnonymousOnly (cache enabled only for requests when Thread.CurrentPrincipal is not set)

Additionally, the library is setting ETags for you, and keeping them unchanged for the duration of the caching period. Caching by default can only be applied to GET actions.

Installation

You can build from the source here, or you can install the Nuget version:

For Web API 2 (.NET 4.5)

PM> Install-Package Strathweb.CacheOutput.WebApi2

For Web API 1 (.NET 4.0)

PM> Install-Package Strathweb.CacheOutput

Usage

//Cache for 100 seconds on the server, inform the client that response is valid for 100 seconds
[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100)]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

//Cache for 100 seconds on the server, inform the client that response is valid for 100 seconds. Cache for anonymous users only.
[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100, AnonymousOnly = true)]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

//Inform the client that response is valid for 50 seconds. Force client to revalidate.
[CacheOutput(ClientTimeSpan = 50, MustRevalidate = true)]
public string Get(int id)
{
    return "value";
}

//Cache for 50 seconds on the server. Ignore querystring parameters when serving cached content.
[CacheOutput(ServerTimeSpan = 50, ExcludeQueryStringFromCacheKey = true)]
public string Get(int id)
{
    return "value";
}

Variations

CacheOutputUntil is used to cache data until a specific moment in time. This applies to both client and server.

//Cache until 01/25/2013 17:00
[CacheOutputUntil(2013,01,25,17,00)]
public string Get_until25012013_1700()
{
    return "test";
}

CacheOutputUntilToday is used to cache data until a specific hour later on the same day. This applies to both client and server.

//Cache until 23:55:00 today
[CacheOutputUntilToday(23,55)]
public string Get_until2355_today()
{
    return "value";
}

CacheOutputUntilThisMonth is used to cache data until a specific point later this month. This applies to both client and server.

//Cache until the 31st day of the current month
[CacheOutputUntilThisMonth(31)]
public string Get_until31_thismonth()
{
    return "value";
}

CacheOutputUntilThisYear is used to cache data until a specific point later this year. This applies to both client and server.

//Cache until the 31st of July this year
[CacheOutputUntilThisYear(7,31)]
public string Get_until731_thisyear()
{
    return "value";
}

Each of these can obviously be combined with the 5 general properties mentioned in the beginning.

Caching convention

In order to determine the expected content type of the response, CacheOutput will run Web APIs internal content negotiation process, based on the incoming request & the return type of the action on which caching is applied.

Each individual content type response is cached separately (so out of the box, you can expect the action to be cached as JSON and XML, if you introduce more formatters, those will be cached as well).

Important: We use action name as part of the key. Therefore it is necessary that action names are unique inside the controller - that's the only way we can provide consistency.

So you either should use unique method names inside a single controller, or (if you really want to keep them the same names when overloading) you need to use ActionName attribute to provide uniqeness for caching. Example:

[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
public IEnumerable<Team> Get()
{
    return Teams;
}

[ActionName("GetById")]
[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
public IEnumerable<Team> Get(int id)
{
    return Teams;
}

If you want to bypass the content negotiation process, you can do so by using the MediaType property:

[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50, MediaType = "image/jpeg")]
public HttpResponseMessage Get(int id)
{
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = GetImage(id); // e.g. StreamContent, ByteArrayContent,...
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
    return response;
}

This will always return a response with image/jpeg as value for the Content-Type header.

Ignoring caching

You can set up caching globally (add the caching filter to HttpConfiguration) or on controller level (decorate the controller with the cahcing attribute). This means that caching settings will cascade down to all the actions in your entire application (in the first case) or in the controller (in the second case).

You can still instruct a specific action to opt out from caching by using [IgnoreCacheOutput] attribute.

[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
public class IgnoreController : ApiController
{
    [Route("cached")]
    public string GetCached()
    {
	return DateTime.Now.ToString();
    }

    [IgnoreCacheOutput]
    [Route("uncached")]
    public string GetUnCached()
    {
	return DateTime.Now.ToString();
    }
}

Server side caching

By default CacheOutput will use System.Runtime.Caching.MemoryCache to cache on the server side. However, you are free to swap this with anything else (static Dictionary, Memcached, Redis, whatever..) as long as you implement the following IApiOutputCache interface (part of the distributed assembly).

public interface IApiOutputCache
{
	T Get<T>(string key) where T : class;
	object Get(string key);
	void Remove(string key);
	void RemoveStartsWith(string key);
	bool Contains(string key);
	void Add(string key, object o, DateTimeOffset expiration, string dependsOnKey = null);
}

Suppose you have a custom implementation:

public class MyCache : IApiOutputCache
{
	// omitted for brevity
}

You can register your implementation using a handy GlobalConfiguration extension method:

//instance
configuration.CacheOutputConfiguration().RegisterCacheOutputProvider(() => new MyCache());

// singleton
var cache = new MyCache();
configuration.CacheOutputConfiguration().RegisterCacheOutputProvider(() => cache);

If you prefer CacheOutput to use resolve the cache implementation directly from your dependency injection provider, that's also possible. Simply register your IApiOutputCache implementation in your Web API DI and that's it. Whenever CacheOutput does not find an implementation in the GlobalConiguration, it will fall back to the DI resolver. Example (using Autofac for Web API):

cache = new MyCache();
var builder = new ContainerBuilder();
builder.RegisterInstance(cache);
config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build());

If no implementation is available in neither GlobalConfiguration or DependencyResolver, we will default to System.Runtime.Caching.MemoryCache.

Each method can be cached multiple times separately - based on the representation (JSON, XML and so on). Therefore, CacheOutput will pass dependsOnKey value (which happens to be a prefix of all variations of a given cached method) when adding items to cache - this gives us flexibility to easily remove all variations of the cached method when we want to clear the cache. When cache gets invalidated, we will call RemoveStartsWith and just pass that key.

The default cache store, System.Runtime.Caching.MemoryCache supports dependencies between cache items, so it's enough to just remove the main one, and all sub-dependencies get flushed. However, if you change the defalt implementation, and your underlying store doesn't - it's not a problem. When we invalidate cache (and need to cascade through all dependencies), we call RemoveStartsWith - so your custom store will just have to iterate through the entire store in the implementation of this method and remove all items with the prefix passed.

Cache invalidation

There are three ways to invalidate cache:

  • [AutoInvalidateCacheOutput] - on the controller level (through an attribute)
  • [InvalidateCacheOutput("ActionName")] - on the action level (through an attribute)
  • Manually - inside the action body

Example:

[AutoInvalidateCacheOutput]
public class Teams2Controller : ApiController
{
	[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
	public IEnumerable<Team> Get()
	{
	    return Teams;
	}

	public void Post(Team value)
	{
	    //do stuff
	}
}

Decorating the controller with [AutoInvalidateCacheOutput] will automatically flush all cached GET data from this controller after a successfull POST/PUT/DELETE request.

You can also use the [AutoInvalidateCacheOutput(TryMatchType = true)] variation. This will only invalidate such GET requests that return the same Type or IEnumerable of Type as the action peformed takes as input parameter.

For example:

[AutoInvalidateCacheOutput(TryMatchType = true)]
public class TeamsController : ApiController
{
	[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
	public IEnumerable<Team> Get()
	{
	    return Teams;
	}

	[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
	public IEnumerable<Player> GetTeamPlayers(int id)
	{
	    //return something
	}

	public void Post(Team value)
	{
	    //this will only invalidate Get, not GetTeamPlayers since TryMatchType is enabled
	}
}

Invalidation on action level is similar - done through attributes. For example:

public class TeamsController : ApiController
{
	[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
	public IEnumerable<Team> Get()
	{
	    return Teams;
	}

	[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
	public IEnumerable<Player> GetTeamPlayers(int id)
	{
	    //return something
	}

	[InvalidateCacheOutput("Get")]
	public void Post(Team value)
	{
	    //this invalidates Get action cache
	}
}

Obviously, multiple attributes are supported. You can also invalidate methods from separate controller:

[InvalidateCacheOutput("Get", typeof(OtherController))] //this will invalidate Get in a different controller
[InvalidateCacheOutput("Get")] //this will invalidate Get in this controller
public void Post(Team value)
{
    //do stuff
}

Finally, you can also invalidate manually. For example:

public void Put(int id, Team value)
{
    // do stuff, update resource etc.

    // now get cache instance
    var cache = Configuration.CacheOutputConfiguration().GetCacheOutputProvider(Request);

    // and invalidate cache for method "Get" of "TeamsController"
    cache.RemoveStartsWith(Configuration.CacheOutputConfiguration().MakeBaseCachekey((TeamsController t) => t.Get()));
}

As you see, you can we use expression try to allow you to point to the method in a strongly typed way (we can't unfortunately do that in the attributes, since C# doesn't support lambdas/expression trees in attributes).

If your method takes in arguments, you can pass whatever - we only use the expression tree to get the name of the controller and the name of the action - and we invalidate all variations.

You can also point to the method in a traditional way:

cache.RemoveStartsWith(Configuration.CacheOutputConfiguration().MakeBaseCachekey("TeamsController", "Get"));

Customizing the cache keys

You can provide your own cache key generator. To do this, you need to implement the ICacheKeyGenerator interface. The default implementation should suffice in most situations.

When implementing, it is easiest to inherit your custom generator from the DefaultCacheKeyGenerator class.

To set your custom implementation as the default, you can do one of these things:

// Method A: register directly
Configuration.CacheOutputConfiguration().RegisterDefaultCacheKeyGeneratorProvider(() => new CustomCacheKeyGenerator());

// Method B: register for DI (AutoFac example, the key is to register it as the default ICacheKeyGenerator)
builder.RegisterInstance(new CustomCacheKeyGenerator()).As<ICacheKeyGenerator>(); // this will be default
builder.RegisterType<SuperNiceCacheKeyGenerator>(); // this will be available, and constructed using dependency injection

You can set a specific cache key generator for an action, using the CacheKeyGenerator property:

[CacheOutput(CacheKeyGenerator=typeof(SuperNiceCacheKeyGenerator))]

PS! If you need dependency injection in your custom cache key generator, register it with your DI as itself.

This works for unregistered generators if they have a parameterless constructor, or with dependency injection if they are registered with your DI.

Finding a matching cache key generator is done in this order:

  1. Internal registration using RegisterCacheKeyGeneratorProvider or RegisterDefaultCacheKeyGeneratorProvider.
  2. Dependency injection.
  3. Parameterless constructor of unregistered classes.
  4. DefaultCacheKeyGenerator

JSONP

We automatically exclude callback parameter from cache key to allow for smooth JSONP support.

So:

/api/something?abc=1&callback=jQuery1213

is cached as:

/api/something?abc=1

Position of the callback parameter does not matter.

Etags

For client side caching, in addition to MaxAge, we will issue Etags. You can use the Etag value to make a request with If-None-Match header. If the resource is still valid, server will then response with a 304 status code.

For example:

GET /api/myresource
Accept: application/json

Status Code: 200
Cache-Control: max-age=100
Content-Length: 24
Content-Type: application/json; charset=utf-8
Date: Fri, 25 Jan 2013 03:37:11 GMT
ETag: "5c479911-97b9-4b78-ae3e-d09db420d5ba"
Server: Microsoft-HTTPAPI/2.0

On the next request:

GET /api/myresource
Accept: application/json
If-None-Match: "5c479911-97b9-4b78-ae3e-d09db420d5ba"

Status Code: 304
Cache-Control: max-age=100
Content-Length: 0
Date: Fri, 25 Jan 2013 03:37:13 GMT
Server: Microsoft-HTTPAPI/2.0

License

Licensed under Apache v2. License included.

More Repositories

1

apress-recipes-webapi

Samples from ASP.NET Web API 2: Recipes book.
C#
335
star
2

DynamicAndGenericControllersSample

C#
101
star
3

Strathweb.TypedRouting.AspNetCore

A library enabling strongly typed routing in ASP.NET Core MVC projects.
C#
75
star
4

aspnetcore-api-samples

C#
69
star
5

Strathweb.AspNetCore.AzureBlobFileProvider

C#
55
star
6

RealtimeCart

Shopping Cart sample with SignalR, Web API and Knockout.js
JavaScript
53
star
7

Strathweb.Samples.BlazorCSharpInteractive

HTML
47
star
8

Strathweb.Samples.CSharp.NoVisibilityChecks

Example of compiling and running C# code without visibility checks.
C#
46
star
9

csharp-string-to-lambda-example

C#
42
star
10

vscode-scriptcs-executor

VS Code extension for running C# snippets
TypeScript
42
star
11

net50-webapi-samples

C#
40
star
12

async-expiring-lazy

AsyncExpiringLazy
C#
37
star
13

WebApi.Html5.Upload

JavaScript
34
star
14

CollaboRoutePlanner

Demo of a collaborative route planner - SignalR, Knockout, Google Maps
JavaScript
31
star
15

html5-push-asp.net-web-api

28
star
16

Strathweb.Samples.DynamicControllerRouting

C#
25
star
17

Strathweb.Samples.AspNetCore.QueryStringBinding

C#
24
star
18

aspnetcore-parallel-pipelines

C#
20
star
19

WebApi.Service.Print

Web API self hosted inside Windows Service to provide remote access to prinitng services.
C#
19
star
20

Strathweb.Samples.Roslyn.Completion

C#
18
star
21

lightweight-aspnetcore-sample-site

C#
17
star
22

dotnet-script-aspnet

C#
17
star
23

webapi.inmemory

JavaScript
16
star
24

aspnetwebapi-wpf-p2p-chat

Build p2p chat application with WPF and ASP.NET Web API
C#
16
star
25

2018-ndcoslo-demos

C#
16
star
26

Strathweb.TypedRouting

C#
16
star
27

script-config

C#
15
star
28

Strathweb.Roslyn

Roslyn refactorings and code actions
C#
15
star
29

Strathweb.Samples.CollectibleAssemblies

C#
15
star
30

WebAPI.SignalR.Tracing

Realtime tracing for Web API
JavaScript
14
star
31

strathweb-webapi-ipfiltering

C#
13
star
32

aspnetcore-api-sample

Sample Web API built using ASP.NET Core
C#
12
star
33

wpf.dragdrop.to.webapi

C#
12
star
34

Mvc4.OpenId.Sample

A sample MVC4 project with OpenID authentication
JavaScript
11
star
35

HybridApiActionSelector

HTTP verb & action name hybrid dispatcher for Web API
C#
11
star
36

aspnetwebapi-api-usage

aspnetwebapi-api-usage
JavaScript
11
star
37

Metadata.WebApi

Adding useful metadata to your Web Api responses
JavaScript
10
star
38

Ninject-resolver-for-ASP.NET-Web-API

Ninject resolver for ASP.NET Web API
C#
9
star
39

Remote.Spotify

Remote.Spotify
JavaScript
8
star
40

Strathweb.Samples.RustFromSwift

Swift
8
star
41

Strathweb.Samples.DispatchProxy

C#
8
star
42

Glimpse.ScriptCs

Execute any code in your web app with ScriptCs and Glimpse
C#
8
star
43

strathweb-phi-engine

Rust
8
star
44

WebApi.ActionInjector

C#
7
star
45

roslyn-samples

C#
7
star
46

AspNetCore.ActionDependencyInjection.Sample

C#
6
star
47

FilterOrdering.WebAPi

JavaScript
5
star
48

Roslyn.Resolvers.Demo

C#
5
star
49

net60-webapi-samples

C#
5
star
50

aspnet5-reusable-components

C#
4
star
51

ndcoslo2015-demos

Demo of migrating Web API to MVC6 from NDC Oslo 2015
C#
4
star
52

Strathweb.Samples.ILproj

C
4
star
53

dotnet-wasi-demos

C#
4
star
54

strathweb-samples-azureopenai

C#
4
star
55

sample-native-cplus-http-service

Sample native C++ HTTP service with Casablanca
C++
4
star
56

Strathweb.RouteConventions.AspNetCore

C#
4
star
57

intro-to-qc-with-qsharp-book

Source code for the "Introduction to Quantum Computing with Q# and QDK" book
Q#
4
star
58

2024-zurich-azure-ug-demos

C#
4
star
59

Strathweb.Samples.RustFromCSharp

Rust
4
star
60

dnx-scriptcs-repl

C#
3
star
61

Strathweb.Dilithium

C#
3
star
62

Strathweb.Samples.DotnetWasi

C
3
star
63

2020-swetugg-demos

C#
3
star
64

AspNetCore.ControllersAsFilters.Sample

C#
3
star
65

csharp-scripting-demos

JavaScript
3
star
66

ScriptCs.WebConsole

JavaScript
3
star
67

update-conf-2018-demos

JavaScript
3
star
68

climax-web-http

A set of add-ons and extensions for ASP.NET Web API.
C#
2
star
69

scriptcs-python

C#
2
star
70

RemoveRegionAnalyzerAndCodeFix

C#
2
star
71

Strathweb.CacheOutput.Azure

Strathweb.CacheOutput.Azure
C#
2
star
72

2019-ndcporto-demos

C#
2
star
73

dotnetconf-2018-demos

C#
2
star
74

sample-scriptcs-webhost

C#
2
star
75

QSharp.Demos

Q#
2
star
76

filipw.github.io

JavaScript
2
star
77

Roslyn.WebApi.IssueExample

Example of implementing Roslyn code issue/action for Web API
C#
2
star
78

ScriptCs.AzureMobileServices

C#
2
star
79

2023-dotnetday-demos

Q#
1
star
80

scriptcs-dnx

C#
1
star
81

2023-ndcoslo-demos

Q#
1
star
82

ScriptCs.HttpLineProcessor

C#
1
star
83

ScriptCs.SsRedis

scriptcs script pack for ServiceStack.Redis
C#
1
star
84

Strathweb.Samples.ServiceBus.ReactiveMessageHandling

1
star
85

Strathweb.Samples.CSharp.Crystals

C#
1
star
86

Strathweb.ScriptConfiguration

C#
1
star
87

Strathweb.Samples.Duende.Dilithium

C#
1
star
88

ndc-sydney2016-demos

C#
1
star
89

2021-geneva-net-ug-demos

Q#
1
star
90

2021-zurich-net-ug-demos

Q#
1
star
91

Sharing

1
star
92

2019-switzerland-netday-demos

C#
1
star
93

2019-dotnetcologne-demos

JavaScript
1
star
94

ScriptCs.Engine.Brainfuck

C#
1
star
95

ndc-sydney-2017-demos

JavaScript
1
star
96

Strathweb.Samples.ServiceBus.TaskScheduling

1
star
97

Strathweb.Samples.NoConstructor

C#
1
star
98

Strathweb.Samples.Roslyn.Classification

C#
1
star
99

dotnext-spb2017-demos

Demos from my Roslyn talk at DotNext St. Petersburg 2017
C#
1
star
100

Strathweb.Samples.RuntimeHostConfigurationOptions

C#
1
star