├── .gitmodules ├── nuget ├── lib │ └── empty ├── Build.bat ├── content │ ├── Views │ │ └── web.config.transform │ └── DevTrends.MvcDonutCaching.README.txt └── MvcDonutCaching.nuspec ├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── DevTrends.MvcDonutCaching.Demo ├── Views │ ├── Home │ │ ├── TestIssue23.cshtml │ │ ├── NestedDonutOne.cshtml │ │ ├── SimpleDonutTwo.cshtml │ │ ├── SimpleDonutOne.cshtml │ │ ├── WorksOnAsyncMethodsToo.cshtml │ │ └── Simple.cshtml │ ├── _ViewStart.cshtml │ ├── LoadTest │ │ ├── ApplyLoad.cshtml │ │ ├── SmallOutPutGrandChildAction.cshtml │ │ ├── MediumOutPutChildAction.cshtml │ │ └── LargeOutPutRootAction.cshtml │ ├── Account │ │ ├── ChangePasswordSuccess.cshtml │ │ ├── LogIn.cshtml │ │ ├── ChangePassword.cshtml │ │ └── Register.cshtml │ ├── Shared │ │ └── _Layout.cshtml │ └── Web.config ├── Areas │ └── SubArea │ │ ├── Views │ │ ├── _ViewStart.cshtml │ │ ├── SubHome │ │ │ ├── AreaDonutOne.cshtml │ │ │ └── Index.cshtml │ │ └── Web.config │ │ ├── SubAreaAreaRegistration.cs │ │ └── Controllers │ │ └── SubHomeController.cs ├── Global.asax ├── .gitignore ├── App_Start │ ├── FilterConfig.cs │ ├── RouteConfig.cs │ └── BundleConfig.cs ├── App_Data │ ├── Roles.xml │ └── Users.xml ├── MvcDonutCaching.Demo.csproj.DotSettings ├── Mvc │ └── ApplicationController.cs ├── .gitattributes ├── GlimpseSecurityPolicy.cs ├── packages.config ├── Web.Debug.config ├── Web.Release.config ├── Properties │ └── AssemblyInfo.cs ├── Models │ └── AccountModels.cs ├── Global.asax.cs ├── Controllers │ ├── HomeController.cs │ ├── LoadTestController.cs │ └── AccountController.cs ├── Content │ ├── main.css │ └── normalize.css ├── Web.config └── MvcDonutCaching.Demo.csproj ├── .editorconfig ├── DevTrends.MvcDonutCaching ├── Interfaces │ ├── IEncryptingActionSettingsSerialiser.cs │ ├── ICacheHeadersHelper.cs │ ├── IKeyGenerator.cs │ ├── IEncryptor.cs │ ├── IActionSettingsSerialiser.cs │ ├── IReadWriteOutputCacheManager.cs │ ├── IDonutHoleFiller.cs │ ├── ICacheSettingsManager.cs │ ├── IKeyBuilder.cs │ └── IOutputCacheManager.cs ├── packages.config ├── MvcDonutCaching.csproj.DotSettings ├── Encryptor.cs ├── CacheItem.cs ├── Properties │ └── AssemblyInfo.cs ├── OutputCacheOptions.cs ├── ActionSettings.cs ├── EncryptingActionSettingsSerialiser.cs ├── ActionSettingsSerialiser.cs ├── MemoryCacheProvider.cs ├── CacheHeadersHelper.cs ├── OutputCache.cs ├── KeyBuilder.cs ├── DonutHoleFiller.cs ├── CacheSettings.cs ├── CacheSettingsManager.cs ├── MvcDonutCaching.csproj ├── KeyGenerator.cs ├── OutputCacheManager.cs ├── DonutOutputCacheAttribute.cs └── HtmlHelperExtensions.cs ├── .gitignore ├── BRANCHES.txt ├── .gitattributes ├── MvcDonutCaching.sln.DotSettings ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── MvcDonutCaching.sln └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nuget/lib/empty: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /nuget/Build.bat: -------------------------------------------------------------------------------- 1 | nuget pack 2 | 3 | pause -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonpyk/mvcdonutcaching/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/TestIssue23.cshtml: -------------------------------------------------------------------------------- 1 | @Request.Url.Host - @DateTime.UtcNow -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/LoadTest/ApplyLoad.cshtml: -------------------------------------------------------------------------------- 1 | @model long 2 | 3 |

Load Test Results

4 |

Executed @Model requests

5 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="DevTrends.MvcDonutCaching.Demo.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/NestedDonutOne.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 | 3 |

4 | Donut nested within another donut. Rendered at : @Model (expires every 5 seconds) 5 |

6 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf8 6 | indent_style = space 7 | indent_size = 4 8 | 9 | [{*.cs,*.cshtml}] 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/SimpleDonutTwo.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 |

3 | This is another donut, not cached at all 4 |
5 | Generated at : @Model 6 |

7 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IEncryptingActionSettingsSerialiser.cs: -------------------------------------------------------------------------------- 1 | namespace DevTrends.MvcDonutCaching 2 | { 3 | public interface IEncryptingActionSettingsSerialiser : IActionSettingsSerialiser 4 | { } 5 | } 6 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Account/ChangePasswordSuccess.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Modifier le mot de passe"; 3 | } 4 | 5 |

Modifier le mot de passe

6 |

7 | Votre mot de passe a été changé. 8 |

9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS junk files 2 | [Tt]humbs.db 3 | *.ide 4 | 5 | # Visual Studio files 6 | bin/ 7 | obj/ 8 | packages/ 9 | *.user 10 | *.suo 11 | *.Publish.xml 12 | 13 | # RS 14 | _[Rr]esharper.* 15 | 16 | # Nuget 17 | nuget/lib/ 18 | 19 | # Doxygen 20 | doc/* -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/.gitignore: -------------------------------------------------------------------------------- 1 | # OS junk files 2 | [Tt]humbs.db 3 | *.ide 4 | 5 | # Visual Studio files 6 | bin/ 7 | obj/ 8 | packages/ 9 | *.user 10 | *.suo 11 | *.Publish.xml 12 | 13 | # RS 14 | _[Rr]esharper.* 15 | 16 | # Nuget 17 | nuget/lib/ 18 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /nuget/content/Views/web.config.transform: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/Views/SubHome/AreaDonutOne.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 |

3 | Area donut rendered at : @Model (expires every 30 seconds) 4 | 5 |

6 | 7 | -------------------------------------------------------------------------------- /nuget/content/DevTrends.MvcDonutCaching.README.txt: -------------------------------------------------------------------------------- 1 | Getting started with DevTrends.MvcDonutCaching 2 | ---------------------------------------------- 3 | 4 | Find out how to use DevTrends.MvcDonutCaching by visiting https://github.com/moonpyk/mvcdonutcaching 5 | 6 | Please report all bugs and feature requests on GitHub. 7 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/SimpleDonutOne.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 |

3 | Donut rendered at : @Model (expires every 60 seconds) 4 | 5 |

6 | 7 |
8 | @Html.Action("NestedDonutOne", true) 9 |
10 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace DevTrends.MvcDonutCaching.Demo 4 | { 5 | public class FilterConfig 6 | { 7 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 8 | { 9 | filters.Add(new HandleErrorAttribute()); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/WorksOnAsyncMethodsToo.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | @if (Model == null) 3 | { 4 |

Error while reading remote web service response

5 | } 6 | else 7 | { 8 |

Everybody loves bacon

9 | foreach (var ipsum in Model) 10 | { 11 |

@ipsum

12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BRANCHES.txt: -------------------------------------------------------------------------------- 1 | master : Contains stable and tested code, it's safe to checkout the code, and link your project directly against it, at any time (a nice alternative between official Nuget releases). Pull requests for bug fixes are welcome. 2 | --- 3 | dev : Work in progress code, contributors should make their pull requests for new features on that branch. Regular merges are done back to master once code quality and stability standards are reached. 4 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/App_Data/Roles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | clients 5 | 6 | client 7 | 8 | 9 | 10 | admin 11 | 12 | admin 13 | 14 | 15 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @Styles.Render("~/Content/style") 8 | 9 | 10 |
11 | @RenderBody() 12 |
13 | @Scripts.Render("~/Scripts/bundle") 14 | @RenderSection("scripts", false) 15 | 16 | 17 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/MvcDonutCaching.Demo.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/ICacheHeadersHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public interface ICacheHeadersHelper 6 | { 7 | /// 8 | /// Implementations should set the cache headers for the HTTP response given . 9 | /// 10 | /// The HTTP response. 11 | /// The cache settings. 12 | void SetCacheHeaders(HttpResponseBase response, CacheSettings settings); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Mvc/ApplicationController.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using Autofac; 3 | using DevTrends.MvcDonutCaching.Annotations; 4 | 5 | namespace DevTrends.MvcDonutCaching.Demo.Mvc 6 | { 7 | public abstract class ApplicationController : Controller 8 | { 9 | public ILifetimeScope LifetimeScope 10 | { 11 | get; 12 | set; 13 | } 14 | 15 | public OutputCacheManager OutputCacheManager 16 | { 17 | get; 18 | [UsedImplicitly] 19 | set; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/MvcDonutCaching.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Routing; 3 | 4 | namespace DevTrends.MvcDonutCaching.Demo 5 | { 6 | public class RouteConfig 7 | { 8 | public static void RegisterRoutes(RouteCollection routes) 9 | { 10 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 | 12 | routes.MapRoute( 13 | name: "Default", 14 | url: "{controller}/{action}/{id}", 15 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 16 | ); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /MvcDonutCaching.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | DO_NOT_SHOW -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/LoadTest/SmallOutPutGrandChildAction.cshtml: -------------------------------------------------------------------------------- 1 |
2 |

Grand Child Action rendered at: @Model

3 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

4 |
5 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IKeyGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public interface IKeyGenerator 6 | { 7 | /// 8 | /// Implementations should generate a key given the and . 9 | /// 10 | /// The controller context. 11 | /// The cache settings. 12 | /// A string that can be used as an output cache key 13 | string GenerateKey(ControllerContext context, CacheSettings cacheSettings); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Encryptor.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Web.Security; 3 | 4 | namespace DevTrends.MvcDonutCaching 5 | { 6 | public class Encryptor : IEncryptor 7 | { 8 | public string Encrypt(string plainText) 9 | { 10 | var plainTextBytes = Encoding.UTF8.GetBytes(plainText); 11 | return MachineKey.Encode(plainTextBytes, MachineKeyProtection.Encryption); 12 | } 13 | 14 | public string Decrypt(string encryptedText) 15 | { 16 | var decryptedBytes = MachineKey.Decode(encryptedText, MachineKeyProtection.Encryption); 17 | return Encoding.UTF8.GetString(decryptedBytes); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Optimization; 6 | 7 | namespace DevTrends.MvcDonutCaching.Demo 8 | { 9 | public class BundleConfig 10 | { 11 | public static void RegisterBundles(BundleCollection t) 12 | { 13 | t.Add(new StyleBundle("~/Content/style").Include( 14 | "~/Content/normalize.css", 15 | "~/Content/main.css" 16 | )); 17 | 18 | t.Add(new ScriptBundle("~/Scripts/bundle").Include( 19 | "~/Scripts/jquery-{version}.js" 20 | )); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/SubAreaAreaRegistration.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace DevTrends.MvcDonutCaching.Demo.Areas.SubArea 4 | { 5 | public class SubAreaAreaRegistration : AreaRegistration 6 | { 7 | public override string AreaName 8 | { 9 | get 10 | { 11 | return "SubArea"; 12 | } 13 | } 14 | 15 | public override void RegisterArea(AreaRegistrationContext context) 16 | { 17 | context.MapRoute( 18 | "SubArea_default", 19 | "SubArea/{controller}/{action}/{id}", 20 | new { action = "Index", id = UrlParameter.Optional } 21 | ); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/CacheItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace DevTrends.MvcDonutCaching 5 | { 6 | [Serializable, DataContract] 7 | public class CacheItem 8 | { 9 | /// 10 | /// Gets or sets content type. 11 | /// 12 | /// 13 | /// The content type. 14 | /// 15 | [DataMember(Order = 1)] 16 | public string ContentType { get; set; } 17 | 18 | /// 19 | /// Gets or sets the content to be cached. 20 | /// 21 | /// 22 | /// The content. 23 | /// 24 | [DataMember(Order = 2)] 25 | public string Content { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("DevTrends.MvcDonutCaching")] 5 | // [assembly: AssemblyDescription("")] 6 | // [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("DevTrends")] 8 | [assembly: AssemblyProduct("DevTrends.MvcDonutCaching")] 9 | [assembly: AssemblyCopyright("Copyright 2016 © MvcDonutCaching contributors")] 10 | // [assembly: AssemblyTrademark("")] 11 | // [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("ebcc3291-f04a-4511-b7eb-ddf57a74ada9")] 14 | [assembly: AssemblyVersion("1.3.1")] 15 | [assembly: AssemblyFileVersion("1.3.1")] 16 | 17 | #if RELEASE_PUBLIC 18 | [assembly: AssemblyKeyFile(@"C:\Users\moonpyk\Documents\Clés\Code\Code.snk")] 19 | #endif -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Original author : Paul Hiles (http://www.devtrends.co.uk/) 2 | Clément Bourgeois : Maintenance and evolutions starting 2013/09/11. 3 | ---- 4 | Magnus Lidbom : Various contributions, testing and quality feedback. 5 | Evgeniya Polyakova : Fixed codeplex issue #2584 6 | Gustavo Rubinsky : Similar fix for codeplex issue #2584 7 | Paweł Olesiejuk : Fixed codeplex issue #2533 8 | Ramgopal : Fixed codeplex issue issue #2578 9 | Gábor Plesz : Work on the demo project. 10 | Luc-Edmond Gaspard : Reported and fixed Github issue #5. 11 | anorborg : Fixed duplicate key exception PR #27 12 | pstarkov : Implemented VaryByHeader support #6 13 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IEncryptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace DevTrends.MvcDonutCaching 7 | { 8 | public interface IEncryptor 9 | { 10 | /// 11 | /// Implementations should encrypt the specified plain text. 12 | /// 13 | /// The plain text. 14 | /// An encrypted representation of 15 | string Encrypt(string plainText); 16 | 17 | /// 18 | /// Implementations should Decrypt the specified encrypted text. 19 | /// 20 | /// The encrypted text. 21 | /// The original text 22 | string Decrypt(string encryptedText); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nuget/MvcDonutCaching.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MvcDonutCaching 5 | 1.3.1-beta1 6 | MvcDonutCaching contributors 7 | Clément Bourgeois 8 | https://raw.github.com/moonpyk/mvcdonutcaching/master/LICENSE.txt 9 | https://github.com/moonpyk/mvcdonutcaching/ 10 | false 11 | MvcDonutCaching provides extensible donut output caching for ASP.NET MVC 3 and above. 12 | Added VaryByHeader support 13 | mvc3 mvc4 mvc5 mvc51 mvc52 donut caching mvc outputcaching outputcache substitution 14 | 15 | 16 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IActionSettingsSerialiser.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace DevTrends.MvcDonutCaching 3 | { 4 | public interface IActionSettingsSerialiser 5 | { 6 | /// 7 | /// Implementations should serialize as string the specified action settings. 8 | /// 9 | /// The action settings. 10 | /// A string representing the given 11 | string Serialise(ActionSettings actionSettings); 12 | 13 | /// 14 | /// Implementations should deserializes the specified serialized action settings. 15 | /// 16 | /// The serialized action settings. 17 | /// An object 18 | ActionSettings Deserialise(string serialisedActionSettings); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IReadWriteOutputCacheManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public interface IReadWriteOutputCacheManager : IOutputCacheManager 6 | { 7 | /// 8 | /// Implementations should add the given in the cache. 9 | /// 10 | /// The cache key to add. 11 | /// The cache item to add. 12 | /// The cache item UTC expiry date and time. 13 | void AddItem(string key, CacheItem cacheItem, DateTime utcExpiry); 14 | 15 | /// 16 | /// Implementations should retrieve a cache item the given the . 17 | /// 18 | /// The key. 19 | /// A instance on cache hit, null otherwise. 20 | CacheItem GetItem(string key); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Paul Hiles 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/OutputCacheOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | [Flags] 6 | public enum OutputCacheOptions 7 | { 8 | None = 0x0, 9 | /// 10 | /// No matter what, never use the query string parameters to generate the cache key name 11 | /// 12 | IgnoreQueryString = 0x1, 13 | /// 14 | /// No matter what, never use the POST data to generate the cache key name 15 | /// 16 | IgnoreFormData = 0x2, 17 | /// 18 | /// If the request is a POST, don't lookup for a cached result, execute the the result normally, 19 | /// caching it for subsequent GET (or any other non POST verb). 20 | /// 21 | NoCacheLookupForPosts = 0x4, 22 | /// 23 | /// Replace donuts in child actions, may affect performance but needed if you intent to have nested 24 | /// donut holes in child actions 25 | /// 26 | ReplaceDonutsInChildActions = 0x8, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/ActionSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | using System.Web.Routing; 4 | 5 | namespace DevTrends.MvcDonutCaching 6 | { 7 | [Serializable, DataContract] 8 | public class ActionSettings 9 | { 10 | /// 11 | /// Gets or sets the action name. 12 | /// 13 | /// 14 | /// The action's name. 15 | /// 16 | [DataMember(Order = 1)] 17 | public string ActionName { get; set; } 18 | 19 | /// 20 | /// Gets or sets the controller name. 21 | /// 22 | /// 23 | /// The the controller name. 24 | /// 25 | [DataMember(Order = 2)] 26 | public string ControllerName { get; set; } 27 | 28 | /// 29 | /// Gets or sets the route values. 30 | /// 31 | /// 32 | /// The route values. 33 | /// 34 | [DataMember(Order = 3)] 35 | public RouteValueDictionary RouteValues { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/EncryptingActionSettingsSerialiser.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace DevTrends.MvcDonutCaching 3 | { 4 | public class EncryptingActionSettingsSerialiser : IEncryptingActionSettingsSerialiser 5 | { 6 | private readonly IActionSettingsSerialiser _serialiser; 7 | private readonly IEncryptor _encryptor; 8 | 9 | public EncryptingActionSettingsSerialiser(IActionSettingsSerialiser serialiser, IEncryptor encryptor) 10 | { 11 | _serialiser = serialiser; 12 | _encryptor = encryptor; 13 | } 14 | 15 | public string Serialise(ActionSettings actionSettings) 16 | { 17 | var serialisedActionSettings = _serialiser.Serialise(actionSettings); 18 | 19 | return _encryptor.Encrypt(serialisedActionSettings); 20 | } 21 | 22 | public ActionSettings Deserialise(string serialisedActionSettings) 23 | { 24 | var decryptedSerialisedActionSettings = _encryptor.Decrypt(serialisedActionSettings); 25 | 26 | return _serialiser.Deserialise(decryptedSerialisedActionSettings); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IDonutHoleFiller.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public interface IDonutHoleFiller 6 | { 7 | /// 8 | /// Implentations should remove the donut hole wrappers. 9 | /// 10 | /// The content. 11 | /// The filter context. 12 | /// The output cache options. 13 | /// A donut hole wrapper free string 14 | string RemoveDonutHoleWrappers(string content, ControllerContext filterContext, OutputCacheOptions options); 15 | 16 | /// 17 | /// Replaces the donut holes content of with fresh content. 18 | /// 19 | /// The content. 20 | /// The filter context. 21 | /// The output cache options. 22 | /// A string containing the donut holes replaced by content. 23 | string ReplaceDonutHoleContent(string content, ControllerContext filterContext, OutputCacheOptions options); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/ActionSettingsSerialiser.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using System.Web.Routing; 5 | 6 | namespace DevTrends.MvcDonutCaching 7 | { 8 | public class ActionSettingsSerialiser : IActionSettingsSerialiser 9 | { 10 | private readonly DataContractSerializer _serialiser; 11 | 12 | public ActionSettingsSerialiser() 13 | { 14 | _serialiser = new DataContractSerializer(typeof(ActionSettings), new[] { typeof(RouteValueDictionary) }); 15 | } 16 | 17 | public string Serialise(ActionSettings actionSettings) 18 | { 19 | using (var memoryStream = new MemoryStream()) 20 | { 21 | _serialiser.WriteObject(memoryStream, actionSettings); 22 | return Encoding.UTF8.GetString(memoryStream.ToArray()); 23 | } 24 | } 25 | 26 | public ActionSettings Deserialise(string serialisedActionSettings) 27 | { 28 | using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(serialisedActionSettings))) 29 | { 30 | return (ActionSettings)_serialiser.ReadObject(memoryStream); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/ICacheSettingsManager.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.Web.Configuration; 3 | 4 | namespace DevTrends.MvcDonutCaching 5 | { 6 | public interface ICacheSettingsManager 7 | { 8 | /// 9 | /// Implementations should return the output cache provider settings. 10 | /// 11 | /// A instance. 12 | ProviderSettings RetrieveOutputCacheProviderSettings(); 13 | 14 | /// 15 | /// Implementation should return an output cache profile for the asked . 16 | /// 17 | /// Name of the cache profile. 18 | /// A instance. 19 | OutputCacheProfile RetrieveOutputCacheProfile(string cacheProfileName); 20 | 21 | /// 22 | /// Implementation should return a value indicating whether caching is globally enabled. 23 | /// 24 | /// 25 | /// true if caching is globally enabled; otherwise, false. 26 | /// 27 | bool IsCachingEnabledGlobally { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/LoadTest/MediumOutPutChildAction.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 |
3 |

Child Action rendered at: @Model

4 | @for(int i = 0; i < 2; i++) 5 | { 6 | 7 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

8 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

9 | @Html.Action("SmallOutPutGrandChildAction", true) 10 | } 11 |
12 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/GlimpseSecurityPolicy.cs: -------------------------------------------------------------------------------- 1 | /* 2 | // Uncomment this class to provide custom runtime policy for Glimpse 3 | 4 | using Glimpse.AspNet.Extensions; 5 | using Glimpse.Core.Extensibility; 6 | 7 | namespace DevTrends.MvcDonutCaching.Demo 8 | { 9 | public class GlimpseSecurityPolicy:IRuntimePolicy 10 | { 11 | public RuntimePolicy Execute(IRuntimePolicyContext policyContext) 12 | { 13 | // You can perform a check like the one below to control Glimpse's permissions within your application. 14 | // More information about RuntimePolicies can be found at http://getglimpse.com/Help/Custom-Runtime-Policy 15 | // var httpContext = policyContext.GetHttpContext(); 16 | // if (!httpContext.User.IsInRole("Administrator")) 17 | // { 18 | // return RuntimePolicy.Off; 19 | // } 20 | 21 | return RuntimePolicy.On; 22 | } 23 | 24 | public RuntimeEvent ExecuteOn 25 | { 26 | // The RuntimeEvent.ExecuteResource is only needed in case you create a security policy 27 | // Have a look at http://blog.getglimpse.com/2013/12/09/protect-glimpse-axd-with-your-custom-runtime-policy/ for more details 28 | get { return RuntimeEvent.EndRequest | RuntimeEvent.ExecuteResource; } 29 | } 30 | } 31 | } 32 | */ -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/Views/SubHome/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 | @{ 3 | ViewBag.Title = "Area donut holes demo"; 4 | } 5 | 6 |

@ViewBag.Title

7 | 8 |

9 | This page has been rendered at @Model (expires in 24h) 10 |

11 | 12 |
13 | @Html.Action("AreaDonutOne", true) 14 |
15 | 16 |

17 | This is again the main page 18 |

19 | 20 | 21 | 22 | @section scripts 23 | { 24 | 48 | } 49 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/MemoryCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.Caching; 5 | using System.Web.Caching; 6 | 7 | namespace DevTrends.MvcDonutCaching 8 | { 9 | public class MemoryCacheProvider : OutputCacheProvider, IEnumerable> 10 | { 11 | private static readonly ObjectCache Cache = MemoryCache.Default; 12 | 13 | public override object Add(string key, object entry, DateTime utcExpiry) 14 | { 15 | return Cache.AddOrGetExisting(key, entry, utcExpiry); 16 | } 17 | 18 | public override object Get(string key) 19 | { 20 | return Cache.Get(key); 21 | } 22 | 23 | public override void Remove(string key) 24 | { 25 | Cache.Remove(key); 26 | } 27 | 28 | public override void Set(string key, object entry, DateTime utcExpiry) 29 | { 30 | Cache.Set(key, entry, utcExpiry); 31 | } 32 | 33 | public IEnumerator> GetEnumerator() 34 | { 35 | return ((IEnumerable>) Cache).GetEnumerator(); 36 | } 37 | 38 | IEnumerator IEnumerable.GetEnumerator() 39 | { 40 | return GetEnumerator(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/Controllers/SubHomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using DevTrends.MvcDonutCaching.Demo.Controllers; 7 | using DevTrends.MvcDonutCaching.Demo.Mvc; 8 | 9 | namespace DevTrends.MvcDonutCaching.Demo.Areas.SubArea.Controllers 10 | { 11 | public class SubHomeController : ApplicationController 12 | { 13 | // 14 | // GET: /SubArea/Home/ 15 | [DonutOutputCache(Duration = 24 * 3600)] 16 | public ActionResult Index() 17 | { 18 | return View(DateTime.Now); 19 | } 20 | 21 | [ChildActionOnly, DonutOutputCache(Duration = 30, Options = OutputCacheOptions.ReplaceDonutsInChildActions)] 22 | public ActionResult AreaDonutOne() 23 | { 24 | return PartialView(DateTime.Now); 25 | } 26 | 27 | public ActionResult ExpireAreaDonutOne() 28 | { 29 | OutputCacheManager.RemoveItem("SubHome", "AreaDonutOne", new { area="SubArea" }); 30 | 31 | return Content("OK", "text/plain"); 32 | } 33 | 34 | public ActionResult ExpireAreaDonuts() 35 | { 36 | OutputCacheManager.RemoveItems(null, null, new { area="SubArea" }); 37 | 38 | return Content("OK", "text/plain"); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Home/Simple.cshtml: -------------------------------------------------------------------------------- 1 | @model DateTime 2 | @{ 3 | ViewBag.Title = "Donut holes demo"; 4 | } 5 | 6 |

@ViewBag.Title

7 | 8 |

9 | This page has been rendered at @Model (expires in 24h) 10 |

11 | 12 |
13 | @Html.Action("SimpleDonutOne", true) 14 |
15 | 16 |

17 | This is again the main page 18 |

19 | 20 |
21 | @Html.Action("SimpleDonutTwo", true) 22 |
23 | 24 |

25 | This is again again the main page 26 |

27 | 28 | @section scripts 29 | { 30 | 54 | } 55 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Account/LogIn.cshtml: -------------------------------------------------------------------------------- 1 | @model DevTrends.MvcDonutCaching.Demo.Models.LogOnModel 2 | 3 | @{ 4 | ViewBag.Title = "Se connecter"; 5 | } 6 | 7 |

Se connecter

8 |

9 | Entrez un nom d'utilisateur et un mot de passe. @Html.ActionLink("S'inscrire", "Register") si vous ne possédez pas de compte. 10 |

11 | 12 | @Html.ValidationSummary(true, "Échec de la connexion. Corrigez les erreurs et réessayez.") 13 | 14 | @using (Html.BeginForm()) 15 | { 16 |
17 |
18 | Informations de compte 19 | 20 |
21 | @Html.LabelFor(m => m.UserName) 22 |
23 |
24 | @Html.TextBoxFor(m => m.UserName) 25 | @Html.ValidationMessageFor(m => m.UserName) 26 |
27 | 28 |
29 | @Html.LabelFor(m => m.Password) 30 |
31 |
32 | @Html.PasswordFor(m => m.Password) 33 | @Html.ValidationMessageFor(m => m.Password) 34 |
35 | 36 |
37 | @Html.CheckBoxFor(m => m.RememberMe) 38 | @Html.LabelFor(m => m.RememberMe) 39 |
40 | 41 |

42 | 43 |

44 |
45 |
46 | } 47 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Les informations générales relatives à un assembly dépendent de 6 | // l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations 7 | // associées à un assembly. 8 | [assembly: AssemblyTitle("MvcDonutCaching.Demo")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MvcDonutCaching.Demo")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly 18 | // aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de 19 | // COM, affectez la valeur true à l'attribut ComVisible sur ce type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM 23 | [assembly: Guid("25ab36b3-64e5-4c8f-9216-dabdb48eac60")] 24 | 25 | // Les informations de version pour un assembly se composent des quatre valeurs suivantes : 26 | // 27 | // Version principale 28 | // Version secondaire 29 | // Numéro de build 30 | // Révision 31 | // 32 | // Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de révision et de build par défaut 33 | // en utilisant '*', comme indiqué ci-dessous : 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/App_Data/Users.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b96a6344-fd04-45eb-9f1e-240c9140c38b 5 | client 6 | client 7 | 8 | client@exemple.net 9 | 10 | 11 | 0001-01-01T00:00:00 12 | 0001-01-01T00:00:00 13 | 0001-01-01T00:00:00 14 | 0001-01-01T00:00:00 15 | 9999-12-31T23:59:59.9999999 16 | true 17 | false 18 | 0 19 | 20 | 21 | 26406e01-d5c6-467f-9b4d-180dc63b822c 22 | admin 23 | admin 24 | 25 | admin@exemple.net 26 | 0001-01-01T00:00:00 27 | 0001-01-01T00:00:00 28 | 0001-01-01T00:00:00 29 | 0001-01-01T00:00:00 30 | 9999-12-31T23:59:59.9999999 31 | true 32 | false 33 | 0 34 | 35 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/CacheHeadersHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web; 3 | using System.Web.UI; 4 | 5 | namespace DevTrends.MvcDonutCaching 6 | { 7 | public class CacheHeadersHelper : ICacheHeadersHelper 8 | { 9 | /// 10 | /// Sets the cache headers for the HTTP response given . 11 | /// 12 | /// The HTTP response. 13 | /// The cache settings. 14 | public void SetCacheHeaders(HttpResponseBase response, CacheSettings settings) 15 | { 16 | var cacheability = HttpCacheability.NoCache; 17 | 18 | switch (settings.Location) 19 | { 20 | case OutputCacheLocation.Any: 21 | case OutputCacheLocation.Downstream: 22 | cacheability = HttpCacheability.Public; 23 | break; 24 | case OutputCacheLocation.Client: 25 | case OutputCacheLocation.ServerAndClient: 26 | cacheability = HttpCacheability.Private; 27 | break; 28 | } 29 | 30 | response.Cache.SetCacheability(cacheability); 31 | 32 | if (cacheability != HttpCacheability.NoCache) 33 | { 34 | response.Cache.SetExpires(DateTime.Now.AddSeconds(settings.Duration)); 35 | response.Cache.SetMaxAge(new TimeSpan(0, 0, settings.Duration)); 36 | } 37 | 38 | if (settings.NoStore) 39 | { 40 | response.Cache.SetNoStore(); 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IKeyBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Web.Routing; 3 | 4 | namespace DevTrends.MvcDonutCaching 5 | { 6 | public interface IKeyBuilder 7 | { 8 | /// 9 | /// Implementations should build a cache key given . 10 | /// 11 | /// Name of the controller. 12 | /// 13 | string BuildKey(string controllerName); 14 | 15 | /// 16 | /// Implementations should build a cache key given the and . 17 | /// 18 | /// Name of the controller. 19 | /// Name of the action. 20 | string BuildKey(string controllerName, string actionName); 21 | 22 | /// 23 | /// Builds a cache key given the , and . 24 | /// 25 | /// Name of the controller. 26 | /// Name of the action. 27 | /// The route values. 28 | string BuildKey(string controllerName, string actionName, RouteValueDictionary routeValues); 29 | 30 | /// 31 | /// Implementations should build a cache key fragment for given . 32 | /// 33 | /// The route value to process. 34 | string BuildKeyFragment(KeyValuePair routeValue); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Account/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model DevTrends.MvcDonutCaching.Demo.Models.ChangePasswordModel 2 | 3 | @{ 4 | ViewBag.Title = "Modifier le mot de passe"; 5 | } 6 | 7 |

Modifier le mot de passe

8 |

9 | Utilisez le formulaire ci-dessous pour changer votre mot de passe. 10 |

11 |

12 | Les nouveaux mots de passe doivent comporter au minimum @Membership.MinRequiredPasswordLength caractères. 13 |

14 | 15 | @using (Html.BeginForm()) 16 | { 17 | @Html.ValidationSummary(true, "Échec de la modification du mot de passe. Corrigez les erreurs et réessayez.") 18 |
19 |
20 | Informations de compte 21 | 22 |
23 | @Html.LabelFor(m => m.OldPassword) 24 |
25 |
26 | @Html.PasswordFor(m => m.OldPassword) 27 | @Html.ValidationMessageFor(m => m.OldPassword) 28 |
29 | 30 |
31 | @Html.LabelFor(m => m.NewPassword) 32 |
33 |
34 | @Html.PasswordFor(m => m.NewPassword) 35 | @Html.ValidationMessageFor(m => m.NewPassword) 36 |
37 | 38 |
39 | @Html.LabelFor(m => m.ConfirmPassword) 40 |
41 |
42 | @Html.PasswordFor(m => m.ConfirmPassword) 43 | @Html.ValidationMessageFor(m => m.ConfirmPassword) 44 |
45 | 46 |

47 | 48 |

49 |
50 |
51 | } 52 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/OutputCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Web.Caching; 4 | 5 | namespace DevTrends.MvcDonutCaching 6 | { 7 | public sealed class OutputCache 8 | { 9 | static OutputCache() 10 | { 11 | DefaultOptions = OutputCacheOptions.None; 12 | 13 | var providerSettings = new CacheSettingsManager().RetrieveOutputCacheProviderSettings(); 14 | 15 | if (providerSettings == null || providerSettings.Type == null) 16 | { 17 | Instance = new MemoryCacheProvider(); 18 | } 19 | else 20 | { 21 | try 22 | { 23 | Instance = (OutputCacheProvider)Activator.CreateInstance(Type.GetType(providerSettings.Type)); 24 | Instance.Initialize(providerSettings.Name, providerSettings.Parameters); 25 | 26 | } 27 | catch (Exception ex) 28 | { 29 | throw new ConfigurationErrorsException( 30 | string.Format("Unable to instantiate and initialize OutputCacheProvider of type '{0}'. Make sure you are specifying the full type name.", providerSettings.Type), 31 | ex 32 | ); 33 | } 34 | } 35 | } 36 | 37 | private OutputCache() 38 | { 39 | } 40 | 41 | /// 42 | /// Gets the current instance. 43 | /// 44 | public static OutputCacheProvider Instance 45 | { 46 | get; 47 | private set; 48 | } 49 | 50 | /// 51 | /// Specifies the default value for the 52 | /// 53 | public static OutputCacheOptions DefaultOptions 54 | { 55 | get; 56 | set; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model DevTrends.MvcDonutCaching.Demo.Models.RegisterModel 2 | 3 | @{ 4 | ViewBag.Title = "S'inscrire"; 5 | } 6 | 7 |

Créer un nouveau compte

8 |

9 | Utilisez le formulaire ci-dessous pour créer un nouveau compte. 10 |

11 |

12 | Les mots de passe doivent comporter au minimum @Membership.MinRequiredPasswordLength caractères. 13 |

14 | 15 | @using (Html.BeginForm()) { 16 | @Html.ValidationSummary(true, "Échec de la création du compte. Corrigez les erreurs et réessayez.") 17 |
18 |
19 | Informations de compte 20 | 21 |
22 | @Html.LabelFor(m => m.UserName) 23 |
24 |
25 | @Html.TextBoxFor(m => m.UserName) 26 | @Html.ValidationMessageFor(m => m.UserName) 27 |
28 | 29 |
30 | @Html.LabelFor(m => m.Email) 31 |
32 |
33 | @Html.TextBoxFor(m => m.Email) 34 | @Html.ValidationMessageFor(m => m.Email) 35 |
36 | 37 |
38 | @Html.LabelFor(m => m.Password) 39 |
40 |
41 | @Html.PasswordFor(m => m.Password) 42 | @Html.ValidationMessageFor(m => m.Password) 43 |
44 | 45 |
46 | @Html.LabelFor(m => m.ConfirmPassword) 47 |
48 |
49 | @Html.PasswordFor(m => m.ConfirmPassword) 50 | @Html.ValidationMessageFor(m => m.ConfirmPassword) 51 |
52 | 53 |

54 | 55 |

56 |
57 |
58 | } 59 | 60 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/KeyBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Web.Routing; 4 | 5 | namespace DevTrends.MvcDonutCaching 6 | { 7 | public class KeyBuilder : IKeyBuilder 8 | { 9 | private string _cacheKeyPrefix = "_d0nutc@che."; 10 | 11 | public string CacheKeyPrefix 12 | { 13 | get 14 | { 15 | return _cacheKeyPrefix; 16 | } 17 | set 18 | { 19 | _cacheKeyPrefix = value; 20 | } 21 | } 22 | 23 | public string BuildKey(string controllerName) 24 | { 25 | return BuildKey(controllerName, null, null); 26 | } 27 | 28 | public string BuildKey(string controllerName, string actionName) 29 | { 30 | return BuildKey(controllerName, actionName, null); 31 | } 32 | 33 | public string BuildKey(string controllerName, string actionName, RouteValueDictionary routeValues) 34 | { 35 | var builder = new StringBuilder(CacheKeyPrefix); 36 | 37 | if (controllerName != null) 38 | { 39 | builder.AppendFormat("{0}.", controllerName.ToLowerInvariant()); 40 | } 41 | 42 | if (actionName != null) 43 | { 44 | builder.AppendFormat("{0}#", actionName.ToLowerInvariant()); 45 | } 46 | 47 | if (routeValues != null) 48 | { 49 | foreach (var routeValue in routeValues) 50 | { 51 | builder.Append(BuildKeyFragment(routeValue)); 52 | } 53 | } 54 | 55 | return builder.ToString(); 56 | } 57 | 58 | public string BuildKeyFragment(KeyValuePair routeValue) 59 | { 60 | var value = routeValue.Value == null ? "" : routeValue.Value.ToString().ToLowerInvariant(); 61 | 62 | return string.Format("{0}={1}#", routeValue.Key.ToLowerInvariant(), value); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MvcDonutCaching.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcDonutCaching", "DevTrends.MvcDonutCaching\MvcDonutCaching.csproj", "{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcDonutCaching.Demo", "DevTrends.MvcDonutCaching.Demo\MvcDonutCaching.Demo.csproj", "{2C31E962-9616-4292-9DB6-52E40CB07E19}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | CI|Any CPU = CI|Any CPU 13 | Debug|Any CPU = Debug|Any CPU 14 | Release.Public|Any CPU = Release.Public|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.CI|Any CPU.ActiveCfg = CI|Any CPU 19 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.CI|Any CPU.Build.0 = CI|Any CPU 20 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release.Public|Any CPU.ActiveCfg = Release.Public|Any CPU 23 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release.Public|Any CPU.Build.0 = Release.Public|Any CPU 24 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.CI|Any CPU.ActiveCfg = CI|Any CPU 27 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.Release.Public|Any CPU.ActiveCfg = Release|Any CPU 30 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2C31E962-9616-4292-9DB6-52E40CB07E19}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Models/AccountModels.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace DevTrends.MvcDonutCaching.Demo.Models 4 | { 5 | 6 | public class ChangePasswordModel 7 | { 8 | [Required] 9 | [DataType(DataType.Password)] 10 | [Display(Name = "Mot de passe actuel")] 11 | public string OldPassword { get; set; } 12 | 13 | [Required] 14 | [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] 15 | [DataType(DataType.Password)] 16 | [Display(Name = "Nouveau mot de passe")] 17 | public string NewPassword { get; set; } 18 | 19 | [DataType(DataType.Password)] 20 | [Display(Name = "Confirmer le nouveau mot de passe")] 21 | [Compare("NewPassword", ErrorMessage = "Le nouveau mot de passe et le mot de passe de confirmation ne correspondent pas.")] 22 | public string ConfirmPassword { get; set; } 23 | } 24 | 25 | public class LogOnModel 26 | { 27 | [Required] 28 | [Display(Name = "Nom d'utilisateur")] 29 | public string UserName { get; set; } 30 | 31 | [Required] 32 | [DataType(DataType.Password)] 33 | [Display(Name = "Mot de passe")] 34 | public string Password { get; set; } 35 | 36 | [Display(Name = "Mémoriser le mot de passe ?")] 37 | public bool RememberMe { get; set; } 38 | } 39 | 40 | public class RegisterModel 41 | { 42 | [Required] 43 | [Display(Name = "Nom d'utilisateur")] 44 | public string UserName { get; set; } 45 | 46 | [Required] 47 | [DataType(DataType.EmailAddress)] 48 | [Display(Name = "Adresse de messagerie")] 49 | public string Email { get; set; } 50 | 51 | [Required] 52 | [StringLength(100, ErrorMessage = "La chaîne {0} doit comporter au moins {2} caractères.", MinimumLength = 6)] 53 | [DataType(DataType.Password)] 54 | [Display(Name = "Mot de passe")] 55 | public string Password { get; set; } 56 | 57 | [DataType(DataType.Password)] 58 | [Display(Name = "Confirmer le mot de passe")] 59 | [Compare("Password", ErrorMessage = "Le mot de passe et le mot de passe de confirmation ne correspondent pas.")] 60 | public string ConfirmPassword { get; set; } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | using System.Web.Optimization; 4 | using System.Web.Routing; 5 | using Autofac; 6 | using Autofac.Integration.Mvc; 7 | 8 | namespace DevTrends.MvcDonutCaching.Demo 9 | { 10 | public class MvcApplication : HttpApplication 11 | { 12 | public IContainer Container 13 | { 14 | get; 15 | set; 16 | } 17 | 18 | protected void Application_Start() 19 | { 20 | AreaRegistration.RegisterAllAreas(); 21 | 22 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 23 | RouteConfig.RegisterRoutes(RouteTable.Routes); 24 | BundleConfig.RegisterBundles(BundleTable.Bundles); 25 | 26 | Container = RegisterAutofac(); 27 | } 28 | 29 | private static IContainer RegisterAutofac() 30 | { 31 | var builder = new ContainerBuilder(); 32 | 33 | builder.RegisterType() 34 | .AsImplementedInterfaces() 35 | .AsSelf() 36 | .SingleInstance(); 37 | 38 | builder.RegisterControllers(typeof(MvcApplication).Assembly).PropertiesAutowired(); 39 | builder.RegisterFilterProvider(); 40 | builder.RegisterModelBinderProvider(); 41 | 42 | var container = builder.Build(); 43 | container.ActivateGlimpse(); 44 | DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 45 | 46 | return container; 47 | } 48 | 49 | public override string GetVaryByCustomString(HttpContext context, string custom) 50 | { 51 | if (string.IsNullOrWhiteSpace(custom)) 52 | { 53 | return base.GetVaryByCustomString(context, custom); 54 | } 55 | 56 | switch (custom.ToLowerInvariant()) 57 | { 58 | case "subdomain": 59 | return context.Request.Url.Host == "sub.localtest.me" 60 | ? "sub" 61 | : "main"; 62 | 63 | 64 | case "user": 65 | var principal = context.User; 66 | if (principal != null) 67 | { 68 | return string.Format("{0}@{1}", principal.Identity.Name, principal.Identity.AuthenticationType); 69 | } 70 | break; 71 | } 72 | 73 | return base.GetVaryByCustomString(context, custom); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/LoadTest/LargeOutPutRootAction.cshtml: -------------------------------------------------------------------------------- 1 |

Lots of text and a bunch of child actions rendered at: @Model

2 | 3 | 4 | @for(int i = 0; i < 10; i++) 5 | { 6 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

7 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

8 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

9 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

10 |

Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

11 | 12 | @Html.Action("MediumOutPutChildAction", true) 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using System.Web.Mvc; 6 | using DevTrends.MvcDonutCaching.Demo.Mvc; 7 | using Newtonsoft.Json; 8 | 9 | namespace DevTrends.MvcDonutCaching.Demo.Controllers 10 | { 11 | public class HomeController : ApplicationController 12 | { 13 | public ActionResult Index() 14 | { 15 | return RedirectToAction("Simple"); 16 | } 17 | 18 | // 19 | // GET: /Home/ 20 | [DonutOutputCache(Duration = 24 * 3600)] 21 | public ActionResult Simple() 22 | { 23 | return View(DateTime.Now); 24 | } 25 | 26 | [ChildActionOnly, DonutOutputCache(Duration = 60, Options = OutputCacheOptions.ReplaceDonutsInChildActions)] 27 | public ActionResult SimpleDonutOne() 28 | { 29 | return PartialView(DateTime.Now); 30 | } 31 | 32 | [ChildActionOnly, DonutOutputCache(Duration = 5)] 33 | public ActionResult NestedDonutOne() 34 | { 35 | return PartialView(DateTime.Now); 36 | } 37 | 38 | [ChildActionOnly] 39 | public ActionResult SimpleDonutTwo() 40 | { 41 | return PartialView(DateTime.Now); 42 | } 43 | 44 | public ActionResult ExpireSimpleDonutCache() 45 | { 46 | OutputCacheManager.RemoveItem("Home", "Simple"); 47 | 48 | return Content("OK", "text/plain"); 49 | } 50 | 51 | public ActionResult ExpireSimpleDonutOneCache() 52 | { 53 | OutputCacheManager.RemoveItem("Home", "SimpleDonutOne"); 54 | 55 | return Content("OK", "text/plain"); 56 | } 57 | 58 | [DonutOutputCache(CacheProfile = "medium", VaryByParam = "*", VaryByCustom = "subdomain")] 59 | public ActionResult TestIssue23() 60 | { 61 | return View(); 62 | } 63 | 64 | [DonutOutputCache(Duration = 3600 /* Bacon is still good one hour later */)] 65 | public async Task WorksOnAsyncMethodsToo() 66 | { 67 | var req = WebRequest.Create("http://baconipsum.com/api/?type=meat-and-filler"); 68 | 69 | string[] final = null; 70 | 71 | using (var resp = await req.GetResponseAsync()) 72 | { 73 | var rStream = resp.GetResponseStream(); 74 | if (rStream != null) 75 | { 76 | using (var r = new StreamReader(rStream)) 77 | { 78 | final = JsonConvert.DeserializeObject(r.ReadToEnd()); 79 | } 80 | } 81 | } 82 | 83 | return View(final); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASP.NET MVC Extensible Donut Caching # 2 | 3 | ASP.NET MVC Extensible Donut Caching brings donut caching to ASP.NET MVC 3 and later. The code allows you to cache all of your page apart from one or more Html.Actions which can be executed every request. Perfect for user specific content. 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/snh8n1jjhea9fdot)](https://ci.appveyor.com/project/moonpyk/mvcdonutcaching) 6 | 7 | ## Download ## 8 | 9 | The best way to add donut caching to your MVC project is to use the NuGet package. From within Visual Studio, select *Tools | Library Package Manager* and then choose either Package Manager Console or Manage NuGet Packages. Via the console, just type **install-package MvcDonutCaching** and hit return. From the GUI, just search for **MvcDonutCaching** and click the install button. 10 | 11 | ## Usage ## 12 | 13 | The package adds several overloads to the built-in Html.Action HTML helper. The extra parameter in each overload is named *excludeFromParentCache*. Set this to true for any action that should not be cached, or should have a different cache duration from the rest of the page. 14 | 15 | ```csharp 16 | @Html.Action("Login", "Account", true) 17 | ``` 18 | 19 | The package also include a DonutOutputCacheAttribute to be used in place of the built-in OutputCacheAttribute. This attribute is typically placed on every controller action that needs be be cached. 20 | 21 | You can either specify a fixed duration: 22 | 23 | ```csharp 24 | [DonutOutputCache(Duration = "300")] 25 | public ActionResult Index() 26 | { 27 | return View(); 28 | } 29 | ``` 30 | 31 | Or, use a cache profile: 32 | 33 | ```csharp 34 | [DonutOutputCache(CacheProfile = "FiveMins")] 35 | public ActionResult Index() 36 | { 37 | return View(); 38 | } 39 | ``` 40 | 41 | If you are using cache profiles, be sure to configure the profiles in the web.config. Add the following within the system.web element: 42 | 43 | ```xml 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ``` 52 | 53 | You can also configure the output cache to use a custom provider: 54 | 55 | ```xml 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | Note, that a custom provider is not included with this project but you can write one fairly easily by subclassing *System.Web.Caching.OutputCacheProvider*. A number of implementations are also available on the web. 66 | 67 | ## More Information ## 68 | 69 | A comprehensive guide to MVC Extensible Donut Caching is now available on the [DevTrends Blog](http://www.devtrends.co.uk/blog/donut-output-caching-in-asp.net-mvc-3). 70 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/DonutHoleFiller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text.RegularExpressions; 4 | using System.Web.Mvc; 5 | using System.Web.Mvc.Html; 6 | using System.Web.Routing; 7 | 8 | namespace DevTrends.MvcDonutCaching 9 | { 10 | public class DonutHoleFiller : IDonutHoleFiller 11 | { 12 | private static readonly Regex DonutHoles = new Regex("(.*?)", RegexOptions.Compiled | RegexOptions.Singleline); 13 | 14 | private readonly IActionSettingsSerialiser _actionSettingsSerialiser; 15 | 16 | public DonutHoleFiller(IActionSettingsSerialiser actionSettingsSerialiser) 17 | { 18 | if (actionSettingsSerialiser == null) 19 | { 20 | throw new ArgumentNullException("actionSettingsSerialiser"); 21 | } 22 | 23 | _actionSettingsSerialiser = actionSettingsSerialiser; 24 | } 25 | 26 | public string RemoveDonutHoleWrappers(string content, ControllerContext filterContext, OutputCacheOptions options) 27 | { 28 | if ( 29 | filterContext.IsChildAction && 30 | (options & OutputCacheOptions.ReplaceDonutsInChildActions) != OutputCacheOptions.ReplaceDonutsInChildActions) 31 | { 32 | return content; 33 | } 34 | 35 | return DonutHoles.Replace(content, match => match.Groups[2].Value); 36 | } 37 | 38 | public string ReplaceDonutHoleContent(string content, ControllerContext filterContext, OutputCacheOptions options) 39 | { 40 | if ( 41 | filterContext.IsChildAction && 42 | (options & OutputCacheOptions.ReplaceDonutsInChildActions) != OutputCacheOptions.ReplaceDonutsInChildActions) 43 | { 44 | return content; 45 | } 46 | 47 | return DonutHoles.Replace(content, match => 48 | { 49 | var actionSettings = _actionSettingsSerialiser.Deserialise(match.Groups[1].Value); 50 | 51 | return InvokeAction( 52 | filterContext.Controller, 53 | actionSettings.ActionName, 54 | actionSettings.ControllerName, 55 | actionSettings.RouteValues 56 | ); 57 | }); 58 | } 59 | 60 | private static string InvokeAction(ControllerBase controller, string actionName, string controllerName, RouteValueDictionary routeValues) 61 | { 62 | var viewContext = new ViewContext( 63 | controller.ControllerContext, 64 | new WebFormView(controller.ControllerContext, "tmp"), 65 | controller.ViewData, 66 | controller.TempData, 67 | TextWriter.Null 68 | ); 69 | 70 | var htmlHelper = new HtmlHelper(viewContext, new ViewPage()); 71 | 72 | return htmlHelper.Action(actionName, controllerName, routeValues).ToString(); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/CacheSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Web.UI; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public class CacheSettings 6 | { 7 | /// 8 | /// Gets or sets a value indicating whether the cache is enabled. 9 | /// 10 | /// 11 | /// true if cache is enabled otherwise, false. 12 | /// 13 | public bool IsCachingEnabled { get; set; } 14 | 15 | /// 16 | /// Gets or sets the cache duration. 17 | /// 18 | /// 19 | /// The cache duration. 20 | /// 21 | public int Duration { get; set; } 22 | 23 | /// 24 | /// Gets or sets the VaryByParam cache parameter. 25 | /// 26 | /// 27 | /// The VaryByParam cache parameter. 28 | /// 29 | public string VaryByParam { get; set; } 30 | 31 | /// 32 | /// Gets or sets the VaryByHeader cache parameter. 33 | /// 34 | /// 35 | /// The VaryByHeader cache parameter. 36 | /// 37 | public string VaryByHeader { get; set; } 38 | 39 | /// 40 | /// Gets or sets the VaryByCustom cache parameter. 41 | /// 42 | /// 43 | /// The VaryByCustom cache parameter. 44 | /// 45 | public string VaryByCustom { get; set; } 46 | 47 | /// 48 | /// Gets or sets the output cache location. 49 | /// 50 | /// 51 | /// The output cache location. 52 | /// 53 | public OutputCacheLocation Location { get; set; } 54 | 55 | /// 56 | /// Gets or sets a value indicating whether store or not the result. 57 | /// 58 | /// 59 | /// true if no store; otherwise, false. 60 | /// 61 | public bool NoStore { get; set; } 62 | 63 | /// 64 | /// Gets or sets the output cache options. 65 | /// 66 | /// 67 | /// The output cache options. 68 | /// 69 | public OutputCacheOptions Options { get; set; } 70 | 71 | /// 72 | /// Gets a value indicating whether the server caching is enabled. 73 | /// 74 | /// 75 | /// true if the server caching enabled; otherwise, false. 76 | /// 77 | public bool IsServerCachingEnabled 78 | { 79 | get 80 | { 81 | return IsCachingEnabled && Duration > 0 && (Location == OutputCacheLocation.Any || 82 | Location == OutputCacheLocation.Server || 83 | Location == OutputCacheLocation.ServerAndClient); 84 | } 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Views/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Areas/SubArea/Views/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/Interfaces/IOutputCacheManager.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Routing; 2 | 3 | namespace DevTrends.MvcDonutCaching 4 | { 5 | public interface IOutputCacheManager 6 | { 7 | /// 8 | /// Implementations should remove a single output cache entry for the specified controller and action. 9 | /// 10 | /// The name of the controller that contains the action method. 11 | /// The name of the controller action method. 12 | void RemoveItem(string controllerName, string actionName); 13 | 14 | /// 15 | /// Implementations should remove a single output cache entry for the specified controller, action and parameters. 16 | /// 17 | /// The name of the controller that contains the action method. 18 | /// The name of the controller action method. 19 | /// An object that contains the parameters for a route. 20 | void RemoveItem(string controllerName, string actionName, object routeValues); 21 | 22 | /// 23 | /// Implementations should remove a single output cache entry for the specified controller, action and parameters. 24 | /// 25 | /// The name of the controller that contains the action method. 26 | /// The name of the controller action method. 27 | /// A dictionary that contains the parameters for a route. 28 | void RemoveItem(string controllerName, string actionName, RouteValueDictionary routeValues); 29 | 30 | /// 31 | /// Implementations should remove all output cache entries. 32 | /// 33 | void RemoveItems(); 34 | 35 | /// 36 | /// Implementations should remove all output cache entries for the specified controller. 37 | /// 38 | /// The name of the controller. 39 | void RemoveItems(string controllerName); 40 | 41 | /// 42 | /// Implementations should remove all output cache entries for the specified controller and action. 43 | /// 44 | /// The name of the controller that contains the action method. 45 | /// The name of the controller action method. 46 | void RemoveItems(string controllerName, string actionName); 47 | 48 | /// 49 | /// Implementations should remove all output cache entries for the specified controller, action and parameters. 50 | /// 51 | /// The name of the controller that contains the action method. 52 | /// The name of the controller action method. 53 | /// A dictionary that contains the parameters for a route. 54 | void RemoveItems(string controllerName, string actionName, RouteValueDictionary routeValues); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Controllers/LoadTestController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Net; 5 | using System.Net.Cache; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Web.Mvc; 9 | 10 | namespace DevTrends.MvcDonutCaching.Demo.Controllers 11 | { 12 | public class LoadTestController : Controller 13 | { 14 | public ActionResult ApplyLoad() 15 | { 16 | Console.WriteLine("Starting"); 17 | 18 | long requestsMade = 0; 19 | 20 | var relativeUrl = Url.Action("LargeOutPutRootAction"); 21 | 22 | Debug.Assert(Request.Url != null, "Request.Url != null"); 23 | 24 | var uri = string.Format( 25 | "{0}://{1}{2}", 26 | Request.Url.Scheme, 27 | Request.Url.Authority, 28 | relativeUrl 29 | ); 30 | 31 | var cancellationTokenSource = new CancellationTokenSource(); 32 | var parallelOptions = new ParallelOptions 33 | { 34 | CancellationToken = cancellationTokenSource.Token, 35 | MaxDegreeOfParallelism = 20 36 | }; 37 | 38 | var runUntil = DateTime.Now.AddSeconds(10); 39 | 40 | try 41 | { 42 | Parallel.For(1, 43 | int.MaxValue, 44 | parallelOptions, 45 | _ => 46 | { 47 | if (runUntil < DateTime.Now) 48 | { 49 | cancellationTokenSource.Cancel(); 50 | } 51 | parallelOptions.CancellationToken.ThrowIfCancellationRequested(); 52 | 53 | var webRequest = WebRequest.Create(uri); 54 | webRequest.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.NoCacheNoStore); 55 | 56 | using (var response = webRequest.GetResponse()) 57 | using (var stream = response.GetResponseStream()) 58 | { 59 | Debug.Assert(stream != null, "stream != null"); 60 | 61 | using (var reader = new StreamReader(stream)) 62 | { 63 | reader.ReadToEnd(); 64 | Interlocked.Increment(ref requestsMade); 65 | } 66 | } 67 | }); 68 | } 69 | catch (OperationCanceledException) 70 | { 71 | } 72 | 73 | return View(requestsMade); 74 | } 75 | 76 | #if PROFILE_DONUTS_CHILDACTION 77 | [DonutOutputCache(Duration = 3600, Options = OutputCacheOptions.ReplaceDonutsInChildActions)] 78 | #else 79 | [DonutOutputCache(Duration = 3600)] 80 | #endif 81 | public ActionResult LargeOutPutRootAction() 82 | { 83 | return View(DateTime.Now); 84 | } 85 | 86 | #if PROFILE_DONUTS_CHILDACTION 87 | [DonutOutputCache(Duration = 3600, Options = OutputCacheOptions.ReplaceDonutsInChildActions)] 88 | #else 89 | [DonutOutputCache(Duration = 3600)] 90 | #endif 91 | public ActionResult MediumOutPutChildAction() 92 | { 93 | return PartialView(DateTime.Now); 94 | } 95 | 96 | #if PROFILE_DONUTS_CHILDACTION 97 | [DonutOutputCache(Duration = 3600, Options = OutputCacheOptions.ReplaceDonutsInChildActions)] 98 | #else 99 | [DonutOutputCache(Duration = 3600)] 100 | #endif 101 | public ActionResult SmallOutPutGrandChildAction() 102 | { 103 | return PartialView(DateTime.Now); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/CacheSettingsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Diagnostics; 4 | using System.Security; 5 | using System.Web; 6 | using System.Web.Configuration; 7 | 8 | namespace DevTrends.MvcDonutCaching 9 | { 10 | public class CacheSettingsManager : ICacheSettingsManager 11 | { 12 | private const string AspnetInternalProviderName = "AspNetInternalProvider"; 13 | private readonly OutputCacheSection _outputCacheSection; 14 | 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public CacheSettingsManager() 19 | { 20 | try 21 | { 22 | _outputCacheSection = (OutputCacheSection)ConfigurationManager.GetSection("system.web/caching/outputCache"); 23 | } 24 | catch (SecurityException) 25 | { 26 | Trace.WriteLine("MvcDonutCaching does not have permission to read web.config section 'OutputCacheSection'. Using default provider."); 27 | _outputCacheSection = new OutputCacheSection 28 | { 29 | DefaultProviderName = AspnetInternalProviderName, 30 | EnableOutputCache = true 31 | }; 32 | } 33 | } 34 | 35 | /// 36 | /// Returns the output cache provider settings. 37 | /// 38 | /// 39 | /// A instance. 40 | /// 41 | public ProviderSettings RetrieveOutputCacheProviderSettings() 42 | { 43 | return _outputCacheSection.DefaultProviderName == AspnetInternalProviderName 44 | ? null 45 | : _outputCacheSection.Providers[_outputCacheSection.DefaultProviderName]; 46 | } 47 | 48 | /// 49 | /// Returns an output cache profile for the asked . 50 | /// 51 | /// Name of the cache profile. 52 | /// 53 | /// A instance. 54 | /// 55 | /// MvcDonutCaching does not have permission to read web.config section 'OutputCacheSettingsSection'. 56 | /// 57 | public OutputCacheProfile RetrieveOutputCacheProfile(string cacheProfileName) 58 | { 59 | OutputCacheSettingsSection outputCacheSettingsSection; 60 | 61 | try 62 | { 63 | outputCacheSettingsSection = (OutputCacheSettingsSection)ConfigurationManager.GetSection("system.web/caching/outputCacheSettings"); 64 | } 65 | catch (SecurityException) 66 | { 67 | throw new SecurityException("MvcDonutCaching does not have permission to read web.config section 'OutputCacheSettingsSection'."); 68 | } 69 | 70 | if (outputCacheSettingsSection != null && outputCacheSettingsSection.OutputCacheProfiles.Count > 0) 71 | { 72 | var cacheProfile = outputCacheSettingsSection.OutputCacheProfiles[cacheProfileName]; 73 | 74 | if (cacheProfile != null) 75 | { 76 | return cacheProfile; 77 | } 78 | } 79 | 80 | throw new HttpException(string.Format("The '{0}' cache profile is not defined. Please define it in the configuration file.", cacheProfileName)); 81 | } 82 | 83 | /// 84 | /// Return a value indicating whether caching is globally enabled. 85 | /// 86 | /// 87 | /// true if caching is globally enabled; otherwise, false. 88 | /// 89 | public bool IsCachingEnabledGlobally 90 | { 91 | get { return _outputCacheSection.EnableOutputCache; } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/MvcDonutCaching.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {854E90C7-8320-4EB6-A286-24A8EE5EBE9B} 9 | Library 10 | Properties 11 | DevTrends.MvcDonutCaching 12 | DevTrends.MvcDonutCaching 13 | v4.0 14 | 512 15 | ..\..\ 16 | true 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | ..\nuget\lib\net40\ 37 | TRACE;RELEASE_PUBLIC 38 | true 39 | pdbonly 40 | AnyCPU 41 | prompt 42 | ..\nuget\lib\net40\DevTrends.MvcDonutCaching.XML 43 | 44 | 45 | true 46 | bin\CI\ 47 | DEBUG;TRACE 48 | full 49 | AnyCPU 50 | prompt 51 | MinimumRecommendedRules.ruleset 52 | 53 | 54 | 55 | ..\packages\JetBrains.Annotations.10.0.0\lib\net20\JetBrains.Annotations.dll 56 | False 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ..\packages\Microsoft.AspNet.Mvc.3.0.50813.1\lib\net40\System.Web.Mvc.dll 67 | False 68 | False 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Designer 110 | 111 | 112 | 113 | 114 | 115 | 116 | Ce projet fait référence à des packages NuGet qui sont manquants sur cet ordinateur. Activez l'option de restauration des packages NuGet pour les télécharger. Pour plus d'informations, consultez http://go.microsoft.com/fwlink/?LinkID=322105. Le fichier manquant est le suivant : {0}. 117 | 118 | 119 | 120 | 127 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Content/main.css: -------------------------------------------------------------------------------- 1 | /*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */ 2 | 3 | /* 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html, 14 | button, 15 | input, 16 | select, 17 | textarea { 18 | color: #222; 19 | } 20 | 21 | html { 22 | font-size: 1em; 23 | line-height: 1.4; 24 | } 25 | 26 | /* 27 | * Remove text-shadow in selection highlight: h5bp.com/i 28 | * These selection rule sets have to be separate. 29 | * Customize the background color to match your design. 30 | */ 31 | 32 | ::-moz-selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | ::selection { 38 | background: #b3d4fc; 39 | text-shadow: none; 40 | } 41 | 42 | /* 43 | * A better looking default horizontal rule 44 | */ 45 | 46 | hr { 47 | display: block; 48 | height: 1px; 49 | border: 0; 50 | border-top: 1px solid #ccc; 51 | margin: 1em 0; 52 | padding: 0; 53 | } 54 | 55 | /* 56 | * Remove the gap between images, videos, audio and canvas and the bottom of 57 | * their containers: h5bp.com/i/440 58 | */ 59 | 60 | audio, 61 | canvas, 62 | img, 63 | video { 64 | vertical-align: middle; 65 | } 66 | 67 | /* 68 | * Remove default fieldset styles. 69 | */ 70 | 71 | fieldset { 72 | border: 0; 73 | margin: 0; 74 | padding: 0; 75 | } 76 | 77 | /* 78 | * Allow only vertical resizing of textareas. 79 | */ 80 | 81 | textarea { 82 | resize: vertical; 83 | } 84 | 85 | /* ========================================================================== 86 | Browse Happy prompt 87 | ========================================================================== */ 88 | 89 | .browsehappy { 90 | margin: 0.2em 0; 91 | background: #ccc; 92 | color: #000; 93 | padding: 0.2em 0; 94 | } 95 | 96 | /* ========================================================================== 97 | Author's custom styles 98 | ========================================================================== */ 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | /* ========================================================================== 117 | Helper classes 118 | ========================================================================== */ 119 | 120 | /* 121 | * Image replacement 122 | */ 123 | 124 | .ir { 125 | background-color: transparent; 126 | border: 0; 127 | overflow: hidden; 128 | /* IE 6/7 fallback */ 129 | *text-indent: -9999px; 130 | } 131 | 132 | .ir:before { 133 | content: ""; 134 | display: block; 135 | width: 0; 136 | height: 150%; 137 | } 138 | 139 | /* 140 | * Hide from both screenreaders and browsers: h5bp.com/u 141 | */ 142 | 143 | .hidden { 144 | display: none !important; 145 | visibility: hidden; 146 | } 147 | 148 | /* 149 | * Hide only visually, but have it available for screenreaders: h5bp.com/v 150 | */ 151 | 152 | .visuallyhidden { 153 | border: 0; 154 | clip: rect(0 0 0 0); 155 | height: 1px; 156 | margin: -1px; 157 | overflow: hidden; 158 | padding: 0; 159 | position: absolute; 160 | width: 1px; 161 | } 162 | 163 | /* 164 | * Extends the .visuallyhidden class to allow the element to be focusable 165 | * when navigated to via the keyboard: h5bp.com/p 166 | */ 167 | 168 | .visuallyhidden.focusable:active, 169 | .visuallyhidden.focusable:focus { 170 | clip: auto; 171 | height: auto; 172 | margin: 0; 173 | overflow: visible; 174 | position: static; 175 | width: auto; 176 | } 177 | 178 | /* 179 | * Hide visually and from screenreaders, but maintain layout 180 | */ 181 | 182 | .invisible { 183 | visibility: hidden; 184 | } 185 | 186 | /* 187 | * Clearfix: contain floats 188 | * 189 | * For modern browsers 190 | * 1. The space content is one way to avoid an Opera bug when the 191 | * `contenteditable` attribute is included anywhere else in the document. 192 | * Otherwise it causes space to appear at the top and bottom of elements 193 | * that receive the `clearfix` class. 194 | * 2. The use of `table` rather than `block` is only necessary if using 195 | * `:before` to contain the top-margins of child elements. 196 | */ 197 | 198 | .clearfix:before, 199 | .clearfix:after { 200 | content: " "; /* 1 */ 201 | display: table; /* 2 */ 202 | } 203 | 204 | .clearfix:after { 205 | clear: both; 206 | } 207 | 208 | /* 209 | * For IE 6/7 only 210 | * Include this rule to trigger hasLayout and contain floats. 211 | */ 212 | 213 | .clearfix { 214 | *zoom: 1; 215 | } 216 | 217 | /* ========================================================================== 218 | EXAMPLE Media Queries for Responsive Design. 219 | These examples override the primary ('mobile first') styles. 220 | Modify as content requires. 221 | ========================================================================== */ 222 | 223 | @media only screen and (min-width: 35em) { 224 | /* Style adjustments for viewports that meet the condition */ 225 | } 226 | 227 | @media print, 228 | (-o-min-device-pixel-ratio: 5/4), 229 | (-webkit-min-device-pixel-ratio: 1.25), 230 | (min-resolution: 120dpi) { 231 | /* Style adjustments for high resolution devices */ 232 | } 233 | 234 | /* ========================================================================== 235 | Print styles. 236 | Inlined to avoid required HTTP connection: h5bp.com/r 237 | ========================================================================== */ 238 | 239 | @media print { 240 | * { 241 | background: transparent !important; 242 | color: #000 !important; /* Black prints faster: h5bp.com/s */ 243 | box-shadow: none !important; 244 | text-shadow: none !important; 245 | } 246 | 247 | a, 248 | a:visited { 249 | text-decoration: underline; 250 | } 251 | 252 | a[href]:after { 253 | content: " (" attr(href) ")"; 254 | } 255 | 256 | abbr[title]:after { 257 | content: " (" attr(title) ")"; 258 | } 259 | 260 | /* 261 | * Don't show links for images, or javascript/internal links 262 | */ 263 | 264 | .ir a:after, 265 | a[href^="javascript:"]:after, 266 | a[href^="#"]:after { 267 | content: ""; 268 | } 269 | 270 | pre, 271 | blockquote { 272 | border: 1px solid #999; 273 | page-break-inside: avoid; 274 | } 275 | 276 | thead { 277 | display: table-header-group; /* h5bp.com/t */ 278 | } 279 | 280 | tr, 281 | img { 282 | page-break-inside: avoid; 283 | } 284 | 285 | img { 286 | max-width: 100% !important; 287 | } 288 | 289 | @page { 290 | margin: 0.5cm; 291 | } 292 | 293 | p, 294 | h2, 295 | h3 { 296 | orphans: 3; 297 | widows: 3; 298 | } 299 | 300 | h2, 301 | h3 { 302 | page-break-after: avoid; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Mvc; 3 | using System.Web.Security; 4 | using DevTrends.MvcDonutCaching.Demo.Models; 5 | using DevTrends.MvcDonutCaching.Demo.Mvc; 6 | 7 | namespace DevTrends.MvcDonutCaching.Demo.Controllers 8 | { 9 | [Authorize] 10 | public class AccountController : ApplicationController 11 | { 12 | // 13 | // GET: /Account/LogIn 14 | [AllowAnonymous] 15 | public ActionResult LogIn() 16 | { 17 | return View(); 18 | } 19 | 20 | // 21 | // POST: /Account/LogIn 22 | [AllowAnonymous, HttpPost] 23 | public ActionResult LogIn(LogOnModel model, string returnUrl) 24 | { 25 | if (ModelState.IsValid) 26 | { 27 | if (Membership.ValidateUser(model.UserName, model.Password)) 28 | { 29 | FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); 30 | 31 | if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") 32 | && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\")) 33 | { 34 | return Redirect(returnUrl); 35 | } 36 | 37 | return RedirectToAction("Index", "Home"); 38 | } 39 | 40 | ModelState.AddModelError("", "Le nom d'utilisateur ou mot de passe fourni est incorrect."); 41 | } 42 | 43 | // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire 44 | return View(model); 45 | } 46 | 47 | // 48 | // GET: /Account/LogOut 49 | public ActionResult LogOut() 50 | { 51 | FormsAuthentication.SignOut(); 52 | 53 | return RedirectToAction("Index", "Home"); 54 | } 55 | 56 | // 57 | // GET: /Account/Register 58 | [AllowAnonymous] 59 | public ActionResult Register() 60 | { 61 | return View(); 62 | } 63 | 64 | // 65 | // POST: /Account/Register 66 | [AllowAnonymous, HttpPost] 67 | public ActionResult Register(RegisterModel model) 68 | { 69 | if (ModelState.IsValid) 70 | { 71 | // Tentative d'inscription de l'utilisateur 72 | MembershipCreateStatus createStatus; 73 | Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, 74 | out createStatus); 75 | 76 | if (createStatus == MembershipCreateStatus.Success) 77 | { 78 | FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */); 79 | return RedirectToAction("Index", "Home"); 80 | } 81 | 82 | ModelState.AddModelError("", ErrorCodeToString(createStatus)); 83 | } 84 | 85 | // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire 86 | return View(model); 87 | } 88 | 89 | // 90 | // GET: /Account/ChangePassword 91 | public ActionResult ChangePassword() 92 | { 93 | return View(); 94 | } 95 | 96 | // 97 | // POST: /Account/ChangePassword 98 | [HttpPost] 99 | public ActionResult ChangePassword(ChangePasswordModel model) 100 | { 101 | if (ModelState.IsValid) 102 | { 103 | // ChangePassword lève une exception plutôt 104 | // que de retourner false dans certains scénarios de défaillance. 105 | bool changePasswordSucceeded; 106 | try 107 | { 108 | var currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */); 109 | changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword); 110 | } 111 | catch (Exception) 112 | { 113 | changePasswordSucceeded = false; 114 | } 115 | 116 | if (changePasswordSucceeded) 117 | { 118 | return RedirectToAction("ChangePasswordSuccess"); 119 | } 120 | 121 | ModelState.AddModelError( 122 | "", 123 | "Le mot de passe actuel est incorrect ou le nouveau mot de passe n'est pas valide." 124 | ); 125 | } 126 | 127 | // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire 128 | return View(model); 129 | } 130 | 131 | // 132 | // GET: /Account/ChangePasswordSuccess 133 | public ActionResult ChangePasswordSuccess() 134 | { 135 | return View(); 136 | } 137 | 138 | #region Status Codes 139 | 140 | private static string ErrorCodeToString(MembershipCreateStatus createStatus) 141 | { 142 | // Consultez http://go.microsoft.com/fwlink/?LinkID=177550 pour 143 | // obtenir la liste complète des codes d'état. 144 | switch (createStatus) 145 | { 146 | case MembershipCreateStatus.DuplicateUserName: 147 | return "Le nom d'utilisateur existe déjà. Entrez un nom d'utilisateur différent."; 148 | 149 | case MembershipCreateStatus.DuplicateEmail: 150 | return 151 | "Un nom d'utilisateur pour cette adresse de messagerie existe déjà. Entrez une adresse de messagerie différente."; 152 | 153 | case MembershipCreateStatus.InvalidPassword: 154 | return "Le mot de passe fourni n'est pas valide. Entrez une valeur de mot de passe valide."; 155 | 156 | case MembershipCreateStatus.InvalidEmail: 157 | return "L'adresse de messagerie fournie n'est pas valide. Vérifiez la valeur et réessayez."; 158 | 159 | case MembershipCreateStatus.InvalidAnswer: 160 | return 161 | "La réponse de récupération du mot de passe fournie n'est pas valide. Vérifiez la valeur et réessayez."; 162 | 163 | case MembershipCreateStatus.InvalidQuestion: 164 | return 165 | "La question de récupération du mot de passe fournie n'est pas valide. Vérifiez la valeur et réessayez."; 166 | 167 | case MembershipCreateStatus.InvalidUserName: 168 | return "Le nom d'utilisateur fourni n'est pas valide. Vérifiez la valeur et réessayez."; 169 | 170 | case MembershipCreateStatus.ProviderError: 171 | return 172 | "Le fournisseur d'authentification a retourné une erreur. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système."; 173 | 174 | case MembershipCreateStatus.UserRejected: 175 | return 176 | "La demande de création d'utilisateur a été annulée. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système."; 177 | 178 | default: 179 | return 180 | "Une erreur inconnue s'est produite. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système."; 181 | } 182 | } 183 | 184 | #endregion 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/KeyGenerator.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Web; 6 | using System.Web.Mvc; 7 | using System.Web.Routing; 8 | 9 | namespace DevTrends.MvcDonutCaching 10 | { 11 | public class KeyGenerator : IKeyGenerator 12 | { 13 | internal const string RouteDataKeyAction = "action"; 14 | internal const string RouteDataKeyController = "controller"; 15 | internal const string DataTokensKeyArea = "area"; 16 | 17 | private readonly IKeyBuilder _keyBuilder; 18 | 19 | public KeyGenerator(IKeyBuilder keyBuilder) 20 | { 21 | if (keyBuilder == null) 22 | { 23 | throw new ArgumentNullException("keyBuilder"); 24 | } 25 | 26 | _keyBuilder = keyBuilder; 27 | } 28 | 29 | /// 30 | /// Generates a key given the and . 31 | /// 32 | /// The controller context. 33 | /// The cache settings. 34 | /// A string that can be used as an output cache key 35 | [CanBeNull] 36 | public string GenerateKey(ControllerContext context, CacheSettings cacheSettings) 37 | { 38 | var routeData = context.RouteData; 39 | 40 | if (routeData == null) 41 | { 42 | return null; 43 | } 44 | 45 | string actionName = null, 46 | controllerName = null; 47 | 48 | if ( 49 | routeData.Values.ContainsKey(RouteDataKeyAction) && 50 | routeData.Values[RouteDataKeyAction] != null) 51 | { 52 | actionName = routeData.Values[RouteDataKeyAction].ToString(); 53 | } 54 | 55 | if ( 56 | routeData.Values.ContainsKey(RouteDataKeyController) && 57 | routeData.Values[RouteDataKeyController] != null) 58 | { 59 | controllerName = routeData.Values[RouteDataKeyController].ToString(); 60 | } 61 | 62 | if (string.IsNullOrEmpty(actionName) || string.IsNullOrEmpty(controllerName)) 63 | { 64 | return null; 65 | } 66 | 67 | string areaName = null; 68 | 69 | if ( 70 | routeData.DataTokens.ContainsKey(DataTokensKeyArea) && 71 | routeData.DataTokens[DataTokensKeyArea] != null 72 | ) 73 | { 74 | areaName = routeData.DataTokens[DataTokensKeyArea].ToString(); 75 | } 76 | 77 | // remove controller, action and DictionaryValueProvider which is added by the framework for child actions 78 | var filteredRouteData = routeData.Values.Where( 79 | x => x.Key.ToLowerInvariant() != RouteDataKeyController && 80 | x.Key.ToLowerInvariant() != RouteDataKeyAction && 81 | x.Key.ToLowerInvariant() != DataTokensKeyArea && 82 | !(x.Value is DictionaryValueProvider) 83 | ).ToList(); 84 | 85 | if (!string.IsNullOrWhiteSpace(areaName)) 86 | { 87 | filteredRouteData.Add(new KeyValuePair(DataTokensKeyArea, areaName)); 88 | } 89 | 90 | var routeValues = new RouteValueDictionary(filteredRouteData.ToDictionary(x => x.Key.ToLowerInvariant(), x => x.Value)); 91 | 92 | if (!context.IsChildAction) 93 | { 94 | // note that route values take priority over form values and form values take priority over query string values 95 | if ((cacheSettings.Options & OutputCacheOptions.IgnoreFormData) != OutputCacheOptions.IgnoreFormData) 96 | { 97 | foreach (var formKey in context.HttpContext.Request.Form.AllKeys) 98 | { 99 | if (routeValues.ContainsKey(formKey.ToLowerInvariant())) 100 | { 101 | continue; 102 | } 103 | 104 | var item = context.HttpContext.Request.Form[formKey]; 105 | routeValues.Add( 106 | formKey.ToLowerInvariant(), 107 | item != null 108 | ? item.ToLowerInvariant() 109 | : string.Empty 110 | ); 111 | } 112 | } 113 | 114 | if ((cacheSettings.Options & OutputCacheOptions.IgnoreQueryString) != OutputCacheOptions.IgnoreQueryString) 115 | { 116 | foreach (var queryStringKey in context.HttpContext.Request.QueryString.AllKeys) 117 | { 118 | // queryStringKey is null if url has as name without value. e.g. test.com?q 119 | if (queryStringKey == null || routeValues.ContainsKey(queryStringKey.ToLowerInvariant())) 120 | { 121 | continue; 122 | } 123 | 124 | var item = context.HttpContext.Request.QueryString[queryStringKey]; 125 | routeValues.Add( 126 | queryStringKey.ToLowerInvariant(), 127 | item != null 128 | ? item.ToLowerInvariant() 129 | : string.Empty 130 | ); 131 | } 132 | } 133 | } 134 | 135 | if (!string.IsNullOrEmpty(cacheSettings.VaryByParam)) 136 | { 137 | if (cacheSettings.VaryByParam.ToLowerInvariant() == "none") 138 | { 139 | routeValues.Clear(); 140 | } 141 | else if (cacheSettings.VaryByParam != "*") 142 | { 143 | var parameters = cacheSettings.VaryByParam.ToLowerInvariant().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); 144 | routeValues = new RouteValueDictionary(routeValues.Where(x => parameters.Contains(x.Key)) 145 | .ToDictionary(x => x.Key, x => x.Value)); 146 | } 147 | } 148 | 149 | if (!string.IsNullOrEmpty(cacheSettings.VaryByCustom)) 150 | { 151 | // If there is an existing route value with the same key as varybycustom, we should overwrite it 152 | routeValues[cacheSettings.VaryByCustom.ToLowerInvariant()] = 153 | context.HttpContext.ApplicationInstance.GetVaryByCustomString(HttpContext.Current, cacheSettings.VaryByCustom); 154 | } 155 | 156 | if (!string.IsNullOrEmpty(cacheSettings.VaryByHeader)) 157 | { 158 | var headers = cacheSettings.VaryByHeader.ToLowerInvariant().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); 159 | var existingHeaders = context.HttpContext.Request.Headers.AllKeys.Where(x => headers.Contains(x.ToLowerInvariant())); 160 | foreach (var header in existingHeaders) 161 | { 162 | routeValues[header] = context.HttpContext.Request.Headers[header]; 163 | } 164 | } 165 | 166 | var key = _keyBuilder.BuildKey(controllerName, actionName, routeValues); 167 | 168 | return key; 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/OutputCacheManager.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Web.Caching; 6 | using System.Web.Routing; 7 | 8 | namespace DevTrends.MvcDonutCaching 9 | { 10 | public class OutputCacheManager : IReadWriteOutputCacheManager 11 | { 12 | private readonly OutputCacheProvider _outputCacheProvider; 13 | private readonly IKeyBuilder _keyBuilder; 14 | 15 | public OutputCacheManager() 16 | : this(OutputCache.Instance, new KeyBuilder()) 17 | { 18 | } 19 | 20 | public OutputCacheManager(OutputCacheProvider outputCacheProvider, IKeyBuilder keyBuilder) 21 | { 22 | _outputCacheProvider = outputCacheProvider; 23 | _keyBuilder = keyBuilder; 24 | } 25 | 26 | /// 27 | /// Gets the key builder. 28 | /// 29 | /// 30 | /// The key builder. 31 | /// 32 | public IKeyBuilder KeyBuilder 33 | { 34 | get { return _keyBuilder; } 35 | } 36 | 37 | /// 38 | /// Add sthe given in the cache. 39 | /// 40 | /// The cache key to add. 41 | /// The cache item to add. 42 | /// The cache item UTC expiry date and time. 43 | public void AddItem(string key, CacheItem cacheItem, DateTime utcExpiry) 44 | { 45 | _outputCacheProvider.Add(key, cacheItem, utcExpiry); 46 | } 47 | 48 | /// 49 | /// Retrieves a cache item the given the . 50 | /// 51 | /// The key. 52 | /// 53 | /// A instance on cache hit, null otherwise. 54 | /// 55 | public CacheItem GetItem(string key) 56 | { 57 | return _outputCacheProvider.Get(key) as CacheItem; 58 | } 59 | 60 | /// 61 | /// Removes a single output cache entry for the specified controller and action. 62 | /// 63 | /// The name of the controller that contains the action method. 64 | /// The name of the controller action method. 65 | public void RemoveItem([AspMvcController] string controllerName, [AspMvcAction] string actionName) 66 | { 67 | RemoveItem(controllerName, actionName, null); 68 | } 69 | 70 | /// 71 | /// Removes a single output cache entry for the specified controller, action and parameters. 72 | /// 73 | /// The name of the controller that contains the action method. 74 | /// The name of the controller action method. 75 | /// An object that contains the parameters for a route. 76 | public void RemoveItem([AspMvcController] string controllerName, [AspMvcAction] string actionName, object routeValues) 77 | { 78 | RemoveItem(controllerName, actionName, new RouteValueDictionary(routeValues)); 79 | } 80 | 81 | /// 82 | /// Removes a single output cache entry for the specified controller, action and parameters. 83 | /// 84 | /// The name of the controller that contains the action method. 85 | /// The name of the controller action method. 86 | /// A dictionary that contains the parameters for a route. 87 | public void RemoveItem([AspMvcController] string controllerName, [AspMvcAction] string actionName, RouteValueDictionary routeValues) 88 | { 89 | var key = _keyBuilder.BuildKey(controllerName, actionName, routeValues); 90 | 91 | _outputCacheProvider.Remove(key); 92 | } 93 | 94 | /// 95 | /// Removes all output cache entries. 96 | /// 97 | public void RemoveItems() 98 | { 99 | RemoveItems(null, null, null); 100 | } 101 | 102 | /// 103 | /// Removes all output cache entries for the specified controller. 104 | /// 105 | /// The name of the controller. 106 | public void RemoveItems([AspMvcController] string controllerName) 107 | { 108 | RemoveItems(controllerName, null, null); 109 | } 110 | 111 | /// 112 | /// Removes all output cache entries for the specified controller and action. 113 | /// 114 | /// The name of the controller that contains the action method. 115 | /// The name of the controller action method. 116 | public void RemoveItems([AspMvcController] string controllerName, [AspMvcAction] string actionName) 117 | { 118 | RemoveItems(controllerName, actionName, null); 119 | } 120 | 121 | /// 122 | /// Removes all output cache entries for the specified controller, action and parameters. 123 | /// 124 | /// The name of the controller that contains the action method. 125 | /// The name of the controller action method. 126 | /// An object that contains the parameters for a route. 127 | public void RemoveItems([AspMvcController] string controllerName, [AspMvcAction] string actionName, object routeValues) 128 | { 129 | RemoveItems(controllerName, actionName, new RouteValueDictionary(routeValues)); 130 | } 131 | 132 | /// 133 | /// Removes all output cache entries for the specified controller, action and parameters. 134 | /// 135 | /// The name of the controller that contains the action method. 136 | /// The name of the controller action method. 137 | /// A dictionary that contains the parameters for a route. 138 | public void RemoveItems([AspMvcController] string controllerName, [AspMvcAction] string actionName, RouteValueDictionary routeValues) 139 | { 140 | var enumerableCache = _outputCacheProvider as IEnumerable>; 141 | 142 | if (enumerableCache == null) 143 | { 144 | throw new NotSupportedException("Ensure that your custom OutputCacheProvider implements IEnumerable>."); 145 | } 146 | 147 | var key = _keyBuilder.BuildKey(controllerName, actionName); 148 | 149 | if (string.IsNullOrEmpty(key)) 150 | { 151 | return; 152 | } 153 | 154 | var keysToDelete = enumerableCache 155 | .Where(_ => !string.IsNullOrEmpty(_.Key) && _.Key.StartsWith(key)) 156 | .Select(_ => _.Key); 157 | 158 | if (routeValues != null) 159 | { 160 | foreach (var routeValue in routeValues) 161 | { 162 | // Ignoring the "area" part of the route values if it's an empty string 163 | // Ref : https://github.com/moonpyk/mvcdonutcaching/issues/36 164 | if (routeValue.Key == KeyGenerator.DataTokensKeyArea) 165 | { 166 | var areaString = routeValue.Value as string; 167 | if (string.IsNullOrWhiteSpace(areaString)) 168 | { 169 | continue; 170 | } 171 | } 172 | 173 | var keyFrag = _keyBuilder.BuildKeyFragment(routeValue); 174 | 175 | if (string.IsNullOrEmpty(keyFrag)) 176 | { 177 | continue; 178 | } 179 | 180 | keysToDelete = keysToDelete.Where(_ => !string.IsNullOrEmpty(_) && _.Contains(keyFrag)); 181 | } 182 | } 183 | 184 | foreach (var keyToDelete in keysToDelete) 185 | { 186 | _outputCacheProvider.Remove(keyToDelete); 187 | } 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/Content/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | *display: inline; 35 | *zoom: 1; 36 | } 37 | 38 | /** 39 | * Prevent modern browsers from displaying `audio` without controls. 40 | * Remove excess height in iOS 5 devices. 41 | */ 42 | 43 | audio:not([controls]) { 44 | display: none; 45 | height: 0; 46 | } 47 | 48 | /** 49 | * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. 50 | * Known issue: no IE 6 support. 51 | */ 52 | 53 | [hidden] { 54 | display: none; 55 | } 56 | 57 | /* ========================================================================== 58 | Base 59 | ========================================================================== */ 60 | 61 | /** 62 | * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using 63 | * `em` units. 64 | * 2. Prevent iOS text size adjust after orientation change, without disabling 65 | * user zoom. 66 | */ 67 | 68 | html { 69 | font-size: 100%; /* 1 */ 70 | -ms-text-size-adjust: 100%; /* 2 */ 71 | -webkit-text-size-adjust: 100%; /* 2 */ 72 | } 73 | 74 | /** 75 | * Address `font-family` inconsistency between `textarea` and other form 76 | * elements. 77 | */ 78 | 79 | html, 80 | button, 81 | input, 82 | select, 83 | textarea { 84 | font-family: sans-serif; 85 | } 86 | 87 | /** 88 | * Address margins handled incorrectly in IE 6/7. 89 | */ 90 | 91 | body { 92 | margin: 0; 93 | } 94 | 95 | /* ========================================================================== 96 | Links 97 | ========================================================================== */ 98 | 99 | /** 100 | * Address `outline` inconsistency between Chrome and other browsers. 101 | */ 102 | 103 | a:focus { 104 | outline: thin dotted; 105 | } 106 | 107 | /** 108 | * Improve readability when focused and also mouse hovered in all browsers. 109 | */ 110 | 111 | a:active, 112 | a:hover { 113 | outline: 0; 114 | } 115 | 116 | /* ========================================================================== 117 | Typography 118 | ========================================================================== */ 119 | 120 | /** 121 | * Address font sizes and margins set differently in IE 6/7. 122 | * Address font sizes within `section` and `article` in Firefox 4+, Safari 5, 123 | * and Chrome. 124 | */ 125 | 126 | h1 { 127 | font-size: 2em; 128 | margin: 0.67em 0; 129 | } 130 | 131 | h2 { 132 | font-size: 1.5em; 133 | margin: 0.83em 0; 134 | } 135 | 136 | h3 { 137 | font-size: 1.17em; 138 | margin: 1em 0; 139 | } 140 | 141 | h4 { 142 | font-size: 1em; 143 | margin: 1.33em 0; 144 | } 145 | 146 | h5 { 147 | font-size: 0.83em; 148 | margin: 1.67em 0; 149 | } 150 | 151 | h6 { 152 | font-size: 0.67em; 153 | margin: 2.33em 0; 154 | } 155 | 156 | /** 157 | * Address styling not present in IE 7/8/9, Safari 5, and Chrome. 158 | */ 159 | 160 | abbr[title] { 161 | border-bottom: 1px dotted; 162 | } 163 | 164 | /** 165 | * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. 166 | */ 167 | 168 | b, 169 | strong { 170 | font-weight: bold; 171 | } 172 | 173 | blockquote { 174 | margin: 1em 40px; 175 | } 176 | 177 | /** 178 | * Address styling not present in Safari 5 and Chrome. 179 | */ 180 | 181 | dfn { 182 | font-style: italic; 183 | } 184 | 185 | /** 186 | * Address differences between Firefox and other browsers. 187 | * Known issue: no IE 6/7 normalization. 188 | */ 189 | 190 | hr { 191 | -moz-box-sizing: content-box; 192 | box-sizing: content-box; 193 | height: 0; 194 | } 195 | 196 | /** 197 | * Address styling not present in IE 6/7/8/9. 198 | */ 199 | 200 | mark { 201 | background: #ff0; 202 | color: #000; 203 | } 204 | 205 | /** 206 | * Address margins set differently in IE 6/7. 207 | */ 208 | 209 | p, 210 | pre { 211 | margin: 1em 0; 212 | } 213 | 214 | /** 215 | * Correct font family set oddly in IE 6, Safari 4/5, and Chrome. 216 | */ 217 | 218 | code, 219 | kbd, 220 | pre, 221 | samp { 222 | font-family: monospace, serif; 223 | _font-family: 'courier new', monospace; 224 | font-size: 1em; 225 | } 226 | 227 | /** 228 | * Improve readability of pre-formatted text in all browsers. 229 | */ 230 | 231 | pre { 232 | white-space: pre; 233 | white-space: pre-wrap; 234 | word-wrap: break-word; 235 | } 236 | 237 | /** 238 | * Address CSS quotes not supported in IE 6/7. 239 | */ 240 | 241 | q { 242 | quotes: none; 243 | } 244 | 245 | /** 246 | * Address `quotes` property not supported in Safari 4. 247 | */ 248 | 249 | q:before, 250 | q:after { 251 | content: ''; 252 | content: none; 253 | } 254 | 255 | /** 256 | * Address inconsistent and variable font size in all browsers. 257 | */ 258 | 259 | small { 260 | font-size: 80%; 261 | } 262 | 263 | /** 264 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 265 | */ 266 | 267 | sub, 268 | sup { 269 | font-size: 75%; 270 | line-height: 0; 271 | position: relative; 272 | vertical-align: baseline; 273 | } 274 | 275 | sup { 276 | top: -0.5em; 277 | } 278 | 279 | sub { 280 | bottom: -0.25em; 281 | } 282 | 283 | /* ========================================================================== 284 | Lists 285 | ========================================================================== */ 286 | 287 | /** 288 | * Address margins set differently in IE 6/7. 289 | */ 290 | 291 | dl, 292 | menu, 293 | ol, 294 | ul { 295 | margin: 1em 0; 296 | } 297 | 298 | dd { 299 | margin: 0 0 0 40px; 300 | } 301 | 302 | /** 303 | * Address paddings set differently in IE 6/7. 304 | */ 305 | 306 | menu, 307 | ol, 308 | ul { 309 | padding: 0 0 0 40px; 310 | } 311 | 312 | /** 313 | * Correct list images handled incorrectly in IE 7. 314 | */ 315 | 316 | nav ul, 317 | nav ol { 318 | list-style: none; 319 | list-style-image: none; 320 | } 321 | 322 | /* ========================================================================== 323 | Embedded content 324 | ========================================================================== */ 325 | 326 | /** 327 | * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 328 | * 2. Improve image quality when scaled in IE 7. 329 | */ 330 | 331 | img { 332 | border: 0; /* 1 */ 333 | -ms-interpolation-mode: bicubic; /* 2 */ 334 | } 335 | 336 | /** 337 | * Correct overflow displayed oddly in IE 9. 338 | */ 339 | 340 | svg:not(:root) { 341 | overflow: hidden; 342 | } 343 | 344 | /* ========================================================================== 345 | Figures 346 | ========================================================================== */ 347 | 348 | /** 349 | * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. 350 | */ 351 | 352 | figure { 353 | margin: 0; 354 | } 355 | 356 | /* ========================================================================== 357 | Forms 358 | ========================================================================== */ 359 | 360 | /** 361 | * Correct margin displayed oddly in IE 6/7. 362 | */ 363 | 364 | form { 365 | margin: 0; 366 | } 367 | 368 | /** 369 | * Define consistent border, margin, and padding. 370 | */ 371 | 372 | fieldset { 373 | border: 1px solid #c0c0c0; 374 | margin: 0 2px; 375 | padding: 0.35em 0.625em 0.75em; 376 | } 377 | 378 | /** 379 | * 1. Correct color not being inherited in IE 6/7/8/9. 380 | * 2. Correct text not wrapping in Firefox 3. 381 | * 3. Correct alignment displayed oddly in IE 6/7. 382 | */ 383 | 384 | legend { 385 | border: 0; /* 1 */ 386 | padding: 0; 387 | white-space: normal; /* 2 */ 388 | *margin-left: -7px; /* 3 */ 389 | } 390 | 391 | /** 392 | * 1. Correct font size not being inherited in all browsers. 393 | * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, 394 | * and Chrome. 395 | * 3. Improve appearance and consistency in all browsers. 396 | */ 397 | 398 | button, 399 | input, 400 | select, 401 | textarea { 402 | font-size: 100%; /* 1 */ 403 | margin: 0; /* 2 */ 404 | vertical-align: baseline; /* 3 */ 405 | *vertical-align: middle; /* 3 */ 406 | } 407 | 408 | /** 409 | * Address Firefox 3+ setting `line-height` on `input` using `!important` in 410 | * the UA stylesheet. 411 | */ 412 | 413 | button, 414 | input { 415 | line-height: normal; 416 | } 417 | 418 | /** 419 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 420 | * All other form control elements do not inherit `text-transform` values. 421 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. 422 | * Correct `select` style inheritance in Firefox 4+ and Opera. 423 | */ 424 | 425 | button, 426 | select { 427 | text-transform: none; 428 | } 429 | 430 | /** 431 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 432 | * and `video` controls. 433 | * 2. Correct inability to style clickable `input` types in iOS. 434 | * 3. Improve usability and consistency of cursor style between image-type 435 | * `input` and others. 436 | * 4. Remove inner spacing in IE 7 without affecting normal text inputs. 437 | * Known issue: inner spacing remains in IE 6. 438 | */ 439 | 440 | button, 441 | html input[type="button"], /* 1 */ 442 | input[type="reset"], 443 | input[type="submit"] { 444 | -webkit-appearance: button; /* 2 */ 445 | cursor: pointer; /* 3 */ 446 | *overflow: visible; /* 4 */ 447 | } 448 | 449 | /** 450 | * Re-set default cursor for disabled elements. 451 | */ 452 | 453 | button[disabled], 454 | html input[disabled] { 455 | cursor: default; 456 | } 457 | 458 | /** 459 | * 1. Address box sizing set to content-box in IE 8/9. 460 | * 2. Remove excess padding in IE 8/9. 461 | * 3. Remove excess padding in IE 7. 462 | * Known issue: excess padding remains in IE 6. 463 | */ 464 | 465 | input[type="checkbox"], 466 | input[type="radio"] { 467 | box-sizing: border-box; /* 1 */ 468 | padding: 0; /* 2 */ 469 | *height: 13px; /* 3 */ 470 | *width: 13px; /* 3 */ 471 | } 472 | 473 | /** 474 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 475 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 476 | * (include `-moz` to future-proof). 477 | */ 478 | 479 | input[type="search"] { 480 | -webkit-appearance: textfield; /* 1 */ 481 | -moz-box-sizing: content-box; 482 | -webkit-box-sizing: content-box; /* 2 */ 483 | box-sizing: content-box; 484 | } 485 | 486 | /** 487 | * Remove inner padding and search cancel button in Safari 5 and Chrome 488 | * on OS X. 489 | */ 490 | 491 | input[type="search"]::-webkit-search-cancel-button, 492 | input[type="search"]::-webkit-search-decoration { 493 | -webkit-appearance: none; 494 | } 495 | 496 | /** 497 | * Remove inner padding and border in Firefox 3+. 498 | */ 499 | 500 | button::-moz-focus-inner, 501 | input::-moz-focus-inner { 502 | border: 0; 503 | padding: 0; 504 | } 505 | 506 | /** 507 | * 1. Remove default vertical scrollbar in IE 6/7/8/9. 508 | * 2. Improve readability and alignment in all browsers. 509 | */ 510 | 511 | textarea { 512 | overflow: auto; /* 1 */ 513 | vertical-align: top; /* 2 */ 514 | } 515 | 516 | /* ========================================================================== 517 | Tables 518 | ========================================================================== */ 519 | 520 | /** 521 | * Remove most spacing between table cells. 522 | */ 523 | 524 | table { 525 | border-collapse: collapse; 526 | border-spacing: 0; 527 | } 528 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/DonutOutputCacheAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.UI; 7 | 8 | namespace DevTrends.MvcDonutCaching 9 | { 10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 11 | public class DonutOutputCacheAttribute : ActionFilterAttribute, IExceptionFilter 12 | { 13 | // Protected 14 | protected readonly ICacheHeadersHelper CacheHeadersHelper; 15 | protected readonly ICacheSettingsManager CacheSettingsManager; 16 | protected readonly IDonutHoleFiller DonutHoleFiller; 17 | protected readonly IKeyGenerator KeyGenerator; 18 | protected readonly IReadWriteOutputCacheManager OutputCacheManager; 19 | protected CacheSettings CacheSettings; 20 | 21 | // Private 22 | private bool? _noStore; 23 | private OutputCacheOptions? _options; 24 | 25 | /// 26 | /// Initializes a new instance of the class. 27 | /// 28 | public DonutOutputCacheAttribute() : this(new KeyBuilder()) { } 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// The key builder. 34 | public DonutOutputCacheAttribute(IKeyBuilder keyBuilder) : 35 | this( 36 | new KeyGenerator(keyBuilder), 37 | new OutputCacheManager(OutputCache.Instance, keyBuilder), 38 | new DonutHoleFiller(HtmlHelperExtensions.Serialiser), 39 | new CacheSettingsManager(), 40 | new CacheHeadersHelper() 41 | ) 42 | { } 43 | 44 | /// 45 | /// Initializes a new instance of the class. 46 | /// 47 | /// The key generator. 48 | /// The output cache manager. 49 | /// The donut hole filler. 50 | /// The cache settings manager. 51 | /// The cache headers helper. 52 | protected DonutOutputCacheAttribute( 53 | IKeyGenerator keyGenerator, IReadWriteOutputCacheManager outputCacheManager, 54 | IDonutHoleFiller donutHoleFiller, ICacheSettingsManager cacheSettingsManager, ICacheHeadersHelper cacheHeadersHelper 55 | ) 56 | { 57 | KeyGenerator = keyGenerator; 58 | OutputCacheManager = outputCacheManager; 59 | DonutHoleFiller = donutHoleFiller; 60 | CacheSettingsManager = cacheSettingsManager; 61 | CacheHeadersHelper = cacheHeadersHelper; 62 | 63 | Duration = -1; 64 | Location = (OutputCacheLocation)(-1); 65 | Options = OutputCache.DefaultOptions; 66 | } 67 | 68 | /// 69 | /// Gets or sets the cache duration, in seconds. 70 | /// 71 | public int Duration 72 | { 73 | get; 74 | set; 75 | } 76 | 77 | /// 78 | /// Gets or sets the vary-by-param value. 79 | /// 80 | public string VaryByParam 81 | { 82 | get; 83 | set; 84 | } 85 | 86 | /// 87 | /// Gets or sets the vary-by-header value. 88 | /// 89 | public string VaryByHeader 90 | { 91 | get; 92 | set; 93 | } 94 | 95 | /// 96 | /// Gets or sets the vary-by-custom value. 97 | /// 98 | public string VaryByCustom 99 | { 100 | get; 101 | set; 102 | } 103 | 104 | /// 105 | /// Gets or sets the cache profile name. 106 | /// 107 | public string CacheProfile 108 | { 109 | get; 110 | set; 111 | } 112 | 113 | /// 114 | /// Gets or sets the location. 115 | /// 116 | public OutputCacheLocation Location 117 | { 118 | get; 119 | set; 120 | } 121 | 122 | /// 123 | /// Gets or sets a value that indicates whether to store the cache. 124 | /// 125 | public bool NoStore 126 | { 127 | get 128 | { 129 | return _noStore ?? false; 130 | } 131 | set 132 | { 133 | _noStore = value; 134 | } 135 | } 136 | 137 | /// 138 | /// Get or sets the for this attributes. Specifying a value here will 139 | /// make the value ignored. 140 | /// 141 | public OutputCacheOptions Options 142 | { 143 | get 144 | { 145 | return _options ?? OutputCacheOptions.None; 146 | } 147 | set 148 | { 149 | _options = value; 150 | } 151 | } 152 | 153 | public void OnException(ExceptionContext filterContext) 154 | { 155 | if (CacheSettings != null) 156 | { 157 | ExecuteCallback(filterContext, true); 158 | } 159 | } 160 | 161 | /// 162 | /// Called before an action method executes. 163 | /// 164 | /// The filter context. 165 | public override void OnActionExecuting(ActionExecutingContext filterContext) 166 | { 167 | CacheSettings = BuildCacheSettings(); 168 | 169 | var cacheKey = KeyGenerator.GenerateKey(filterContext, CacheSettings); 170 | 171 | // If we are unable to generate a cache key it means we can't do anything 172 | if (string.IsNullOrEmpty(cacheKey)) 173 | { 174 | return; 175 | } 176 | 177 | // Are we actually storing data on the server side ? 178 | if (CacheSettings.IsServerCachingEnabled) 179 | { 180 | CacheItem cachedItem = null; 181 | 182 | // If the request is a POST, we lookup for NoCacheLookupForPosts option 183 | // We are fetching the stored value only if the option has not been set and the request is not a POST 184 | if ( 185 | (CacheSettings.Options & OutputCacheOptions.NoCacheLookupForPosts) != OutputCacheOptions.NoCacheLookupForPosts || 186 | filterContext.HttpContext.Request.HttpMethod != "POST" 187 | ) 188 | { 189 | cachedItem = OutputCacheManager.GetItem(cacheKey); 190 | } 191 | 192 | // We have a cached version on the server side 193 | if (cachedItem != null) 194 | { 195 | // We inject the previous result into the MVC pipeline 196 | // The MVC action won't execute as we injected the previous cached result. 197 | filterContext.Result = new ContentResult 198 | { 199 | Content = DonutHoleFiller.ReplaceDonutHoleContent(cachedItem.Content, filterContext, CacheSettings.Options), 200 | ContentType = cachedItem.ContentType 201 | }; 202 | } 203 | } 204 | 205 | // Did we already injected something ? 206 | if (filterContext.Result != null) 207 | { 208 | return; // No need to continue 209 | } 210 | 211 | // We are hooking into the pipeline to replace the response Output writer 212 | // by something we own and later eventually gonna cache 213 | var cachingWriter = new StringWriter(CultureInfo.InvariantCulture); 214 | 215 | var originalWriter = filterContext.HttpContext.Response.Output; 216 | 217 | filterContext.HttpContext.Response.Output = cachingWriter; 218 | 219 | // Will be called back by OnResultExecuted -> ExecuteCallback 220 | filterContext.HttpContext.Items[cacheKey] = new Action(hasErrors => 221 | { 222 | // Removing this executing action from the context 223 | filterContext.HttpContext.Items.Remove(cacheKey); 224 | 225 | // We restore the original writer for response 226 | filterContext.HttpContext.Response.Output = originalWriter; 227 | 228 | if (hasErrors) 229 | { 230 | return; // Something went wrong, we are not going to cache something bad 231 | } 232 | 233 | // Now we use owned caching writer to actually store data 234 | var cacheItem = new CacheItem 235 | { 236 | Content = cachingWriter.ToString(), 237 | ContentType = filterContext.HttpContext.Response.ContentType 238 | }; 239 | 240 | filterContext.HttpContext.Response.Write( 241 | DonutHoleFiller.RemoveDonutHoleWrappers(cacheItem.Content, filterContext, CacheSettings.Options) 242 | ); 243 | 244 | if (CacheSettings.IsServerCachingEnabled && filterContext.HttpContext.Response.StatusCode == 200) 245 | { 246 | OutputCacheManager.AddItem(cacheKey, cacheItem, DateTime.UtcNow.AddSeconds(CacheSettings.Duration)); 247 | } 248 | }); 249 | } 250 | 251 | /// 252 | /// Called after an action result executes. 253 | /// 254 | /// The filter context. 255 | public override void OnResultExecuted(ResultExecutedContext filterContext) 256 | { 257 | if (CacheSettings == null) 258 | { 259 | return; 260 | } 261 | 262 | // See OnActionExecuting 263 | ExecuteCallback(filterContext, filterContext.Exception != null); 264 | 265 | // If we are in the context of a child action, the main action is responsible for setting 266 | // the right HTTP Cache headers for the final response. 267 | if (!filterContext.IsChildAction) 268 | { 269 | CacheHeadersHelper.SetCacheHeaders(filterContext.HttpContext.Response, CacheSettings); 270 | } 271 | } 272 | 273 | /// 274 | /// Builds the cache settings. 275 | /// 276 | /// 277 | /// 278 | /// The 'duration' attribute must have a value that is greater than or equal to zero. 279 | /// 280 | protected CacheSettings BuildCacheSettings() 281 | { 282 | CacheSettings cacheSettings; 283 | 284 | if (string.IsNullOrEmpty(CacheProfile)) 285 | { 286 | cacheSettings = new CacheSettings 287 | { 288 | IsCachingEnabled = CacheSettingsManager.IsCachingEnabledGlobally, 289 | Duration = Duration, 290 | VaryByCustom = VaryByCustom, 291 | VaryByParam = VaryByParam, 292 | VaryByHeader = VaryByHeader, 293 | Location = (int)Location == -1 ? OutputCacheLocation.Server : Location, 294 | NoStore = NoStore, 295 | Options = Options, 296 | }; 297 | } 298 | else 299 | { 300 | var cacheProfile = CacheSettingsManager.RetrieveOutputCacheProfile(CacheProfile); 301 | 302 | cacheSettings = new CacheSettings 303 | { 304 | IsCachingEnabled = CacheSettingsManager.IsCachingEnabledGlobally && cacheProfile.Enabled, 305 | Duration = Duration == -1 ? cacheProfile.Duration : Duration, 306 | VaryByCustom = VaryByCustom ?? cacheProfile.VaryByCustom, 307 | VaryByParam = VaryByParam ?? cacheProfile.VaryByParam, 308 | VaryByHeader = VaryByHeader ?? cacheProfile.VaryByHeader, 309 | Location = (int)Location == -1 ? ((int)cacheProfile.Location == -1 ? OutputCacheLocation.Server : cacheProfile.Location) : Location, 310 | NoStore = _noStore.HasValue ? _noStore.Value : cacheProfile.NoStore, 311 | Options = Options, 312 | }; 313 | } 314 | 315 | if (cacheSettings.Duration == -1) 316 | { 317 | throw new HttpException("The directive or the configuration settings profile must specify the 'duration' attribute."); 318 | } 319 | 320 | if (cacheSettings.Duration < 0) 321 | { 322 | throw new HttpException("The 'duration' attribute must have a value that is greater than or equal to zero."); 323 | } 324 | 325 | return cacheSettings; 326 | } 327 | 328 | /// 329 | /// Executes the callback. 330 | /// 331 | /// The context. 332 | /// if set to true [has errors]. 333 | private void ExecuteCallback(ControllerContext context, bool hasErrors) 334 | { 335 | var cacheKey = KeyGenerator.GenerateKey(context, CacheSettings); 336 | 337 | if (string.IsNullOrEmpty(cacheKey)) 338 | { 339 | return; 340 | } 341 | 342 | var callback = context.HttpContext.Items[cacheKey] as Action; 343 | 344 | if (callback != null) 345 | { 346 | callback.Invoke(hasErrors); 347 | } 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching/HtmlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Annotations; 2 | using System.Web.Mvc; 3 | using System.Web.Mvc.Html; 4 | using System.Web.Routing; 5 | 6 | namespace DevTrends.MvcDonutCaching 7 | { 8 | public static class HtmlHelperExtensions 9 | { 10 | private static IActionSettingsSerialiser _serialiser; 11 | 12 | /// 13 | /// Gets or sets the serialiser. 14 | /// 15 | /// 16 | /// The serialiser. 17 | /// 18 | public static IActionSettingsSerialiser Serialiser 19 | { 20 | get 21 | { 22 | return _serialiser ?? (_serialiser = new EncryptingActionSettingsSerialiser(new ActionSettingsSerialiser(), new Encryptor())); 23 | } 24 | set 25 | { 26 | _serialiser = value; 27 | } 28 | } 29 | 30 | /// 31 | /// Invokes the specified child action method and returns the result as an HTML string. 32 | /// 33 | /// The HTML helper instance that this method extends. 34 | /// The name of the action method to invoke. 35 | /// A flag that determines whether the action should be excluded from any parent cache. 36 | /// The child action result as an HTML string. 37 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, bool excludeFromParentCache) 38 | { 39 | return h.Action(actionName, null, null, excludeFromParentCache); 40 | } 41 | 42 | /// 43 | /// Invokes the specified child action method using the specified parameters and returns the result as an HTML string. 44 | /// 45 | /// The HTML helper instance that this method extends. 46 | /// The name of the action method to invoke. 47 | /// An object that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 48 | /// A flag that determines whether the action should be excluded from any parent cache. 49 | /// The child action result as an HTML string. 50 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, object routeValues, bool excludeFromParentCache) 51 | { 52 | return h.Action(actionName, null, new RouteValueDictionary(routeValues), excludeFromParentCache); 53 | } 54 | 55 | /// 56 | /// Invokes the specified child action method using the specified parameters and returns the result as an HTML string. 57 | /// 58 | /// The HTML helper instance that this method extends. 59 | /// The name of the action method to invoke. 60 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 61 | /// A flag that determines whether the action should be excluded from any parent cache. 62 | /// The child action result as an HTML string. 63 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, RouteValueDictionary routeValues, bool excludeFromParentCache) 64 | { 65 | return h.Action(actionName, null, routeValues, excludeFromParentCache); 66 | } 67 | 68 | /// 69 | /// Invokes the specified child action method using the specified parameters and controller name and returns the result as an HTML string. 70 | /// 71 | /// The HTML helper instance that this method extends. 72 | /// The name of the action method to invoke. 73 | /// The name of the controller that contains the action method. 74 | /// A flag that determines whether the action should be excluded from any parent cache. 75 | /// The child action result as an HTML string. 76 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, bool excludeFromParentCache) 77 | { 78 | return h.Action(actionName, controllerName, null, excludeFromParentCache); 79 | } 80 | 81 | /// 82 | /// Invokes the specified child action method using the specified parameters and controller name and returns the result as an HTML string. 83 | /// 84 | /// The HTML helper instance that this method extends. 85 | /// The name of the action method to invoke. 86 | /// The name of the controller that contains the action method. 87 | /// An object that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 88 | /// A flag that determines whether the action should be excluded from any parent cache. 89 | /// The child action result as an HTML string. 90 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, object routeValues, bool excludeFromParentCache) 91 | { 92 | return h.Action(actionName, controllerName, new RouteValueDictionary(routeValues), excludeFromParentCache); 93 | } 94 | 95 | /// 96 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 97 | /// 98 | /// The HTML helper instance that this method extends. 99 | /// The name of the child action method to invoke. 100 | /// A flag that determines whether the action should be excluded from any parent cache. 101 | 102 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, bool excludeFromParentCache) 103 | { 104 | RenderAction(h, actionName, null, null, excludeFromParentCache); 105 | } 106 | 107 | /// 108 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 109 | /// 110 | /// The HTML helper instance that this method extends. 111 | /// The name of the child action method to invoke. 112 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 113 | /// A flag that determines whether the action should be excluded from any parent cache. 114 | 115 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, object routeValues, bool excludeFromParentCache) 116 | { 117 | RenderAction(h, actionName, null, new RouteValueDictionary(routeValues), excludeFromParentCache); 118 | } 119 | 120 | /// 121 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 122 | /// 123 | /// The HTML helper instance that this method extends. 124 | /// The name of the child action method to invoke. 125 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 126 | /// A flag that determines whether the action should be excluded from any parent cache. 127 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, RouteValueDictionary routeValues, bool excludeFromParentCache) 128 | { 129 | RenderAction(h, actionName, null, routeValues, excludeFromParentCache); 130 | } 131 | 132 | /// 133 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 134 | /// 135 | /// The HTML helper instance that this method extends. 136 | /// The name of the child action method to invoke. 137 | /// The name of the controller that contains the action method. 138 | /// A flag that determines whether the action should be excluded from any parent cache. 139 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, bool excludeFromParentCache) 140 | { 141 | RenderAction(h, actionName, controllerName, null, excludeFromParentCache); 142 | } 143 | 144 | /// 145 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 146 | /// 147 | /// The HTML helper instance that this method extends. 148 | /// The name of the child action method to invoke. 149 | /// The name of the controller that contains the action method. 150 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 151 | /// A flag that determines whether the action should be excluded from any parent cache. 152 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, object routeValues, bool excludeFromParentCache) 153 | { 154 | RenderAction(h, actionName, controllerName, new RouteValueDictionary(routeValues), excludeFromParentCache); 155 | } 156 | 157 | /// 158 | /// Invokes the specified child action method using the specified parameters and controller name and renders the result inline in the parent view. 159 | /// 160 | /// The HTML helper instance that this method extends. 161 | /// The name of the child action method to invoke. 162 | /// The name of the controller that contains the action method. 163 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 164 | /// A flag that determines whether the action should be excluded from any parent cache. 165 | public static void RenderAction(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, RouteValueDictionary routeValues, bool excludeFromParentCache) 166 | { 167 | if (excludeFromParentCache) 168 | { 169 | var serialisedActionSettings = GetSerialisedActionSettings(actionName, controllerName, routeValues); 170 | 171 | h.ViewContext.Writer.Write("", serialisedActionSettings); 172 | } 173 | 174 | h.RenderAction(actionName, controllerName, routeValues); 175 | 176 | if (excludeFromParentCache) 177 | { 178 | h.ViewContext.Writer.Write(""); 179 | } 180 | } 181 | 182 | /// 183 | /// Invokes the specified child action method using the specified parameters and controller name and returns the result as an HTML string. 184 | /// 185 | /// The HTML helper instance that this method extends. 186 | /// The name of the action method to invoke. 187 | /// The name of the controller that contains the action method. 188 | /// A dictionary that contains the parameters for a route. You can use routeValues to provide the parameters that are bound to the action method parameters. The routeValues parameter is merged with the original route values and overrides them. 189 | /// A flag that determines whether the action should be excluded from any parent cache. 190 | /// The child action result as an HTML string. 191 | public static MvcHtmlString Action(this HtmlHelper h, [AspMvcAction] string actionName, [AspMvcController] string controllerName, RouteValueDictionary routeValues, bool excludeFromParentCache) 192 | { 193 | if (excludeFromParentCache) 194 | { 195 | var serialisedActionSettings = GetSerialisedActionSettings(actionName, controllerName, routeValues); 196 | 197 | return new MvcHtmlString(string.Format("{1}", serialisedActionSettings, h.Action(actionName, controllerName, routeValues))); 198 | } 199 | 200 | return h.Action(actionName, controllerName, routeValues); 201 | } 202 | 203 | private static string GetSerialisedActionSettings(string actionName, string controllerName, RouteValueDictionary routeValues) 204 | { 205 | var actionSettings = new ActionSettings 206 | { 207 | ActionName = actionName, 208 | ControllerName = controllerName, 209 | RouteValues = routeValues 210 | }; 211 | 212 | return Serialiser.Serialise(actionSettings); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /DevTrends.MvcDonutCaching.Demo/MvcDonutCaching.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {2C31E962-9616-4292-9DB6-52E40CB07E19} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | DevTrends.MvcDonutCaching.Demo 15 | DevTrends.MvcDonutCaching.Demo 16 | v4.5 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | 24 | ..\ 25 | true 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\ 32 | TRACE;DEBUG;PROFILE_DONUTS_CHILDACTION 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | pdbonly 39 | true 40 | bin\ 41 | TRACE 42 | prompt 43 | 4 44 | false 45 | 46 | 47 | 48 | False 49 | ..\packages\Antlr.3.5.0.2\lib\Antlr3.Runtime.dll 50 | 51 | 52 | False 53 | ..\packages\Artem.XmlProviders40.4.0\lib\Artem.Web.Security.dll 54 | 55 | 56 | False 57 | ..\packages\Artem.XmlProviders40.4.0\lib\Artem.Web.Security.Xml.dll 58 | 59 | 60 | False 61 | ..\packages\Autofac.3.3.1\lib\net40\Autofac.dll 62 | 63 | 64 | False 65 | ..\packages\Autofac.Mvc5.3.2.1\lib\net45\Autofac.Integration.Mvc.dll 66 | 67 | 68 | False 69 | ..\packages\Glimpse.AspNet.1.9.0\lib\net45\Glimpse.AspNet.dll 70 | 71 | 72 | False 73 | ..\packages\Glimpse.Autofac.0.2.4\lib\net40\Glimpse.Autofac.dll 74 | 75 | 76 | False 77 | ..\packages\Glimpse.1.8.5\lib\net45\Glimpse.Core.dll 78 | 79 | 80 | False 81 | ..\packages\Glimpse.Mvc5.1.5.3\lib\net45\Glimpse.Mvc5.dll 82 | 83 | 84 | 85 | True 86 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 87 | 88 | 89 | False 90 | ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | False 104 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.Helpers.dll 105 | 106 | 107 | True 108 | ..\packages\Microsoft.AspNet.Mvc.5.1.2\lib\net45\System.Web.Mvc.dll 109 | 110 | 111 | ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll 112 | True 113 | 114 | 115 | False 116 | ..\packages\Microsoft.AspNet.Razor.3.1.2\lib\net45\System.Web.Razor.dll 117 | 118 | 119 | 120 | False 121 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.dll 122 | 123 | 124 | False 125 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Deployment.dll 126 | 127 | 128 | False 129 | ..\packages\Microsoft.AspNet.WebPages.3.1.2\lib\net45\System.Web.WebPages.Razor.dll 130 | 131 | 132 | 133 | 134 | 135 | 136 | False 137 | ..\packages\WebGrease.1.6.0\lib\WebGrease.dll 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Global.asax 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | Designer 170 | 171 | 172 | Web.config 173 | 174 | 175 | Web.config 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | {854e90c7-8320-4eb6-a286-24a8ee5ebe9b} 193 | MvcDonutCaching 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Designer 227 | 228 | 229 | 230 | 10.0 231 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 232 | 233 | 234 | true 235 | bin\ 236 | TRACE;DEBUG;PROFILE_DONUTS_CHILDACTION 237 | full 238 | AnyCPU 239 | prompt 240 | MinimumRecommendedRules.ruleset 241 | false 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | True 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | Ce projet fait référence à des packages NuGet qui sont manquants sur cet ordinateur. Activez l'option de restauration des packages NuGet pour les télécharger. Pour plus d'informations, consultez http://go.microsoft.com/fwlink/?LinkID=322105. Le fichier manquant est le suivant : {0}. 262 | 263 | 264 | 265 | 271 | --------------------------------------------------------------------------------