In the previous section, we
have seen how Site Stager works. It is a good tool for creating static
snapshots of ASP-based sites. Obviously, we don’t want to take a huge
step back and convert all our templates from ASP.NET to ASP. How, then,
shall we stage sites that make use of the newer and improved ASP.NET? To
get around this limitation of Site Stager, we will build our very own
version of the staging tool. The good news is that we won’t start
entirely from scratch: we will borrow some of the concepts and ideas
behind Site Stager.
Let’s build a
console application that mimics what Site Stager does for MCMS 2001 or
ASP-based templates in MCMS 2002. Note that the application uses the
MCMS PAPI and will need to be executed on a server that has MCMS
installed.
Here’s what the application will do:
Walk
through the entire channel structure, starting from a specified start
channel. For each posting and channel cover page found, a static HTML
file is generated.
Scan through each channel cover page and posting for attachments and stage them.
Provide
settings for administrators to change the behavior of the stager. For
example, administrators can specify the start channel, whether or not
hidden postings will be staged, and so on.
We will make the
assumption that the staged pages will be placed in the same folder
structure as found in Site Manager. For relative links within pages to
remain unbroken, the pages should not be nested within sub folders. For
example, http://tropicalgreen1 can be staged to http://tropicalgreen2 but not to http://servername/tropicalgreen3/.
The DotNetSiteStager Project
Let’s start by creating a Visual C# Console Application project named DotNetSiteStager. Rename Class1.cs to Stager.cs.
Add the following references to the project:
The following namespaces are also required by the solution. Add them above the namespace declaration:
using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;
using Microsoft.ContentManagement.Publishing;
namespace DotNetSiteStager
{
. . . code continues . . .
}
Configuring Stager Settings
The
.NET stager will provide several configurable settings as shown in the
following table. Notice that some settings are equivalent to fields
available in Site Stager. We did say that we were going to borrow some
of Site Stager’s ideas!
Setting | Description | Value for Example |
---|
m_DestinationDirectory | Specifies
the folder where we will create all staged folders and files. This
could be a location on the source server itself, or a directory on a
remote server in the network.
Equivalent to the Destination Directory field of Site Stager. | C:\StagedSite\ |
m_StartChannel | The
channel that the stager will begin staging from. You can change the
start channel to point to any channel within the hierarchy, allowing you
to increase or reduce the scope of the stager accordingly. | /Channels/www.tropicalgreen.net/ |
m_LogFile | The path of the file that the stager will use to write all warnings and error messages to. | C:\StagedSite\logs\ |
m_SourceHost | The
name of the host server. As the stager must be executed on the source
MCMS server, the value is typically http://localhost. But for sites with
map channel names to host header turned on, you can specify the host
name instead (e.g. http://www.tropicalgreen.net). | http://www.tropicalgreen.net |
m_UserAgent | Allows
you to specify the value of the UserAgent HTTP header, which identifies
the browser version and platform of the client. This accommodates sites
that are rendered differently for different client platforms or
browsers. | Mozilla/4.0 (compatible MSIE 6.0 Windows NT 5.1) |
m_StageAsUser | The
account that will be used to retrieve all MCMS objects from the
repository. Restricting the access rights of the ‘Stage As’ user will
limit the stager’s scope.
Equivalent to the Stage As User field of Site Stager, minus the
domain name, which is defined separately here. | StagerUser |
m_StageAsPwd | The password of the ‘Stage As’ user account.
Equivalent to the User Password field of Site Stager. | StagerPassword |
m_StageAsDomain | The domain of the ‘Stage As’ user account. | localmachine |
m_DoNotExportHiddenItems | A flag to indicate whether or not hidden files and channels will be staged as well. A value of false stages hidden files. | True |
m_DefaultFileName | The name of the file generated for channel rendering scripts and default postings.
Equivalent to the Default Filename field in Site Stager. | default |
m_DefaultFileExtension | The file extension of all generated postings.
Equivalent to the Default Extension for HTML files field in Site Stager. | htm |
m_HttpHeader | This
is an additional header that will be added to the request to allow the
application to behave differently if the site is hit by the stager or by
a browser. For example, you could replace active scripts that work only
on dynamic pages with content that will work on a static copy. | DotNetSiteStager |
m_CodePage | The
default encoding used to download a static version of each page. If
your site uses different character sets (for instance, if it is a
multilingual site), you may define a different code page. | UTF-8 |
m_BaseUrl | The RobotMetaTag inserted in all template files injects a <base>
tag into all postings. If you are planning to stage the files to a
different host from the source, such as when generating files from http://www.tropicalgreen.net to http://tropicalgreen, you could set this value to http://tropicalgreen. | http://tropicalgreen |
As
the behavior of the methods within the class depends on the values of
these settings, let’s declare all of them as class variables. We will
also declare an ArrayList object for storing attachment URLs, a CmsApplicationContext variable for working with the MCMS PAPI and a NetworkCredential object for downloading pages later. Add the following code above the Main() method:
private static string m_LogFile;
private static string m_StageAsUser;
private static string m_StageAsPwd;
private static string m_StageAsDomain;
private static bool m_DoNotExportHiddenItems;
private static string m_StartChannel;
private static string m_SourceHost;
private static string m_UserAgent;
private static string m_HttpHeader;
private static string m_DefaultFileName;
private static string m_DestinationDirectory;
private static string m_DefaultFileExtension;
private static string m_CodePage;
private static string m_BaseUrl;
private static string m_LocalBaseUrl;
// ArrayLists for handling attachments
private static ArrayList m_AttachmentUrls;
// CmsApplicationContext for working with the MCMS PAPI
private static CmsApplicationContext cmsContext;
// Network credentials for downloading pages
private static NetworkCredential m_credentials;
[STAThread]
static void Main(string[] args)
{
. . . code continues . . .
}
Our application will handle two types of files:
ContentPage: Postings and channel rendering scripts
ContentBinary: Images, Office documents, and other attachments
We will declare an enumeration named EnumBinary to identify each of these file types. Add the following code above the Main() routine.
// Have enum values for each file type handled
enum EnumBinary
{
ContentBinary,
ContentPage
}
Within the Main()
routine, we will initialize each of the variables declared above. Go
ahead and change their values. You will at the very least have to set
the ‘Stage As’ user, domain, and password as well as the start channel
and the destination directory to match the environment of your computer.
[STAThread]
static void Main(string[] args)
{
// Set parameter values
m_LogFile = @"c:\StagedSite\logs\";
m_StageAsDomain = "localmachine";
m_StageAsUser = "StagerUser";
m_StageAsPwd = "StagerPassword";
m_DoNotExportHiddenItems = true;
m_StartChannel = "/Channels/www.tropicalgreen.net/";
m_SourceHost = "http://www.tropicalgreen.net";
m_UserAgent = "Mozilla/4.0 (compatible MSIE 6.0 Windows NT 5.1)";
m_HttpHeader = "DotNetSiteStager";
m_DefaultFileName = "default.htm";
m_DestinationDirectory = @"c:\StagedSite\";
m_DefaultFileExtension = "htm";
m_CodePage = "UTF-8";
m_BaseUrl = "http://tropicalgreen";
m_LocalBaseUrl = m_BaseUrl;
m_AttachmentUrls = new ArrayList();
}
A better idea would be to store the settings within an Application configuration file (app.config).
In this way, you don’t have to re-compile the code each time any of the
settings changes. For simplicity, we will leave them as class variables
in this example.
Recording Messages to a Log File
As the stager
progresses through its job, it may encounter exceptions when staging
certain URLs. Instead of terminating the entire process, we will trap
all exceptions and write all error messages to a log file. The location
of the log file is set in the m_LogFile variable defined earlier.
The helper function that writes messages to the log file is shown below. Add it directly below the Main() method.
private static void WriteToLog(string message)
{
Console.WriteLine(message);
FileStream fsLog = new FileStream(m_LogFile, FileMode.Append,
FileAccess.Write);
StreamWriter wLog = new StreamWriter(fsLog);
wLog.WriteLine(message);
wLog.Flush();
wLog.Close();
fsLog.Close();
}
For audit purposes, we
will record the current date and time as well as the settings that we
defined earlier in the log file. Within the Main() routine, add the following code:
[STAThread]
static void Main(string[] args)
{
. . . code continues . . .
// Ensure that the logfile directory is created
Directory.CreateDirectory(m_LogFile);
// Generate the name of the log file (in the format log_yyyyMMdd_HHmm.txt)
if(!m_LogFile.EndsWith("\\"))
{
m_LogFile = m_LogFile + "\\";
}
m_LogFile = m_LogFile + "log_" + DateTime.Now.ToString("yyyyMMdd_HHmm")
+ ".txt";
System.Text.StringBuilder sb = new System.Text.StringBuilder();
string newLine = System.Environment.NewLine;
// Write the timestamp to the log file
sb.Append(DateTime.Now.ToString("dd MMM yyyy HH:mm") + newLine);
sb.Append("----------" + newLine);
// Write the settings to the log file
sb.Append("Log File = " + m_LogFile + newLine);
sb.Append("Stage As Domain = " + m_StageAsDomain + newLine);
sb.Append("Stage As User = " + m_StageAsUser + newLine);
sb.Append("Do No Export Hidden Items = " + m_DoNotExportHiddenItems
+ newLine);
sb.Append("Start Channel = " + m_StartChannel + newLine);
sb.Append("HTTP Host = " + m_SourceHost + newLine);
sb.Append("User Agent = " + m_UserAgent + newLine);
sb.Append("HTTP Header = " + m_HttpHeader + newLine);
sb.Append("Default File Name = " + m_DefaultFileName + newLine);
sb.Append("Destination Directory = " + m_DestinationDirectory + newLine);
sb.Append("Default File Extension = " + m_DefaultFileExtension + newLine);
sb.Append("Base Url = " + m_BaseUrl + newLine);
sb.Append("Code Page = " + m_CodePage + newLine);
WriteToLog(sb.ToString());
}