GOAL
I recently wanted a portal/sites wide footer for my SharePoint installation. I wanted this to affect the SPS portal and every WSS site without my needing to modify any of the .aspx pages that come with SharePoint.
RESEARCH
I found a great blog article on creating a footer with various means.
danielmcpherson - How To Create A Footer
Read the article and you may come to the same conclusion that the "3. DHTML and Stylesheet" solution is the most compelling, requiring no changes other than style sheet. Unfortunately this technique only works on IE due to HTC.
This effort underway which may help, but I was unable to graft into my scenario:
Scott Galloway - Use HTC within Mozilla!
I was convinced that the DOM script within the HTC was going to ultimately cause an incompatibility, so I scrapped the stylesheet-only idea.
SOLUTION
I ultimately created an HttpModule that I plugged into the site to put a footer on every dynamic page in the virtual directory. This "filter" would look for a </body> tag within any HTML content before it sent it to the user, and inserts my footer code. To make it more "style-sheet like," I decided to insert a javascript reference so that the content of the footer could be maintained by a separate file.
The trick is to create an IHttpModule implementation that installs a customized Stream filter to capture, watch and manipulate the text. Unfortunately stream needs to implement Stream and all it's methods so that you can override the Stream.Write(byte[] data, int offset, int count) as well as optionally Stream.Close(), Stream.Flush() or anything else you want.
I chose to capture all data pushed into Stream.Write(), and modify/output it only on Stream.Close(), with special attention on the destructor to make sure Stream.Close() was actually called.
Interesting excerpts:
public class MyFooterModule : IHttpModule
{
public MyFooterModule() {}
public string ModuleName { get { return "MyFooterModule"; } }
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
private void context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
context.Response.Filter = new MyFooterFilter(context.Response.Filter, context);
}
public void Dispose() {}
}
public class FooterFilter : Stream {
public const string FOOTER_HTML = "<script src=\"/Elements/Footer.js\"></script>";
public const string BEFORE_REGEX = @"</body>\s*</html>";
protected byte[] mycache = null;
public override void Close() {
if (mycache != null)
{
string buffer = System.Text.UTF8Encoding.UTF8.GetString(mycache, 0, mycache.Length);
Regex regex = new Regex(BEFORE_REGEX, RegexOptions.IgnoreCase);
Match match = regex.Match(buffer);
if ((match != null) && match.Success)
{
StringBuilder newBuffer = new StringBuilder(buffer.Length + FOOTER_HTML.Length);
newBuffer.Append(buffer.Substring(0, match.Index;));
newBuffer.Append(FOOTER_HTML);
newBuffer.Append(buffer.Substring(match.Index;));
mycache = MakeByteArrayFromString(newBuffer.ToString());
}
BaseStream.Write(mycache, 0, mycache.Length);
}
base.Close();
}
}
When you get to the installation, consider signing the assembly, and NOT putting it into the GAC, or resorting to setting web.config to Full trust. In the meantime, do whatever it takes to make the following line work in your web.config file:
<add name="FooterModule" type="MyNameSpace,myassembly"/>
</httpModules>
You will have to put the assembly in the site's bin directory. Also put it into the layouts bin directory if you want to have the footer appear in the _layouts/1033/*.aspx pages.
By installing this footer module into the default web site's web.config file, every portal and site .aspx page will be affected. Making the same modification to the layouts virtual directory will add the footer to the _layouts/1033/*.aspx pages, but some will look undesirable such as the rich text editor. As far as I've found, no pages actually break using this technique the the provided regular expression during content manipulation.
I heard that this type of filtering does not properly adjust the Content-Length header, which should be a problem, but I have not seen any ill effects yet.
This was an old post on MindsharpBlogs that unfortunately is not more online.
Labels: moss 2007, moss sharepoint 2007, Sharepoint, Sharepoint 2007