1+ using System ;
2+ using System . Configuration ;
3+ using System . IO ;
4+ using System . Text . RegularExpressions ;
5+ using System . Web ;
6+ using System . Web . Caching ;
7+ using System . Web . Configuration ;
8+ using System . Web . Mvc ;
9+ using System . Web . Mvc . Html ;
10+ using System . Web . Script . Serialization ;
11+
12+ namespace DevTrends . MvcDonutCaching
13+ {
14+ [ AttributeUsage ( AttributeTargets . Class | AttributeTargets . Method , Inherited = true , AllowMultiple = false ) ]
15+ public class DonutOutputCacheAttribute : ActionFilterAttribute
16+ {
17+ private const string AspnetInternalProviderName = "AspNetInternalProvider" ;
18+ private const string CacheKeyPrefix = "_d0nutc@che." ;
19+
20+ private static readonly Regex DonutHoles = new Regex ( "<!--Donut#(.*)#-->" , RegexOptions . Compiled ) ;
21+ private static readonly bool IsCachingEnabled ;
22+ private static readonly OutputCacheProvider Cache ;
23+
24+ private string _key ;
25+
26+ public int Duration { get ; set ; }
27+ public string VaryByParam { get ; set ; }
28+ public string CacheProfile { get ; set ; }
29+
30+ static DonutOutputCacheAttribute ( )
31+ {
32+ var outputCacheConfigSection = ( OutputCacheSection ) ConfigurationManager . GetSection ( "system.web/caching/outputCache" ) ;
33+
34+ if ( outputCacheConfigSection . EnableOutputCache )
35+ {
36+ IsCachingEnabled = true ;
37+
38+ if ( outputCacheConfigSection . DefaultProviderName == AspnetInternalProviderName )
39+ {
40+ Cache = new MemoryCacheProvider ( ) ;
41+ }
42+ else
43+ {
44+ var providerType = Type . GetType ( outputCacheConfigSection . Providers [ outputCacheConfigSection . DefaultProviderName ] . Type ) ;
45+ Cache = ( OutputCacheProvider ) Activator . CreateInstance ( providerType ) ;
46+ }
47+ }
48+ }
49+
50+ public override void OnActionExecuting ( ActionExecutingContext filterContext )
51+ {
52+ ConfigureCacheProfile ( ) ;
53+
54+ if ( IsCachingEnabled && filterContext . HttpContext . Request . Url != null )
55+ {
56+ //todo only VaryByParam = * is supported right now
57+ _key = string . Concat ( CacheKeyPrefix , filterContext . HttpContext . Request . Url . PathAndQuery . ToLower ( ) ) ;
58+
59+ var content = Cache . Get ( _key ) as string ;
60+
61+ if ( content != null )
62+ {
63+ filterContext . Result = new ContentResult { Content = ReplaceDonutHoleContent ( content , filterContext ) } ;
64+ }
65+ }
66+ }
67+
68+ public override void OnActionExecuted ( ActionExecutedContext filterContext )
69+ {
70+ var viewResult = filterContext . Result as ViewResult ;
71+
72+ if ( viewResult != null )
73+ {
74+ using ( var stringWriter = new StringWriter ( ) )
75+ {
76+ var viewName = String . IsNullOrEmpty ( viewResult . ViewName )
77+ ? filterContext . RouteData . GetRequiredString ( "action" )
78+ : viewResult . ViewName ;
79+
80+ viewResult . View = ViewEngines . Engines . FindView ( filterContext , viewName , viewResult . MasterName ) . View ;
81+
82+ var viewContext = new ViewContext ( filterContext . Controller . ControllerContext , viewResult . View ,
83+ viewResult . ViewData , viewResult . TempData , stringWriter ) ;
84+
85+ viewResult . View . Render ( viewContext , stringWriter ) ;
86+
87+ var content = stringWriter . GetStringBuilder ( ) . ToString ( ) ;
88+
89+ if ( IsCachingEnabled )
90+ {
91+ Cache . Add ( _key , content , DateTime . Now . AddSeconds ( Duration ) ) ;
92+ }
93+
94+ filterContext . Result = new ContentResult { Content = ReplaceDonutHoleContent ( content , filterContext ) } ;
95+ }
96+ }
97+ }
98+
99+ private void ConfigureCacheProfile ( )
100+ {
101+ if ( IsCachingEnabled )
102+ {
103+ if ( ! string . IsNullOrEmpty ( CacheProfile ) )
104+ {
105+ var outputCacheSettingsConfigSection = ( OutputCacheSettingsSection ) ConfigurationManager . GetSection ( "system.web/caching/outputCacheSettings" ) ;
106+
107+ if ( outputCacheSettingsConfigSection != null && outputCacheSettingsConfigSection . OutputCacheProfiles . Count > 0 )
108+ {
109+ var cacheProfile = outputCacheSettingsConfigSection . OutputCacheProfiles [ CacheProfile ] ;
110+
111+ if ( cacheProfile != null )
112+ {
113+ Duration = cacheProfile . Duration ;
114+ VaryByParam = cacheProfile . VaryByParam ;
115+ return ;
116+ }
117+ }
118+
119+ throw new HttpException ( string . Format ( "The '{0}' cache profile is not defined. Please define it in the configuration file." , CacheProfile ) ) ;
120+ }
121+ }
122+ }
123+
124+ private static string ReplaceDonutHoleContent ( string content , ControllerContext filterContext )
125+ {
126+ return DonutHoles . Replace ( content , new MatchEvaluator ( match =>
127+ {
128+ var actionSettings = new JavaScriptSerializer ( ) . Deserialize < ActionSettings > ( match . Groups [ 1 ] . Value ) ;
129+
130+ return InvokeAction ( filterContext . Controller , actionSettings . ActionName , actionSettings . ControllerName , actionSettings . RouteValues ) ;
131+ } ) ) ;
132+ }
133+
134+ private static string InvokeAction ( ControllerBase controller , string actionName , string controllerName , object routeValues )
135+ {
136+ var viewContext = new ViewContext ( controller . ControllerContext , new WebFormView ( controller . ControllerContext , "tmp" ) ,
137+ controller . ViewData , controller . TempData , TextWriter . Null ) ;
138+
139+ var htmlHelper = new HtmlHelper ( viewContext , new ViewPage ( ) ) ;
140+
141+ return htmlHelper . Action ( actionName , controllerName , routeValues ) . ToString ( ) ;
142+ }
143+ }
144+ }
0 commit comments