Tuesday, March 30, 2010

Thinking about static files

OK, this isn’t going to be a typical Sitecore article.  And this isn’t going to be a good code sample.  With those 2 caveats firmly in place…

In my position I often try to think through good examples to highlight possible solutions, rather than developing them in a fully-built-out, real world deliverable.  I’ll leave the expansion of this one (and the well-built code sample) to our fantastic Sitecore developers at our company and within our partner and client ranks.

In talking to many government and financial clients (this applies to many more, but those are two verticals where this is particularly important), the discussion often turns to auditing.  Not only auditing from an internal CMS-activity perspective (who changed which piece of content when, who approved what, etc.—Sitecore certainly has rich features around this activity), but from a web site visitor experience perspective.  What did I quote that person on Monday, October 12, 2009 at 2pm?  What was the banner ad in place when Customer Casey clicked through based on a multi-variate test I was performing?

With the possibilities of very different presentations to different visitors (made possible by Sitecore’s Online Marketing Suite Rules Engine), I might want to ensure we have a record of those various situations or I might want to see what they will look like ahead of time.

Now this is where developers might leave the article.  Again, this isn’t going to be a robust solution to the problem above.  This is demowear with a purpose.  The purpose is to start thinking about the real solution that could be put in place, and, along the way, to talk about a couple of entry points, events and hooks that Sitecore exposes which serve as great options to develop against.  As always, Sitecore provides the access to the entire CMS lifecycle.

There are a number of tools on the market that perform the function of crawling a dynamically generated site for the purposes of creating an “offline” or file-system version of the site.  One of these tools that some of our clients have used with success is HTTrack (http://www.httrack.com/) . 

I wanted to play around with this idea a bit in the Content Editor.  So I created a simple command button which would allow a content author to have any item in the tree highlighted and to create an HTML file based on that item’s default presentation:

image

Since I decided to place the command in the Preview chunk, the item in my Core database is at:

/sitecore/content/Applications/Content Editor/Ribbons/Chunks/Preview/CreateHTMLFile

The command item looks like this:

image

And the command is included in App_Config/Commands.config

image

So the command calls a simple class where we’ll request the response based on the default presentation for the item.  The assumption here is that the item is published and available at the default url based on its location in the content tree.

namespace Examples
{
public class UrlReaderWriter : Sitecore.Shell.Framework.Commands.Command
{
/// <summary>
///
This command will create an HTML file based on a request to the currently selected item as it is responded to from the public site (Web)
/// </summary>
/// <param name="context"></param>

public override void Execute(Sitecore.Shell.Framework.Commands.CommandContext context)
{

if (context.Items.Length == 1)
{
Sitecore.Data.Items.Item thisItem = context.Items[0];
Sitecore.Data.Database web = Sitecore.Data.Database.GetDatabase("web");
Sitecore.Data.Items.Item webItem = web.GetItem(thisItem.ID);

string itemUrl;
if (webItem != null)
{

itemUrl = Sitecore.Links.LinkManager.GetItemUrl(webItem);
string url = itemUrl.Replace("/sitecore/shell/", "/");
WriteFile(thisItem.Name, LoadSiteContents(url));
}
}


}

public static string LoadSiteContents(string url)
{

//create a new WebRequest object
WebRequest request = WebRequest.Create(url);

//create StreamReader to hold the returned request
StreamReader stream = new StreamReader(request.GetResponse().GetResponseStream());

//StringBuilder to hold info from the request
StringBuilder builder = new StringBuilder();

try
{
//now loop through the response
while (!(stream.Peek() == 0))
{
//now make sure we're not looking at a blank line
//if (stream.ReadLine().Length != null && stream.ReadLine().Length > 0) builder.Append(stream.ReadLine());
if (stream.ReadLine().Length > 0) builder.Append(stream.ReadLine());
}
}
catch { }

//close up the StreamReader
stream.Close();

//return the information
return builder.ToString();


}
public static void WriteFile(string itemName, string fileText)
{

// Write the string to a file.
System.IO.StreamWriter file = new System.IO.StreamWriter(HttpContext.Current.Server.MapPath("~/App_Data/" + itemName + ".html"));
file.WriteLine(fileText);
file.Close();
}

}
}



Some obvious things that this code needs:




  • A better read of John West’s Dynamic Links Document to avoid the string replace for the URL.


  • A better place to invoke this.  A button in Content Editor is OK….but better yet, this fits into the Publishing event to create files when necessary (based on data template, branch of the tree, etc.).


  • A better strategy than creating a WebRequest.  In reality, this would be interesting to build from the content in the Master db (get a snapshot of what is actually being published, maybe in contrast to what is currently in the Web db).


  • More variables (a drop-down added to the control button, for instance) where I can define the criteria for the presentation I’m trying to retrieve—device, OMS persona, security role, etc.



But, I told you this was a cheap piece of demoware…you’re up!