@ClientCache.FixedSection("_FixedMarkupTest")
@ClientCache.CssFile("/Content/Site.css")
@ClientCache.ScriptFile("/Scripts/SimpleAlert.js")
The idea is a SPA based one where you expect to have a lot of fixed sections and "data binding" happens on the client where these fixed sections are bound to JSON in dynamic sections or retrieved through JavaScript.
Some stuff still to do:
- Make the JavaScript less dependent on external libraries.
- Handle when a cached section is missing by created a handler that can fetch the required section and serve a Ajax call
- Change JavaScript to handle the case where there is no local storage because some browsers don't have localStorage (although the creators of said browsers have advised against their use...)
- Build versioning system so cache can be update with latest version
- Turn into a Nuget package
Below is the server side code.
public enum SectionType
{
Css,JavaScript,Markup
}
public class ClientCache
{
public const string InCacheElement = "clientcache";
public const string CanBeCachedElement = "cacheable";
public const string ClientCacheIdentifier = "cacheid";
private string[] _cacheList;
private HtmlHelper _htmlHelper;
public const string CacheCookieName = "ClientCache";
public const char Divider = '*';
public ClientCache(HtmlHelper htmlHelper)
{
_htmlHelper = htmlHelper;
}
private string[] ClientCacheIds
{
get
{
if (_cacheList == null)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies[CacheCookieName];
_cacheList = cookie != null
? HttpUtility.UrlDecode(cookie.Value).Split(new[] { Divider })
: new string[0];
}
return _cacheList;
}
}
private string GenerateId(SectionType type, string name)
{
return type.ToString() + name;
}
private bool OnClient(string id)
{
return ClientCacheIds.Contains(id);
}
private MvcHtmlString GenerateCachingMarkup(SectionType type, string identifier)
{
string sectionId = GenerateId(type, identifier);
StringWriter stringWriter = new StringWriter();
using (var writer = new HtmlTextWriter(stringWriter))
{
bool cached = OnClient(sectionId);
writer.AddAttribute(ClientCacheIdentifier, sectionId);
writer.RenderBeginTag(cached ? InCacheElement : CanBeCachedElement);
if (!cached)
{
switch (type)
{
case SectionType.Markup:
writer.Write(_htmlHelper.Partial(identifier));
break;
case SectionType.JavaScript:
writer.AddAttribute("type", "text/javascript");
writer.RenderBeginTag(HtmlTextWriterTag.Script);
writer.Write(GetFileContents(identifier));
writer.RenderEndTag();
break;
case SectionType.Css:
writer.RenderBeginTag(HtmlTextWriterTag.Style);
writer.Write(GetFileContents(identifier));
writer.RenderEndTag();
break;
}
}
writer.RenderEndTag();
}
return new MvcHtmlString(stringWriter.ToString());
}
private string GetFileContents(string path)
{
string fileName = HttpRuntime.AppDomainAppPath + path;
return File.ReadAllText(fileName);
}
// The intention of this is to surround or replace an included section
// FixedSection does not have all the sigantures of Partial since it is not intended to take a model
public MvcHtmlString FixedSection(string partialViewName)
{
return GenerateCachingMarkup(SectionType.Markup, partialViewName);
}
public MvcHtmlString CssFile(string cssFileName)
{
return GenerateCachingMarkup(SectionType.Css, cssFileName);
}
public MvcHtmlString ScriptFile(string scriptFileName)
{
return GenerateCachingMarkup(SectionType.JavaScript, scriptFileName);
}
}
And now the client code.
$(document).ready(function () {
// 1 get all cacheable sections and store in web storage
$("cacheable").each(function () {
var key = $(this).attr("cacheId");
var value = $(this).html();
localStorage.setItem(key, value);
var myCookie = $.cookie("ClientCache");
myCookie = myCookie ? myCookie + "*" + key : key;
$.cookie("ClientCache", myCookie, { expires: 365 });
});
// 2
$("clientcache").each(function () {
var key = $(this).attr("cacheId");
var value = localStorage.getItem(key);
$(this).html(value);
});
});
No comments:
Post a Comment