Skip to content

Commit 72841d3

Browse files
Added local cache viewer
1 parent 6e2604d commit 72841d3

File tree

12 files changed

+308
-308
lines changed

12 files changed

+308
-308
lines changed

DeveloperTools/Controllers/LocalObjectCacheController.cs

Lines changed: 0 additions & 134 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
@model EPiServer.DeveloperTools.Features.LocalObjectCache.LocalObjectCacheModel
2+
3+
<div class="epi-contentArea">
4+
<h1 class="EP-prefix">Local Object Cache</h1>
5+
<p>
6+
This tool shows all of the current items in the local object cache, and allows the deletion of one or more cached items.
7+
</p>
8+
</div>
9+
10+
<div class="epi-contentArea epi-formArea">
11+
@using (Html.BeginForm("Index", "LocalObjectCache", FormMethod.Post))
12+
{
13+
@Html.AntiForgeryToken()
14+
15+
<div>
16+
<span>Filter By</span>
17+
<span>@Html.DropDownListFor(m => m.FilteredBy, Model.Choices)</span>
18+
<span class="epi-cmsButton">
19+
<input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Refresh" type="submit" name="filter" id="filter" value="Filter" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)"/>
20+
</span>
21+
</div>
22+
}
23+
24+
<form action="" method="post">
25+
<div class="epi-contentArea">
26+
<p class="EP-systemInfo">Total count of cached items: @Model.CachedItems.Count()</p>
27+
</div>
28+
<div class="epi-buttonDefault">
29+
<span class="epi-cmsButton">
30+
<input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Delete" type="submit" name="RemoveLocalCache" id="RemoveLocalCache" value="Remove Local Cache Items" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)" />
31+
</span>
32+
<span class="epi-cmsButton">
33+
<input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-Delete" type="submit" name="removeLocalRemoteCache" id="removeLocalRemoteCache" value="Remove Local and Remote Cache Items" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)" />
34+
</span>
35+
<span class="epi-cmsButton">
36+
<input class="epi-cmsButton-text epi-cmsButton-tools epi-cmsButton-ViewMode" type="submit" formaction="ViewObjectSize" name="ViewObjectSize" id="ViewObjectSize" value="View Object Size" onmouseover="EPi.ToolButton.MouseDownHandler(this)" onmouseout="EPi.ToolButton.ResetMouseDownHandler(this)" />
37+
</span>
38+
</div>
39+
40+
<table class="table table-condensed table-bordered table-condensed stripe">
41+
<thead>
42+
<tr>
43+
<th><input type="checkbox" id="clearAll" name="clearAll" onClick="toggle(this)" value="true" /></th>
44+
<th class="table-column-width">Key</th>
45+
<th class="table-column-width">Type</th>
46+
<th>Size (bytes)</th>
47+
</tr>
48+
</thead>
49+
<tbody>
50+
@foreach (var item in Model.CachedItems)
51+
{
52+
<tr>
53+
<td class="center"><input type="checkbox" id="@item.Key" name="cacheKeys" value="@item.Key"/></td>
54+
<td>@item.Key</td>
55+
<td>@item.Value.GetType()</td>
56+
<td>@item.Size</td>
57+
</tr>
58+
}
59+
</tbody>
60+
</table>
61+
<input type="hidden" id="os" name="os" value="@Model.ViewObjectSize.ToString()" />
62+
</form>
63+
</div>
64+
65+
66+
@section AdditionalScripts {
67+
<script language="JavaScript">
68+
function toggle(source) {
69+
checkboxes = document.getElementsByName('cacheKeys');
70+
for (var i = 0, n = checkboxes.length; i < n; i++) {
71+
checkboxes[i].checked = source.checked;
72+
}
73+
}
74+
</script>
75+
}
76+
77+
@section AdditionalStyles {
78+
<style>
79+
.table-column-width {
80+
width: 30%;
81+
}
82+
83+
.stripe tbody tr:nth-child(even) {
84+
background-color: #f0f2f2;
85+
}
86+
</style>
87+
}

DeveloperTools/EPiServer.DeveloperTools.Views/Views/Shared/_ShellLayout.cshtml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
box-sizing: border-box;
2626
}
2727
</style>
28+
@await RenderSectionAsync("AdditionalStyles", false)
2829
</head>
2930
<body>
3031
@Html.AntiForgeryToken()
@@ -39,6 +40,6 @@
3940
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
4041
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
4142
@ClientResources.RenderResources("developertools", new[] { ClientResourceType.Script })
42-
@RenderSection("AdditionalScripts", false)
43+
@await RenderSectionAsync("AdditionalScripts", false)
4344
</body>
4445
</html>
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Reflection;
6+
using System.Runtime.Serialization.Formatters.Binary;
7+
using EPiServer.Core;
8+
using EPiServer.DataAbstraction;
9+
using EPiServer.DeveloperTools.Features.Common;
10+
using EPiServer.DeveloperTools.Infrastructure;
11+
using EPiServer.Framework.Cache;
12+
using Microsoft.AspNetCore.Mvc;
13+
using Microsoft.AspNetCore.Mvc.Rendering;
14+
using Microsoft.AspNetCore.Routing;
15+
using Microsoft.Extensions.Caching.Memory;
16+
17+
namespace EPiServer.DeveloperTools.Features.LocalObjectCache;
18+
19+
public class LocalObjectCacheController : DeveloperToolsController
20+
{
21+
private readonly ISynchronizedObjectInstanceCache _cache;
22+
private readonly IMemoryCache _memoryCache;
23+
24+
public LocalObjectCacheController(ISynchronizedObjectInstanceCache cache, IMemoryCache memoryCache)
25+
{
26+
_cache = cache;
27+
_memoryCache = memoryCache;
28+
}
29+
30+
[HttpGet]
31+
[HttpPost]
32+
public ActionResult Index(string filteredBy, bool os = false)
33+
{
34+
return View(PrepareViewModel(filteredBy, os));
35+
}
36+
37+
[HttpPost]
38+
public ActionResult RemoveLocalCache(string[] cacheKeys, bool os)
39+
{
40+
if (cacheKeys != null)
41+
{
42+
foreach (var key in cacheKeys)
43+
{
44+
_cache.RemoveLocal(key);
45+
}
46+
}
47+
48+
return RedirectToAction(nameof(Index), new RouteValueDictionary(new { os }));
49+
}
50+
51+
[HttpPost]
52+
public ActionResult RemoveLocalRemoteCache(string[] cacheKeys, bool os)
53+
{
54+
if (cacheKeys != null)
55+
{
56+
foreach (var key in cacheKeys)
57+
{
58+
_cache.RemoveLocal(key);
59+
_cache.RemoveRemote(key);
60+
}
61+
}
62+
63+
return RedirectToAction(nameof(Index), new RouteValueDictionary(new { os }));
64+
}
65+
66+
[HttpPost]
67+
public ActionResult ViewObjectSize()
68+
{
69+
return RedirectToAction(nameof(Index), new RouteValueDictionary(new { os = true }));
70+
}
71+
72+
private LocalObjectCacheModel PrepareViewModel(string filteredBy, bool viewObjectSize)
73+
{
74+
var model = new LocalObjectCacheModel();
75+
76+
// try to get in-memory cache object
77+
if (_memoryCache is MemoryCache memCache)
78+
{
79+
var entriesFieldInfo = memCache.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
80+
if (entriesFieldInfo != null && entriesFieldInfo.GetValue(memCache) is IDictionary entries)
81+
{
82+
model.CachedItems = ConvertToListItem(entries, viewObjectSize, filteredBy);
83+
}
84+
}
85+
86+
model.FilteredBy = filteredBy;
87+
model.Choices = new[]
88+
{
89+
new SelectListItem { Text = "All Cached Objects", Value = "all" },
90+
new SelectListItem { Text = "Any Content", Value = "content" },
91+
new SelectListItem { Text = "Pages Only", Value = "pages" },
92+
new SelectListItem { Text = "Property Definitions Only", Value = "properties" },
93+
};
94+
95+
model.ViewObjectSize = viewObjectSize;
96+
97+
return model;
98+
}
99+
100+
private IEnumerable<LocalObjectCacheModelItem> ConvertToListItem(
101+
IDictionary cachedEntries,
102+
bool viewObjectSize,
103+
string filteredBy)
104+
{
105+
var result = new List<LocalObjectCacheModelItem>();
106+
107+
foreach (var key in cachedEntries.Keys)
108+
{
109+
var value = cachedEntries[key].GetProperty("Value");
110+
111+
switch (filteredBy)
112+
{
113+
case "content":
114+
value = value as IContent;
115+
break;
116+
case "pages":
117+
value = value as PageData;
118+
break;
119+
case "properties":
120+
value = value as PropertyDefinition;
121+
break;
122+
}
123+
124+
if (value != null)
125+
{
126+
result.Add(
127+
new LocalObjectCacheModelItem
128+
{
129+
Key = key.ToString(), Value = value, Size = viewObjectSize ? GetObjectSize(value) : 0
130+
});
131+
}
132+
}
133+
134+
return result;
135+
}
136+
137+
private static long GetObjectSize(object obj)
138+
{
139+
if (obj == null)
140+
{
141+
return 0;
142+
}
143+
144+
try
145+
{
146+
using Stream s = new MemoryStream();
147+
var formatter = new BinaryFormatter();
148+
formatter.Serialize(s, obj);
149+
150+
return s.Length;
151+
}
152+
catch (Exception)
153+
{
154+
return -1;
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)