Thursday, 13 August 2009

Clearing individual pages from output cache in a publishing site

If you are using MOSS for a public facing internet site, the chances are you are using page output caching to increase scalability and performance. If you aren't you certainly should be. Page output caching is a feature of ASP.NET which MOSS overlays it's own control over the top.

Under site collection settings you can set up cache profiles which dictate how pages are cached and apply them to different parts of the site. You may have some areas which cache for several hours and other pages which only cache for a few minutes. Either way, not going through the full page rendering cycle for these pages if they are in output cache massively increases the number of visitors your site can support.

Often though you have a need to get some pages refreshed more quickly than just waiting for them to fall out of cache. Sure you can perform an app pool reset but that will take all pages out of cache and possibly lose users their session depending on your session management strategy.

It is possible to force an individual page to be removed from output cache and thus have it re-rendered immediately, useful for getting important content updates to users immediately.

The required call is the HttpResponse.RemoveOutputCacheItem() method which takes a parameter of a string for the site relative URL of the page you wish to remove from cache, e.g. /pages/landingpage.aspx

Given that this must occur within the application you are trying to clear a page from how do we call this ourselves? One approach is to create a custom layouts page which we can navigate to directly and enter the URL to get it cleared. As our public site is very likely set up with anonymous access we will need to ensure that the page inherits from Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase. This will allow it to be viewed by anonymous users.

I have created a sample page which will do this work for us, it is a page you can drop directly into your layouts folder and call from within your site. All the work is done in inline code much like many of the MOSS layouts pages so it doesn't require a code deployment.

Finally I also placed a secondary text box on the page asking for a pass code in order to perform the change so that if the URL of the page is discovered by an external user then can't easily reset the output cache for a page. You would want to change this pass code in your file to something more appropriate. Find the line which looks like

if(txtPasscode.Text != "supersecretpassword1")

and change the value to something else.

Simply copy the code below into a text file called something like ClearPageFromOutputCache.aspx, drop it into the layouts folder at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS

You can then access the page using the URL http://www.nameofmypublicsite.com/_layouts/ClearPageFromOutputCache.aspx




Pop in the site relative URL and your chosen pass code and the page should be removed from output cache. Due to how the API works even if you put a non-existent URL in it returns success but there is nothing I can do about this.


<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" Inherits="Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase" MasterPageFile="~/_layouts/application.master" EnableViewState="false" EnableViewStateMac="false" %>
<%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Register TagPrefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="System.Collections.Generic" %>

<script runat="server">
protected override void OnLoad(EventArgs e)
{
if (IsPostBack)
{
if (txtUrl.Text.Trim() == "")
{
lblResults.Text = "Please enter a valid URL";
return;
}

if(txtUrl.Text.Trim().ToLower().StartsWith("http://"))
{
lblResults.Text = "Please enter a relative URL";
return;
}

if(txtPasscode.Text != "supersecretpassword1")
{
lblResults.Text = "ERROR";
return;
}

try
{
HttpResponse.RemoveOutputCacheItem(txtUrl.Text.Trim());
lblResults.Text = "SUCCESS";
}
catch (Exception ex)
{
lblResults.Text = ex.ToString();
}
}
}
</script>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
<sharepoint:encodedliteral runat="server" text="Page Output Cache Clear Tool" encodemethod='HtmlEncode' />
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
<sharepoint:formattedstring formattext="Page Output Cache Clear Tool" encodemethod="HtmlEncodeAllowSimpleTextFormatting"
runat="server">
<asp:HyperLink id="onetidListEditTitleLink" runat="server" visible="true"/>
</sharepoint:formattedstring>
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageImage" runat="server">
<img src="/_layouts/images/blank.gif" width="1" height="1" alt="">
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderPageDescription" runat="server">
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<table cellspacing="0" cellpadding="0" border="0" style="width: 100%; height: 100%" class="propertysheet">
<tr>
<td class="ms-descriptiontext">
<table>
<tr>
<td class="ms-sectionheader">
<h3 class="ms-standardheader">URL</h3>
</td>
</tr>
<tr>
<td class="ms-descriptiontext ms-inputformdescription">
Enter URL of page to clear from output cache, ensure it is a relative URL and NOT an absolute URL</td>
</tr>
</table>
</td>
<td class="ms-authoringcontrols ms-inputformcontrols">
<table>
<tr>
<td>
<asp:TextBox ID="txtUrl" runat="server" CssClass="ms-input" Columns="100" /></td>
</tr>
</table>
</td>
</tr>
<tr>
<td class="ms-descriptiontext">
<table>
<tr>
<td class="ms-sectionheader">
<h3 class="ms-standardheader">Passcode</h3>
</td>
</tr>
<tr>
<td class="ms-descriptiontext ms-inputformdescription">Enter Passcode</td>
</tr>
</table>
</td>
<td class="ms-authoringcontrols ms-inputformcontrols">
<table>
<tr>
<td>
<asp:TextBox ID="txtPasscode" TextMode="Password" runat="server" CssClass="ms-input" Columns="100" /></td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<asp:Label CssClass="ms-formvalidation" Text="" runat="server" ID="lblResults"></asp:Label></td>
</tr>
<tr>
<td class="ms-sectionline" height="2" colspan="5">
<img src="/_layouts/images/blank.gif" width="1" height="1" alt=""></td>
</tr>
<tr>
<td class="ms-spaceBetContentAndButton" colspan="5">
<img src="/_layouts/images/blank.gif" width="1" height="1" alt=""></td>
</tr>
<tr>
<td colspan="5">
<table cellpadding="0" cellspacing="0" width="100%">
<colgroup>
<col width="99%">
<col width="1%">
</colgroup>
<tr>
<td>
&nbsp;</td>
<td nowrap id="align01">
<input id="onetidCreateList" class="ms-ButtonHeightWidth" type="submit" value="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,multipages_okbutton_text%>' EncodeMethod='HtmlEncode'/>"
accesskey="<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,multipages_okbutton_accesskey%>' EncodeMethod='HtmlEncode'/>">
</td>
</tr>
</table>
</td>
</tr>
<tr height="60">
<td>
&nbsp;</td>
</tr>
</table>
<sharepoint:formdigest runat="server" />
</asp:Content>

Thursday, 23 July 2009

MOSS publishing sites and download aspx file problem - part 2

As you may recall in this earlier blog post I had come across a problem where certain browsers put pages into output cache with slightly different HTTP response headers which caused issues for subsequent users to that page with normal browsers.

This turned out to be ASP.NET and not IIS or MOSS. It was all down to the browser definition files as described in this MSDN article. A bunch of XML configuration files which specify what capabilities certain browsers have. Unfortunately they are very out of date since they were created at the time .NET 2.0 came out and mobile browsers have moved on significantly since then.

I was able to override the default behaviour of ASP.NET by creating an entry in a custom .browser file in the App_browsers folder of our web site. By creating a node with a refId="Default" it effectively overrode a property on one of the base definitions hence applying it for pretty much all requests, the property in question being "preferredRenderingMime" set to "text/html".

Problem solved!

Tuesday, 26 May 2009

Annoying content source bug

I recently came across this annoying bug when setting up content sources recently.

If you try and specify multiple start addresses which differ only by query string then you get an error.



The error is "the object you are trying to create already exists. try again using a different name"



It seems that SharePoint uses only the main part of the URL for adding these to an internal collection. Using Reflector on Microsoft.SharePoint.Portal.Search.Admin.Pages and following the code path through confirmed this to be the case.

The solution is to create a page with those URLs on and crawl that instead. Not the end of the world but annoying all the same.

Wednesday, 11 February 2009

MOSS publishing sites and download aspx file problem

A project I was working on recently experienced a peculiar problem where users would browse to a page and be prompted to download the page e.g. save home.aspx somewhere locally. This was a MOSS publishing internet public facing site with multiple web front ends and page output caching set up.



The downloaded page was not the aspx source itself but the properly rendered HTML.

The problem was seemingly random and intermittent, it would appear on different web front ends and an IISRESET would cure the problem for a while but then other pages would then start to suffer from the same problem.

Eventually after finding a page which exhibited this behaviour and using Fiddler to look at the headers of the request and response I saw that the response content-tye header was set to "text/vnd.wap.wml" and not "text/html" as one might expect.

The problem was that the first person to hit that particular page was using a mobile browser whose user-agent string and Accepts header were making SharePoint/IIS return a content type like that above. Unfortunately this response was then place in the page output cache in MOSS.

When the next person to come along requested the page using a full fat browser like IE or Firefox, those browsers did not know how to handle the content type and offered to save the file instead!

Solution?

To vary the output caching dependant on the browser in use. Care is needed here to not impact the efficiency of the caching too much. You could for instance use the site output cache profile settings and choose user-agent against the vary by HTTP header. This is less than ideal however as user-agent strings can vary greatly:-

"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506)"

Different browsers, versions of browsers, .NET versions installed in the case of Windows PCs and multiple combinations of each can lead to dozens of potential user-agent strings. Storing a separate version of each page for each one would be very wasteful.




A better solution would be to just store a different cached version per browser i.e. just part of the user-agent string. This can be achieved by implementing a vary by custom handler on your site.This implements the IVaryByCustomHandler interface and your own logic determines what to base the output cache on.



There is an MSDN article detailing this here:-

http://msdn.microsoft.com/en-us/library/ms550239.aspx

and my implementation:-


public string GetVaryByCustomString(HttpApplication app, HttpContext context, string custom)
{
//The code looks for parameters specified in the cache
//profile that are passed to the handler and parses those
//delimited by a semicolon.
StringBuilder sb = new StringBuilder();
string[] strings = custom.Split(';');
bool appended = false;
foreach (string str in strings)
{
switch (str)
{
case "PWBrowserCheck":
if (appended)
sb.Append(';');

sb.Append(Request.Browser.Type.ToString());
break;


default:
continue;
}
appended = true;
}
return sb.ToString();
}

Thursday, 3 July 2008

SharePoint site columns and a mngfield.aspx error

I came across an interesting SharePoint bug recently when deploying custom site columns to a site collection.

Some people were complaining that they couldn't get my colour calendar solution to work properly on their systems. That when they installed they got an error trying to view the site column gallery. It turns out that there is a bug in SharePoint to do with the site column gallery and URL case sensitivity.

In the MSDN forums someone had come across the problem before (search for post by CorbyH425) http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=2863822&SiteID=1

My solution contained some custom field definitions in an elements XML file. I was trying to install to a site collection but not the root one.

Let's assume the site collection was created on this URL :-

http://mybigsite.com/sites/MWBigSite1

I then install my solution, deploy my solution and activate the site collection scoped feature passing the following url to stsadm :-

http://mybigsite.com/sites/mwbigsite1

If you then navigate to the site column gallery in the root of the site collection http://mybigsite.com/sites/mwbigsite1/_layouts/mngfield.aspx an error occurs.



A quick check with verbose logging switched on reveals the following

Application error when access /_layouts/mngfield.aspx, Error=Object reference not set to an instance of an object.

If you install, deploy and activate but provide the url the site was created on with the correct case then all is fine.

So although urls you use when deploying should be case insensitive it may pay to just copy and paste the actual url from the browser for stsadm purposes, alternatively, use stsadm to add the solution and the use the solution management UI in central admin to deploy and activate.

Tuesday, 15 April 2008

New release of colour (color) calendar v2.3

A new release of the colour calendar. Here are the changes:-

  • Field naming changes in the UI to make them more user friendly.
  • Event Category Title field does not appear when viewing details of an event.
  • Enable width fix option on colour coding web part - this implements the fix required to allow you to resize the calendar view web part without having scroll bars appear. (thanks to Kyle for this)
  • An instruction manual is now included in the release!

Thanks to everyone for all the feedback. As usual the release can be found here

Monday, 24 March 2008

Another MacBook Pro convert...

There seems to be a spate of posts like this extolling the virtues of MacBook Pros and here is another!

I needed a new laptop as I recently switched jobs and need my own kit now. After first trying a Dell M1530 (horrible screen) and then a Zepto (Scandinavia's largest laptop manufacturer! why oh why did I not go for a big name? anyway space bar didn't work) I decided I just needed something that worked and I was reading such great things about the MacBook Pros.

I went along to the Apple store and tried one out, I was hooked. These things are wonderful pieces of kit. Amazing build quality, fantastic screens, really slim and light. I went for a 15.4” machine as the 17” is just a little too big for my liking plus I have worked at 1920x1200 for a couple of years on a Dell but for the past few months was finding myself winding the resolution down as it was too high so the 1440x900 on the 15.4” is quite nice.

I got one of the new Penryn (new Intel 45nm processors) based ones and bought my own RAM for it. Chose the 2.4GHz as 0.1GHZ with a bit of extra cache simply isn't worth £300 extra.

Unlike most of the other SharePoint guys who have gone down this route though I have not decided to just run with Vista and here is why...

I want to use all of my 4Gb RAM which means Vista x64 or OSX (which has been 64 bit for a while) Now Bootcamp works really well and I have installed and used Vista on it fine. In fact the new OS X install DVDs which have the Bootcamp drivers on even have proper 64 bit drivers for all the devices in a MBP, smoothest install ever. However it is still Vista. I just can't stand it. It is harder to use than XP and is such a resource hog.

The biggest bottleneck in a laptop with VM work is typically the hard drive and Vista hammers it. The fact is that running VMs using VMWare Fusion in OS X is hands down faster and smoother than VMWare Workstation in Vista. I can run VMs with more memory or just more VMs with OS X. It certainly seems to handle memory pressure better than Vista. Also suspending and restoring VMs is soooo much quicker in Fusion it is unbelievable. I have no idea why and I tried fiddling with the settings in VMWare workstation but it made no difference. I know VMWare workstation is more powerful with more options but honestly Fusion does everything I need.

Not everything is perfect, the keyboard takes some getting used to and there are different shortcuts inside and outside the VMs. Also 2 USB ports isn't enough although fairly easily rectified with a small USB hub I carry with me. I haven't had the chance to try out a super fast Firewire 800 external drive yet. OS X has its quirks, the way windows maximise and minimise drives me a bit mad for instance.

I have a Bootcamped XP installation running in Fusion for running Office and any Windows apps I haven't found equivalents for in OS X (i.e. games and Outlook), this is all seamless due to Unity mode in Fusion (check out VMWare Fusion Unity on YouTube) Development VMs then sit full screen on their own desktops and it is a quick key press to jump from one to another.

About a month ago I spent 6 hours trying to sort out a friend's laptop that had been reduced to a never ending BSOD reboot loop due to Vista SP1 (not beta) being applied and decided life is too short to waste on it.
As most SharePoint dev takes place in VMs I just don't care about the host OS any more, OS X just works. There are so many little things about this new laptop which are cool, it has been a long time since I used a piece of technology and doing so made me smile....