Skip to content

Commit 8d2ee9f

Browse files
author
DevTrends
committed
Initial release
0 parents  commit 8d2ee9f

File tree

7 files changed

+335
-0
lines changed

7 files changed

+335
-0
lines changed

DevTrends.MvcDonutCaching.sln

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 11.00
3+
# Visual Studio 2010
4+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevTrends.MvcDonutCaching", "DevTrends.MvcDonutCaching\DevTrends.MvcDonutCaching.csproj", "{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}"
5+
EndProject
6+
Global
7+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8+
Debug|Any CPU = Debug|Any CPU
9+
Release|Any CPU = Release|Any CPU
10+
EndGlobalSection
11+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
12+
{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13+
{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
14+
{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
15+
{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}.Release|Any CPU.Build.0 = Release|Any CPU
16+
EndGlobalSection
17+
GlobalSection(SolutionProperties) = preSolution
18+
HideSolutionNode = FALSE
19+
EndGlobalSection
20+
EndGlobal
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace DevTrends.MvcDonutCaching
2+
{
3+
public class ActionSettings
4+
{
5+
public string ActionName { get; set; }
6+
public string ControllerName { get; set; }
7+
public object RouteValues { get; set; }
8+
}
9+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6+
<ProductVersion>8.0.30703</ProductVersion>
7+
<SchemaVersion>2.0</SchemaVersion>
8+
<ProjectGuid>{854E90C7-8320-4EB6-A286-24A8EE5EBE9B}</ProjectGuid>
9+
<OutputType>Library</OutputType>
10+
<AppDesignerFolder>Properties</AppDesignerFolder>
11+
<RootNamespace>DevTrends.MvcDonutCaching</RootNamespace>
12+
<AssemblyName>DevTrends.MvcDonutCaching</AssemblyName>
13+
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
14+
<FileAlignment>512</FileAlignment>
15+
</PropertyGroup>
16+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
17+
<DebugSymbols>true</DebugSymbols>
18+
<DebugType>full</DebugType>
19+
<Optimize>false</Optimize>
20+
<OutputPath>bin\Debug\</OutputPath>
21+
<DefineConstants>DEBUG;TRACE</DefineConstants>
22+
<ErrorReport>prompt</ErrorReport>
23+
<WarningLevel>4</WarningLevel>
24+
</PropertyGroup>
25+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
26+
<DebugType>pdbonly</DebugType>
27+
<Optimize>true</Optimize>
28+
<OutputPath>bin\Release\</OutputPath>
29+
<DefineConstants>TRACE</DefineConstants>
30+
<ErrorReport>prompt</ErrorReport>
31+
<WarningLevel>4</WarningLevel>
32+
</PropertyGroup>
33+
<ItemGroup>
34+
<Reference Include="System" />
35+
<Reference Include="System.configuration" />
36+
<Reference Include="System.Core" />
37+
<Reference Include="System.Runtime.Caching" />
38+
<Reference Include="System.Web" />
39+
<Reference Include="System.Web.Extensions" />
40+
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
41+
<Reference Include="System.Xml.Linq" />
42+
<Reference Include="System.Data.DataSetExtensions" />
43+
<Reference Include="Microsoft.CSharp" />
44+
<Reference Include="System.Data" />
45+
<Reference Include="System.Xml" />
46+
</ItemGroup>
47+
<ItemGroup>
48+
<Compile Include="ActionSettings.cs" />
49+
<Compile Include="DonutOutputCacheAttribute.cs" />
50+
<Compile Include="HtmlHelperExtensions.cs" />
51+
<Compile Include="MemoryCacheProvider.cs" />
52+
<Compile Include="Properties\AssemblyInfo.cs" />
53+
</ItemGroup>
54+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
55+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
56+
Other similar extension points exist, see Microsoft.Common.targets.
57+
<Target Name="BeforeBuild">
58+
</Target>
59+
<Target Name="AfterBuild">
60+
</Target>
61+
-->
62+
</Project>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Configuration;
3+
using System.IO;
4+
using System.Text.RegularExpressions;
5+
using System.Web;
6+
using System.Web.Caching;
7+
using System.Web.Configuration;
8+
using System.Web.Mvc;
9+
using System.Web.Mvc.Html;
10+
using System.Web.Script.Serialization;
11+
12+
namespace DevTrends.MvcDonutCaching
13+
{
14+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
15+
public class DonutOutputCacheAttribute : ActionFilterAttribute
16+
{
17+
private const string AspnetInternalProviderName = "AspNetInternalProvider";
18+
private const string CacheKeyPrefix = "_d0nutc@che.";
19+
20+
private static readonly Regex DonutHoles = new Regex("<!--Donut#(.*)#-->", RegexOptions.Compiled);
21+
private static readonly bool IsCachingEnabled;
22+
private static readonly OutputCacheProvider Cache;
23+
24+
private string _key;
25+
26+
public int Duration { get; set; }
27+
public string VaryByParam { get; set; }
28+
public string CacheProfile { get; set; }
29+
30+
static DonutOutputCacheAttribute()
31+
{
32+
var outputCacheConfigSection = (OutputCacheSection)ConfigurationManager.GetSection("system.web/caching/outputCache");
33+
34+
if (outputCacheConfigSection.EnableOutputCache)
35+
{
36+
IsCachingEnabled = true;
37+
38+
if (outputCacheConfigSection.DefaultProviderName == AspnetInternalProviderName)
39+
{
40+
Cache = new MemoryCacheProvider();
41+
}
42+
else
43+
{
44+
var providerType = Type.GetType(outputCacheConfigSection.Providers[outputCacheConfigSection.DefaultProviderName].Type);
45+
Cache = (OutputCacheProvider) Activator.CreateInstance(providerType);
46+
}
47+
}
48+
}
49+
50+
public override void OnActionExecuting(ActionExecutingContext filterContext)
51+
{
52+
ConfigureCacheProfile();
53+
54+
if (IsCachingEnabled && filterContext.HttpContext.Request.Url != null)
55+
{
56+
//todo only VaryByParam = * is supported right now
57+
_key = string.Concat(CacheKeyPrefix, filterContext.HttpContext.Request.Url.PathAndQuery.ToLower());
58+
59+
var content = Cache.Get(_key) as string;
60+
61+
if (content != null)
62+
{
63+
filterContext.Result = new ContentResult { Content = ReplaceDonutHoleContent(content, filterContext) };
64+
}
65+
}
66+
}
67+
68+
public override void OnActionExecuted(ActionExecutedContext filterContext)
69+
{
70+
var viewResult = filterContext.Result as ViewResult;
71+
72+
if (viewResult != null)
73+
{
74+
using (var stringWriter = new StringWriter())
75+
{
76+
var viewName = String.IsNullOrEmpty(viewResult.ViewName)
77+
? filterContext.RouteData.GetRequiredString("action")
78+
: viewResult.ViewName;
79+
80+
viewResult.View = ViewEngines.Engines.FindView(filterContext, viewName, viewResult.MasterName).View;
81+
82+
var viewContext = new ViewContext(filterContext.Controller.ControllerContext, viewResult.View,
83+
viewResult.ViewData, viewResult.TempData, stringWriter);
84+
85+
viewResult.View.Render(viewContext, stringWriter);
86+
87+
var content = stringWriter.GetStringBuilder().ToString();
88+
89+
if (IsCachingEnabled)
90+
{
91+
Cache.Add(_key, content, DateTime.Now.AddSeconds(Duration));
92+
}
93+
94+
filterContext.Result = new ContentResult { Content = ReplaceDonutHoleContent(content, filterContext) };
95+
}
96+
}
97+
}
98+
99+
private void ConfigureCacheProfile()
100+
{
101+
if (IsCachingEnabled)
102+
{
103+
if (!string.IsNullOrEmpty(CacheProfile))
104+
{
105+
var outputCacheSettingsConfigSection = (OutputCacheSettingsSection)ConfigurationManager.GetSection("system.web/caching/outputCacheSettings");
106+
107+
if (outputCacheSettingsConfigSection != null && outputCacheSettingsConfigSection.OutputCacheProfiles.Count > 0)
108+
{
109+
var cacheProfile = outputCacheSettingsConfigSection.OutputCacheProfiles[CacheProfile];
110+
111+
if (cacheProfile != null)
112+
{
113+
Duration = cacheProfile.Duration;
114+
VaryByParam = cacheProfile.VaryByParam;
115+
return;
116+
}
117+
}
118+
119+
throw new HttpException(string.Format("The '{0}' cache profile is not defined. Please define it in the configuration file.", CacheProfile));
120+
}
121+
}
122+
}
123+
124+
private static string ReplaceDonutHoleContent(string content, ControllerContext filterContext)
125+
{
126+
return DonutHoles.Replace(content, new MatchEvaluator(match =>
127+
{
128+
var actionSettings = new JavaScriptSerializer().Deserialize<ActionSettings>(match.Groups[1].Value);
129+
130+
return InvokeAction(filterContext.Controller, actionSettings.ActionName, actionSettings.ControllerName, actionSettings.RouteValues);
131+
}));
132+
}
133+
134+
private static string InvokeAction(ControllerBase controller, string actionName, string controllerName, object routeValues)
135+
{
136+
var viewContext = new ViewContext(controller.ControllerContext, new WebFormView(controller.ControllerContext, "tmp"),
137+
controller.ViewData, controller.TempData, TextWriter.Null);
138+
139+
var htmlHelper = new HtmlHelper(viewContext, new ViewPage());
140+
141+
return htmlHelper.Action(actionName, controllerName, routeValues).ToString();
142+
}
143+
}
144+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Web.Mvc;
2+
using System.Web.Mvc.Html;
3+
using System.Web.Routing;
4+
using System.Web.Script.Serialization;
5+
6+
namespace DevTrends.MvcDonutCaching
7+
{
8+
public static class HtmlHelperExtensions
9+
{
10+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, bool excludeFromParentCache)
11+
{
12+
return html.Action(actionName, null, null, excludeFromParentCache);
13+
}
14+
15+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, object routeValues, bool excludeFromParentCache)
16+
{
17+
return html.Action(actionName, null, new RouteValueDictionary(routeValues), excludeFromParentCache);
18+
}
19+
20+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, RouteValueDictionary routeValues, bool excludeFromParentCache)
21+
{
22+
return html.Action(actionName, null, routeValues, excludeFromParentCache);
23+
}
24+
25+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, string controllerName, bool excludeFromParentCache)
26+
{
27+
return html.Action(actionName, controllerName, null, excludeFromParentCache);
28+
}
29+
30+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, string controllerName, object routeValues, bool excludeFromParentCache)
31+
{
32+
return html.Action(actionName, controllerName, new RouteValueDictionary(routeValues), excludeFromParentCache);
33+
}
34+
35+
public static MvcHtmlString Action(this HtmlHelper html, string actionName, string controllerName, RouteValueDictionary routeValues, bool excludeFromParentCache)
36+
{
37+
if (excludeFromParentCache)
38+
{
39+
var actionSettings = new ActionSettings
40+
{
41+
ActionName = actionName,
42+
ControllerName = controllerName,
43+
RouteValues = routeValues
44+
};
45+
46+
var serialisedActionSettings = new JavaScriptSerializer().Serialize(actionSettings);
47+
48+
return new MvcHtmlString(string.Format("<!--Donut#{0}#-->", serialisedActionSettings));
49+
}
50+
51+
return html.Action(actionName, controllerName, routeValues);
52+
}
53+
}
54+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Runtime.Caching;
3+
using System.Web.Caching;
4+
5+
namespace DevTrends.MvcDonutCaching
6+
{
7+
public class MemoryCacheProvider : OutputCacheProvider
8+
{
9+
private static readonly ObjectCache Cache = MemoryCache.Default;
10+
11+
public override object Add(string key, object entry, DateTime utcExpiry)
12+
{
13+
return Cache.AddOrGetExisting(key, entry, utcExpiry);
14+
}
15+
16+
public override object Get(string key)
17+
{
18+
return Cache.Get(key);
19+
}
20+
21+
public override void Remove(string key)
22+
{
23+
Cache.Remove(key);
24+
}
25+
26+
public override void Set(string key, object entry, DateTime utcExpiry)
27+
{
28+
Cache.Set(key, entry, utcExpiry);
29+
}
30+
}
31+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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 © DevTrends 2011")]
10+
[assembly: AssemblyTrademark("")]
11+
[assembly: AssemblyCulture("")]
12+
[assembly: ComVisible(false)]
13+
[assembly: Guid("ebcc3291-f04a-4511-b7eb-ddf57a74ada9")]
14+
[assembly: AssemblyVersion("0.9.0.0")]
15+
[assembly: AssemblyFileVersion("0.9.0.0")]

0 commit comments

Comments
 (0)