We resume our saga from
the previous chapter, where the three amigos are going through some growing pains in coding the
Autosave for DasBlog 1.9...
I think EDMUG started somewhat like thisIn the constructor to the ajaxdelegate, we store all of the values in an Array (which will be passed to the callback object).
this.callbackArguments = new Array();
this.callbackArguments[URL] = url;
this.callbackArguments[CALLBACK] = callback;
this.callbackArguments[ENTRYID] = entryId;
this.callbackArguments[TITLE] = title.replace(/]]>/g, "]]>");
this.callbackArguments[AUTHOR] = author;
this.callbackArguments[POSTTEXT] = postText.replace(/]]>/, "]]>");
As Jesus points out, later on in our Fetch command we're constructing a small piece of XML data like so:
var xmlSubmit = "<savedpostdata><entryid>"+callbackArguments[ENTRYID]+"</entryid>";
xmlSubmit += "<title><![CDATA["+callbackArguments[TITLE]+"]]></title>";
xmlSubmit += "<author>"+callbackArguments[AUTHOR]+"</author>";
xmlSubmit += "<posttext><![CDATA["+callbackArguments[POSTTEXT]+"]]></posttext>";
xmlSubmit += "</savedpostdata>";
request.open("POST", this.url, true);
request.setRequestHeader('Content-Type', 'text/xml');
request.send(xmlSubmit);
You might be thinking that this isn't a big deal, but the XML parser that needs to read this data will *break* on any occurence of the character (of which there will be many, unless you like writing your posts without any spaces). That's just one potential problem Jesus and Denzel are avoiding by wrapping these values in a CDATA tag.
Now let's get back to that weird-@$$ed callbackArguments initialization code. There is still one way the XML parser can break: if we insert a "]]>" character sequence somewhere in the text of either the title or the HTML content of the post text itself. You see, CDATA tags are bordered with "<!CDATA[" on the left side, and "]]>" on the right side. If we threw an extra "]]>" in the middle of this, we'd end the CDATA segment early and pass malformed XML to the AutoSaveEntry handler. Thus, we use a regex that looks for any instance of "]]>" and escapes it to "]]>", > being the escape sequence for the right angle bracket (greater than). While it's very likely that the user is only typing this sequence when making a typo or a deliberate attempt to break the system, it's better for us to be safe than sorry.
In the request, we set our request method to "POST" (as we really don't want to be shoving this in the query string), and set our 'content-type' to 'text/xml', so that the request knows what format this should be sent in. And then we are off!!!
[Before we go on, the exciting part of something like this is that you can actually accomplish the remainder of our auto-save with *any* sort of server-side language that allows some sort of writes to storage (read: pretty much all of them). DasBlog uses XML to store its entries, but you could just as easily use PHP, Java, or whatnot to read our XML and store it somewhere. The remainder of this text goes into our ASP.NET HttpHandler specific solution.]
Jesus Christ pwns the n00bsAfter this exchange, the group bands together and whips up a brief HttpHandler that would take incoming requests and use those requests to save data. The first step: to define a new class called AutoSaveHandler, that implements the IHttpHandler interface. After this, they put an entry in the web.config to ensure that any requests to autosaveEntry.ashx were handled like so:
<add verb="*" path="autoSaveEntry.ashx" type="newtelligence.DasBlog.Web.Services.AutoSaveEntryHandler, newtelligence.DasBlog.Web.Services" />
Now we didn't get any failures when hitting "autosaveentry.ashx", but it still isn't doing anything. So they add some simple code to the ProcessRequest() method.
First, we read the XML data received from the AjaxDelegate. We use an XmlTextReader since it's less overhead than the XmlDocument class, and we only need forward read-access anyway.
System.Xml.XmlTextReader xtr = new System.Xml.XmlTextReader(context.Request.InputStream);
try
{
while (!xtr.EOF)
{
xtr.Read();
if (xtr.Name=="entryid")
{
entryId = xtr.ReadInnerXml();
}
if (xtr.Name=="author")
{
author = xtr.ReadInnerXml();
}
if (xtr.Name=="title" && xtr.NodeType == System.Xml.XmlNodeType.Element)
{
xtr.Read(); // Brings us to the CDATA inside "title"
title = xtr.Value;
}
if (xtr.Name=="posttext" && xtr.NodeType == System.Xml.XmlNodeType.Element)
{
xtr.Read();
textToSave = xtr.Value;
}
}
}
finally
{
xtr.Close();
}
Once we've read all of these values into their respective variables, we save our entry, provided that our entryId value isn't null or empty. There are two cases we need to take care of here. If this is a new entry, we need to create a brand new entry object, assign it our parameters, and call the SaveEntry() method of the DataService. If it's a previously edited/autosaved entry, we get the existing entry, change its Modified time, and save it.
Entry entry = dataService.GetEntry(entryId);
if ( entry != null )
{
entry = dataService.GetEntryForEdit( entryId );
Entry modifiedEntry = entry.Clone();
modifiedEntry.Content = textToSave;
modifiedEntry.Title = title;
modifiedEntry.Author = author;
modifiedEntry.Syndicated = false;
modifiedEntry.IsPublic = false;
modifiedEntry.ModifiedUtc = DateTime.Now.ToUniversalTime();
modifiedEntry.Categories = "";
dataService.SaveEntry(modifiedEntry, null);
}
else // similar code, but creating a new entry instead.
Whew!! With this in place, we now take a large circle back to our Javascript callback function AutoSaveResult() (mentioned in
part 1), which does absolutely nothing but write a small note to the label we inserted under the textbox, saying "Post auto-saved: [current date and time]". Two notes here:
the "post auto-saved" part is actually localizable; we're grabbing it from a variable called autoSaveText which has been set to the value in our .NET localization tables like so:
string autoSaveText = resmgr.GetString("text_autosave");
sb.Append("function AutoSaveResult(url, response) " + "\n");
sb.Append("{ \n");
sb.Append("var dt = new Date(); \n");
sb.Append("document.getElementById('" + this.autoSaveLabel.ClientID + "').innerHTML = '" + autoSaveText + " ' + dt.toLocaleString(); \n");
sb.Append("} \n");
sb.Append("</script> \n");
With all of this in place, the Autosave is ready to run!! Thanks to the triumphant trio for figuring this out! The sad truth, however, is that due to the conspiracy of silence surrounding
DasBlog, I don't think it'd likely that we'll see a
"Thanks to Spider-Man" from
Scott Hanselman anytime soon. But our group has moved on to other projects, as you can see...
And so great teams are born...