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 |
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.
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.
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 |
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 |
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 |
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 | [](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