11using System ;
2+ using System . Globalization ;
3+ using System . IO ;
24using System . Web . Mvc ;
5+ using System . Web . UI ;
6+ using System . Web ;
37
48namespace DevTrends . MvcDonutCaching
59{
610 [ AttributeUsage ( AttributeTargets . Class | AttributeTargets . Method , Inherited = true , AllowMultiple = false ) ]
7- public class DonutOutputCacheAttribute : ActionFilterAttribute
11+ public class DonutOutputCacheAttribute : ActionFilterAttribute , IExceptionFilter
812 {
13+ private const string CallbackKey = "d0nutCallback" ;
14+
915 private readonly IKeyGenerator _keyGenerator ;
1016 private readonly IDonutHoleFiller _donutHoleFiller ;
11- private readonly IActionOutputBuilder _actionOutputBuilder ;
1217 private readonly IExtendedOutputCacheManager _outputCacheManager ;
1318 private readonly ICacheSettingsManager _cacheSettingsManager ;
19+ private readonly ICacheHeadersHelper _cacheHeadersHelper ;
1420
1521 private CacheSettings _cacheSettings ;
1622 private string _cacheKey ;
@@ -19,83 +25,152 @@ public class DonutOutputCacheAttribute : ActionFilterAttribute
1925 public string VaryByParam { get ; set ; }
2026 public string VaryByCustom { get ; set ; }
2127 public string CacheProfile { get ; set ; }
28+ public OutputCacheLocation Location { get ; set ; }
2229
2330 public DonutOutputCacheAttribute ( )
2431 {
2532 var keyBuilder = new KeyBuilder ( ) ;
33+
2634 _keyGenerator = new KeyGenerator ( keyBuilder ) ;
27- _donutHoleFiller = new DonutHoleFiller ( new ActionSettingsSerialiser ( ) ) ;
28- _actionOutputBuilder = new ActionOutputBuilder ( ) ;
35+ _donutHoleFiller = new DonutHoleFiller ( new EncryptingActionSettingsSerialiser ( new ActionSettingsSerialiser ( ) , new Encryptor ( ) ) ) ;
2936 _outputCacheManager = new OutputCacheManager ( OutputCache . Instance , keyBuilder ) ;
3037 _cacheSettingsManager = new CacheSettingsManager ( ) ;
31- }
38+ _cacheHeadersHelper = new CacheHeadersHelper ( ) ;
3239
33- internal DonutOutputCacheAttribute ( IKeyGenerator keyGenerator , IDonutHoleFiller donutHoleFiller , IActionOutputBuilder actionOutputBuilder ,
34- IExtendedOutputCacheManager outputCacheManager , ICacheSettingsManager cacheSettingsManager )
35- {
36- _keyGenerator = keyGenerator ;
37- _donutHoleFiller = donutHoleFiller ;
38- _actionOutputBuilder = actionOutputBuilder ;
39- _outputCacheManager = outputCacheManager ;
40- _cacheSettingsManager = cacheSettingsManager ;
40+ Duration = - 1 ;
41+ Location = ( OutputCacheLocation ) ( - 1 ) ;
4142 }
4243
4344 public override void OnActionExecuting ( ActionExecutingContext filterContext )
4445 {
4546 _cacheSettings = BuildCacheSettings ( ) ;
4647
47- if ( _cacheSettings . IsCachingEnabled )
48+ if ( _cacheSettings . IsServerCachingEnabled )
4849 {
4950 _cacheKey = _keyGenerator . GenerateKey ( filterContext , _cacheSettings ) ;
5051
51- var content = _outputCacheManager . GetItem ( _cacheKey ) ;
52+ var cachedItem = _outputCacheManager . GetItem ( _cacheKey ) ;
5253
53- if ( content != null )
54+ if ( cachedItem != null )
5455 {
55- filterContext . Result = new ContentResult { Content = _donutHoleFiller . ReplaceDonutHoleContent ( content , filterContext ) } ;
56+ filterContext . Result = new ContentResult
57+ {
58+ Content = _donutHoleFiller . ReplaceDonutHoleContent ( cachedItem . Content , filterContext ) ,
59+ ContentType = cachedItem . ContentType
60+ } ;
5661 }
5762 }
58- }
5963
60- public override void OnActionExecuted ( ActionExecutedContext filterContext )
64+ if ( filterContext . Result == null )
65+ {
66+ var callbackKey = BuildCallbackKey ( filterContext ) ;
67+
68+ var cachingWriter = new StringWriter ( CultureInfo . InvariantCulture ) ;
69+
70+ var originalWriter = filterContext . HttpContext . Response . Output ;
71+
72+ filterContext . HttpContext . Response . Output = cachingWriter ;
73+
74+ filterContext . HttpContext . Items [ callbackKey ] = new Action < bool > ( hasErrors =>
75+ {
76+ filterContext . HttpContext . Items . Remove ( callbackKey ) ;
77+
78+ filterContext . HttpContext . Response . Output = originalWriter ;
79+
80+ if ( ! hasErrors )
81+ {
82+ var cacheItem = new CacheItem
83+ {
84+ Content = cachingWriter . ToString ( ) ,
85+ ContentType = filterContext . HttpContext . Response . ContentType
86+ } ;
87+
88+ filterContext . HttpContext . Response . Write ( _donutHoleFiller . RemoveDonutHoleWrappers ( cacheItem . Content , filterContext ) ) ;
89+
90+ if ( _cacheSettings . IsServerCachingEnabled && filterContext . HttpContext . Response . StatusCode == 200 )
91+ {
92+ _outputCacheManager . AddItem ( _cacheKey , cacheItem , DateTime . Now . AddSeconds ( _cacheSettings . Duration ) ) ;
93+ }
94+ }
95+ } ) ;
96+ }
97+ }
98+
99+ public override void OnResultExecuted ( ResultExecutedContext filterContext )
61100 {
62- var viewResult = filterContext . Result as ViewResultBase ; // only cache views and partials
101+ ExecuteCallback ( filterContext , false ) ;
63102
64- if ( viewResult != null )
103+ if ( ! filterContext . IsChildAction )
65104 {
66- var content = _actionOutputBuilder . GetActionOutput ( viewResult , filterContext ) ;
105+ _cacheHeadersHelper . SetCacheHeaders ( filterContext . HttpContext . Response , _cacheSettings ) ;
106+ }
107+ }
67108
68- if ( _cacheSettings . IsCachingEnabled )
69- {
70- _outputCacheManager . AddItem ( _cacheKey , content , DateTime . Now . AddSeconds ( _cacheSettings . Duration ) ) ;
71- }
109+ public void OnException ( ExceptionContext filterContext )
110+ {
111+ ExecuteCallback ( filterContext , true ) ;
112+ }
113+
114+ private void ExecuteCallback ( ControllerContext context , bool hasErrors )
115+ {
116+ var callbackKey = BuildCallbackKey ( context ) ;
117+
118+ var callback = context . HttpContext . Items [ callbackKey ] as Action < bool > ;
119+
120+ if ( callback != null )
121+ {
122+ callback . Invoke ( hasErrors ) ;
123+ }
124+ }
125+
126+ private string BuildCallbackKey ( ControllerContext context )
127+ {
128+ var actionName = context . RouteData . Values [ "action" ] . ToString ( ) ;
129+ var controllerName = context . RouteData . Values [ "controller" ] . ToString ( ) ;
72130
73- filterContext . Result = new ContentResult { Content = _donutHoleFiller . ReplaceDonutHoleContent ( content , filterContext ) } ;
74- }
131+ return string . Format ( "{0}.{1}.{2}" , CallbackKey , controllerName , actionName ) ;
75132 }
76133
77134 private CacheSettings BuildCacheSettings ( )
78135 {
136+ CacheSettings cacheSettings ;
137+
79138 if ( string . IsNullOrEmpty ( CacheProfile ) )
80139 {
81- return new CacheSettings
140+ cacheSettings = new CacheSettings
82141 {
83142 IsCachingEnabled = _cacheSettingsManager . IsCachingEnabledGlobally ,
84143 Duration = Duration ,
85144 VaryByCustom = VaryByCustom ,
86- VaryByParam = VaryByParam
87- } ;
145+ VaryByParam = VaryByParam ,
146+ Location = ( int ) Location == - 1 ? OutputCacheLocation . Server : Location
147+ } ;
148+ }
149+ else
150+ {
151+ var cacheProfile = _cacheSettingsManager . RetrieveOutputCacheProfile ( CacheProfile ) ;
152+
153+ cacheSettings = new CacheSettings
154+ {
155+ IsCachingEnabled = _cacheSettingsManager . IsCachingEnabledGlobally && cacheProfile . Enabled ,
156+ Duration = Duration == - 1 ? cacheProfile . Duration : Duration ,
157+ VaryByCustom = VaryByCustom ?? cacheProfile . VaryByCustom ,
158+ VaryByParam = VaryByParam ?? cacheProfile . VaryByParam ,
159+ Location = ( int ) Location == - 1 ? ( ( int ) cacheProfile . Location == - 1 ? OutputCacheLocation . Server : cacheProfile . Location ) : Location
160+ } ;
88161 }
89162
90- var cacheProfile = _cacheSettingsManager . RetrieveOutputCacheProfile ( CacheProfile ) ;
163+ if ( cacheSettings . Duration == - 1 )
164+ {
165+ throw new HttpException ( "The directive or the configuration settings profile must specify the 'duration' attribute." ) ;
166+ }
91167
92- return new CacheSettings
168+ if ( cacheSettings . Duration < 0 )
93169 {
94- IsCachingEnabled = _cacheSettingsManager . IsCachingEnabledGlobally && cacheProfile . Enabled ,
95- Duration = Duration == 0 ? cacheProfile . Duration : Duration ,
96- VaryByCustom = VaryByCustom ?? cacheProfile . VaryByCustom ,
97- VaryByParam = VaryByParam ?? cacheProfile . VaryByParam
98- } ;
99- }
170+ throw new HttpException ( "The 'duration' attribute must have a value that is greater than or equal to zero." ) ;
171+ }
172+
173+ return cacheSettings ;
174+ }
100175 }
101176}
0 commit comments