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 OutputCacheProvider provider;
49 CacheItemRemovedCallback response_removed;
50 static object keysCacheLock = new object ();
51 Dictionary <string, string> keysCache;
52 Dictionary <string, string> entriesToInvalidate;
54 internal OutputCacheProvider InternalProvider {
55 get { return provider; }
58 public OutputCacheModule ()
62 OutputCacheProvider FindCacheProvider (HttpApplication app)
65 HttpContext ctx = HttpContext.Current;
67 app = ctx != null ? ctx.ApplicationInstance : null;
70 throw new InvalidOperationException ("Unable to find output cache provider.");
73 string providerName = app.GetOutputCacheProviderName (ctx);
74 if (String.IsNullOrEmpty (providerName))
75 throw new ProviderException ("Invalid OutputCacheProvider name. Name must not be null or an empty string.");
77 OutputCacheProvider ret = OutputCache.GetProvider (providerName);
79 throw new ProviderException (String.Format ("OutputCacheProvider named '{0}' cannot be found.", providerName));
84 provider = new InMemoryOutputCacheProvider ();
90 public void Dispose ()
94 public void Init (HttpApplication context)
96 context.ResolveRequestCache += new EventHandler(OnResolveRequestCache);
97 context.UpdateRequestCache += new EventHandler(OnUpdateRequestCache);
98 response_removed = new CacheItemRemovedCallback (OnRawResponseRemoved);
101 void OnBuildManagerRemoveEntry (BuildManagerRemoveEntryEventArgs args)
103 string entry = args.EntryName;
104 HttpContext context = args.Context;
107 lock (keysCacheLock) {
108 if (!keysCache.TryGetValue (entry, out cacheValue))
111 keysCache.Remove (entry);
112 if (context == null) {
113 if (entriesToInvalidate == null) {
114 entriesToInvalidate = new Dictionary <string, string> (StringComparer.Ordinal);
115 entriesToInvalidate.Add (entry, cacheValue);
117 } else if (!entriesToInvalidate.ContainsKey (entry)) {
118 entriesToInvalidate.Add (entry, cacheValue);
124 OutputCacheProvider provider = FindCacheProvider (context != null ? context.ApplicationInstance : null);
125 provider.Remove (entry);
126 if (!String.IsNullOrEmpty (cacheValue))
127 provider.Remove (cacheValue);
130 void OnResolveRequestCache (object o, EventArgs args)
132 HttpApplication app = o as HttpApplication;
133 HttpContext context = app != null ? app.Context : null;
138 OutputCacheProvider provider = FindCacheProvider (app);
139 string vary_key = context.Request.FilePath;
140 CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
147 key = varyby.CreateKey (vary_key, context);
148 c = provider.Get (key) as CachedRawResponse;
152 lock (keysCacheLock) {
154 if (entriesToInvalidate != null && entriesToInvalidate.TryGetValue (vary_key, out invValue) && String.Compare (invValue, key, StringComparison.Ordinal) == 0) {
155 provider.Remove (vary_key);
156 provider.Remove (key);
157 entriesToInvalidate.Remove (vary_key);
162 ArrayList callbacks = c.Policy.ValidationCallbacks;
163 if (callbacks != null && callbacks.Count > 0) {
165 bool isIgnored = false;
167 foreach (Pair p in callbacks) {
168 HttpCacheValidateHandler validate = (HttpCacheValidateHandler)p.First;
169 object data = p.Second;
170 HttpValidationStatus status = HttpValidationStatus.Valid;
173 validate (context, data, ref status);
175 // MS.NET hides the exception
180 if (status == HttpValidationStatus.Invalid) {
183 } else if (status == HttpValidationStatus.IgnoreThisRequest) {
189 OnRawResponseRemoved (key, c, CacheItemRemovedReason.Removed);
191 } else if (isIgnored)
195 HttpResponse response = context.Response;
196 response.ClearContent ();
197 IList cachedData = c.GetData ();
198 if (cachedData != null) {
199 Encoding outEnc = WebEncoding.ResponseEncoding;
201 foreach (CachedRawResponse.DataItem d in cachedData) {
203 response.BinaryWrite (d.Buffer, 0, (int)d.Length);
207 if (d.Callback == null)
210 string s = d.Callback (context);
211 if (s == null || s.Length == 0)
214 byte[] bytes = outEnc.GetBytes (s);
215 response.BinaryWrite (bytes, 0, bytes.Length);
219 response.ClearHeaders ();
220 response.SetCachedHeaders (c.Headers);
221 response.StatusCode = c.StatusCode;
222 response.StatusDescription = c.StatusDescription;
224 app.CompleteRequest ();
227 void OnUpdateRequestCache (object o, EventArgs args)
229 HttpApplication app = o as HttpApplication;
230 HttpContext context = app != null ? app.Context : null;
231 HttpResponse response = context != null ? context.Response : null;
233 if (response != null && response.IsCached && response.StatusCode == 200 && !context.Trace.IsEnabled)
234 DoCacheInsert (context, app, response);
237 void DoCacheInsert (HttpContext context, HttpApplication app, HttpResponse response)
239 string vary_key = context.Request.FilePath;
241 OutputCacheProvider provider = FindCacheProvider (app);
242 CachedVaryBy varyby = provider.Get (vary_key) as CachedVaryBy;
243 CachedRawResponse prev = null;
245 string cacheKey = null, cacheValue = null;
246 HttpCachePolicy cachePolicy = response.Cache;
248 if (varyby == null) {
249 varyby = new CachedVaryBy (cachePolicy, vary_key);
250 provider.Add (vary_key, varyby, Cache.NoAbsoluteExpiration);
255 key = varyby.CreateKey (vary_key, context);
258 prev = provider.Get (key) as CachedRawResponse;
261 CachedRawResponse c = response.GetCachedResponse ();
263 string [] keys = new string [] { vary_key };
264 DateTime utcExpiry, absoluteExpiration;
265 TimeSpan slidingExpiration;
268 varyby.ItemList.Add (key);
270 if (cachePolicy.Sliding) {
271 slidingExpiration = TimeSpan.FromSeconds (cachePolicy.Duration);
272 absoluteExpiration = Cache.NoAbsoluteExpiration;
273 utcExpiry = DateTime.UtcNow + slidingExpiration;
275 slidingExpiration = Cache.NoSlidingExpiration;
276 absoluteExpiration = cachePolicy.Expires;
277 utcExpiry = absoluteExpiration.ToUniversalTime ();
280 provider.Set (key, c, utcExpiry);
281 HttpRuntime.InternalCache.Insert (key, c, new CacheDependency (null, keys), absoluteExpiration, slidingExpiration,
282 CacheItemPriority.Normal, response_removed);
287 if (cacheKey != null) {
288 lock (keysCacheLock) {
289 if (keysCache == null) {
290 BuildManager.RemoveEntry += new BuildManagerRemoveEntryEventHandler (OnBuildManagerRemoveEntry);
291 keysCache = new Dictionary <string, string> (StringComparer.Ordinal);
292 keysCache.Add (cacheKey, cacheValue);
293 } else if (!keysCache.ContainsKey (cacheKey))
294 keysCache.Add (cacheKey, cacheValue);
299 void OnRawResponseRemoved (string key, object value, CacheItemRemovedReason reason)
301 CachedRawResponse c = value as CachedRawResponse;
302 CachedVaryBy varyby = c != null ? c.VaryBy : null;
306 List <string> itemList = varyby.ItemList;
307 OutputCacheProvider provider = FindCacheProvider (null);
309 itemList.Remove (key);
310 provider.Remove (key);
312 if (itemList.Count != 0)
315 provider.Remove (varyby.Key);