2 // System.Web.Caching.OutputCacheModule
5 // Jackson Harper (jackson@ximian.com)
6 // Marek Habersack <mhabersack@novell.com>
8 // (C) 2003-2009 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.Configuration.Provider;
39 using System.Web.Hosting;
41 using System.Web.Util;
42 using System.Web.Compilation;
44 namespace System.Web.Caching
46 sealed class OutputCacheModule : IHttpModule
48 CacheItemRemovedCallback response_removed;
49 static object keysCacheLock = new object ();
50 Dictionary <string, string> keysCache;
51 Dictionary <string, string> entriesToInvalidate;
53 public OutputCacheModule ()
57 OutputCacheProvider FindCacheProvider (HttpApplication app)
59 HttpContext ctx = HttpContext.Current;
61 app = ctx != null ? ctx.ApplicationInstance : null;
64 throw new InvalidOperationException ("Unable to find output cache provider.");
67 string providerName = app.GetOutputCacheProviderName (ctx);
68 if (String.IsNullOrEmpty (providerName))
69 throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string.");
71 OutputCacheProvider ret = OutputCache.GetProvider (providerName);
73 throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName));
78 public void Dispose ()
82 public void Init (HttpApplication context)
84 context.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
85 context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
86 response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
89 void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
91 string entry = args.EntryName;
92 HttpContext context = args.Context;
95 lock (keysCacheLock) {
96 if (!keysCache.TryGetValue (entry, out cacheValue))
99 keysCache.Remove (entry);
100 if (context == null) {
101 if (entriesToInvalidate == null) {
102 entriesToInvalidate = new Dictionary <string, string> (StringComparer.Ordinal);
103 entriesToInvalidate.Add (entry, cacheValue);
105 } else if (!entriesToInvalidate.ContainsKey (entry)) {
106 entriesToInvalidate.Add (entry, cacheValue);
112 OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null);
113 provider.Remove (entry);
114 if (!String.IsNullOrEmpty (cacheValue))
115 provider.Remove (cacheValue);
118 void OnResolveRequestCache (object o, EventArgs args)
120 HttpApplication app = o as HttpApplication;
121 HttpContext context = app != null ? app.Context : null;
126 OutputCacheProvider provider = FindCacheProvider (app);
127 string vary_key = context.Request.FilePath;
128 CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
135 key = varyby.CreateKey (vary_key, context);
136 c = provider.Get (key) as CachedRawResponse;
140 lock (keysCacheLock) {
142 if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
143 provider.Remove (vary_key);
144 provider.Remove (key);
145 entriesToInvalidate.Remove (vary_key);
150 ArrayList callbacks = c.Policy.ValidationCallbacks;
151 if (callbacks != null && callbacks.Count > 0) {
153 bool isIgnored = false;
155 foreach (Pair p in callbacks) {
156 HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First;
157 object data = p.Second;
158 HttpValidationStatus status = HttpValidationStatus.Valid;
161 validate (context, data, ref status);
163 // MS.NET hides the exception
168 if (status == HttpValidationStatus.Invalid) {
171 } else if (status == HttpValidationStatus.IgnoreThisRequest) {
177 OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
179 } else if (isIgnored)
183 HttpResponse response = context.Response;
184 response.ClearContent ();
185 IList cachedData = c.GetData ();
186 if (cachedData != null) {
187 Encoding outEnc = WebEncoding.ResponseEncoding;
189 foreach (CachedRawResponse.DataItem d in cachedData) {
191 response.BinaryWrite (d.Buffer, 0, (int)d.Length);
195 if (d.Callback == null)
198 string s = d.Callback (context);
199 if (s == null || s.Length == 0)
202 byte[] bytes = outEnc.GetBytes (s);
203 response.BinaryWrite (bytes, 0, bytes.Length);
207 response.ClearHeaders ();
208 response.SetCachedHeaders (c.Headers);
209 response.StatusCode = c.StatusCode;
210 response.StatusDescription = c.StatusDescription;
212 app.CompleteRequest ();
215 void OnUpdateRequestCache (object o, EventArgs args)
217 HttpApplication app = o as HttpApplication;
218 HttpContext context = app != null ? app.Context : null;
219 HttpResponse response = context != null ? context.Response : null;
221 if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled)
222 DoCacheInsert (context, app, response);
225 void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
227 string vary_key = context.Request.FilePath;
229 OutputCacheProvider provider = FindCacheProvider (app);
230 CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
231 CachedRawResponse prev = null;
233 string cacheKey = null, cacheValue = null;
234 HttpCachePolicy cachePolicy = response.Cache;
236 if (varyby == null) {
237 varyby = new CachedVaryBy (cachePolicy, vary_key);
238 provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration);
243 key = varyby.CreateKey (vary_key, context);
246 prev = provider.Get (key) as CachedRawResponse;
249 CachedRawResponse c = response.GetCachedResponse ();
251 string [] keys = new string [] { vary_key };
252 DateTime utcExpiry, absoluteExpiration;
253 TimeSpan slidingExpiration;
256 varyby.ItemList.Add (key);
258 if (cachePolicy.Sliding) {
259 slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration);
260 absoluteExpiration = Cache.NoAbsoluteExpiration;
261 utcExpiry = DateTime.UtcNow + slidingExpiration;
263 slidingExpiration = Cache.NoSlidingExpiration;
264 absoluteExpiration = cachePolicy.Expires;
265 utcExpiry = absoluteExpiration.ToUniversalTime ();
268 provider.Set (key, c, utcExpiry);
269 HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration,
270 CacheItemPriority.Normal, response_removed);
275 if (cacheKey != null) {
276 lock (keysCacheLock) {
277 if (keysCache == null) {
278 BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
279 keysCache = new Dictionary <string, string> (StringComparer.Ordinal);
280 keysCache.Add (cacheKey, cacheValue);
281 } else if (!keysCache.ContainsKey (cacheKey))
282 keysCache.Add (cacheKey, cacheValue);
287 void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
289 CachedRawResponse c = value as CachedRawResponse;
290 CachedVaryBy varyby = c != null ? c.VaryBy : null;
294 List <string> itemList = varyby.ItemList;
295 OutputCacheProvider provider = FindCacheProvider (null);
297 itemList.Remove (key);
298 provider.Remove (key);
300 if (itemList.Count != 0)
303 provider.Remove (varyby.Key);