AAron nAAs

All MindsharpBlogs

AAron's SharePoint notepad

My Links

Archives

Blog Stats

My Sites

Adding a SharePoint footer (globally)

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.

Enjoy,

-AAron

NOTE: All source code provided in this article is intended to provide useful tips on achieving the stated goals. The provided code is not intended to be functional code as is. To get this code working, the reader is expected to finish out the implementation, which may still involve a significant amount of work.

posted on Wednesday, April 27, 2005 8:53 PM

Feedback

# re: Adding a SharePoint footer (globally) 4/28/2005 10:20 AM Todd Bleeker

Fabulous post Aaron!!!

<Todd />

# re: Adding a SharePoint footer (globally) 5/5/2005 4:40 PM John McGee

wow, what I wouldn't give for a simple server side include. All I want is a copyright notice on all WSS pages, and I'd be more than happy to add the #include directive manually to all pages. I can't believe how much more difficult this has become with the loss of SSI's.

# Adding a SharePoint Clickwrap (globally) 6/17/2005 1:55 PM AAron nAAs

# re: Adding a SharePoint footer (globally) 12/14/2005 6:33 AM Mark Deraeve

There is one downside in using a httpmodule.
The BeginRequest event is not called on every site of the portal. Eg. portalname/_layouts/1043/spsviewlsts.aspx

I was trying to use the httpmodule for something else when I noticed this.

# re: Adding a SharePoint footer (globally) 12/14/2005 8:06 AM AAron nAAs

Mark, In my experience it will be called if you add the HttpModule to the web.config of the virtual directory. Look for the right web.config in the 60 hive.

Due to strange behavior of the footer in certain pages in the _layouts dir, I opted NOT to add that footer to them. For example, the rich text editor page would get it too :-/

# re: Adding a SharePoint footer (globally) 3/3/2006 3:19 AM Serge van den Oever [Macw]

AAron, What you could do is use the MacwSharePointSkinner, a HTTPModule that can be used to do exactly this, and make advanced rules to exclude for example the rich text editor page.

Have a look at my weblog for more information. See also the post http://weblogs.asp.net/soever/archive/2006/01/12/435105.aspx

# re: Adding a SharePoint footer (globally) 3/3/2006 8:44 AM AAron nAAs

Serge, great suggestion. Your tool is definately the the mature version of what I discussed. Very nice to see such a configurable and robust product to fill this need.

The regular expressions are horrible to maintain, but that is going to happen whether its done my one-off way or with your config file. Advantage is your config file eliminates "code" changes :-)

I started to wonder if I could remove all my aspx file mods and solely use your tool, but I added certain web controls to those pages that must run within the context of the page. Maybe if I managed to make them all web parts and put them automatically into page web part zones... then by using MacwSharePointSkinner maybe I wouldn't need to make any page mods!

Thanks for the info.

# re: Adding a SharePoint footer (globally) 3/3/2006 11:50 AM Serge van den Oever [Macaw]

Hi AAron, problem with the skinner is that it only skins HTML just before it is sent out to the browser. It is not possible to add server controls before rendering. Please let me know if you get things working using the skinner!

Serge

Title  
Name  
Url
CAPTCHA
Protected by Clearscreen.SharpHIPEnter the code you see:
Comments