Archive

Archive for August, 2008

Custom List Forms and List Templates

August 14, 2008 Leave a comment

Creating a deployable list template when the list uses custom forms containing the Custom List Form web part is not quite as straightfoward as it seems. 

Here’s the scenario:

Requirement: create a custom list and provide customised New, Edit and Display forms; then package the list template for deployment to a client’s SharePoint farm.

Proposed Solution: use SharePoint Designer to create custom forms for the list and then save the list as a List Template using the SharePoint UI or use the SharePoint Solution Generator to create a list definition solution in Visual Studio.

Creating the custom forms in SharePoint Designer is at first glance straightforward; see Sahil Malik’s blog for details of how to create a custom New item form.  The Edit and Display forms can be built in a similar fashion.

Important Note: you should be aware that using the Custom List Form web part causes the attachment functionality on the form to be broken.  An unsupported workaround does exist though, see Marc Davis’s blog for details. 

So at this stage the original list has custom NewForm, EditForm and DispForm pages associated with it.  Now all that was required was to save the list definition in some kind of deployable format.

There were two options considered: use the Save list a template option within the List Settings page to produce a .stp file, or use the SharePoint Solution Generator that comes with the Visual Studio extensions for WSS to create a list definition solution in Visual Studio.

In general, the two options appeared similar in terms of the lists produced from the templates; both can be used to create a new list which contains the columns and views defined within the original list.

Save list as template

The main difference seems to be that the Save list as a template option also retains any of the out-of-the-box SharePoint workflows associated with the list; something that may be a decisive factor if you are using one of the these workflows.

Unfortunately creating customised forms using a copy of the original form rather than the original form itself doesn’t quite work when the template contains a workflow. 

The problem is that there exist ‘hidden’ links to the original versions of the forms; these only show up when being referenced by internal SharePoint processes such as workflows.  In the scenario outlined above, any out-of-the-box workflow associated with the list continued to use the uncustomised form. 

The way around these ‘hidden’ links is to edit the original forms directly.  Save often. 

SharePoint Solution Generator

Running the SharePoint Solution Generator produces a Visual Studio project containing an aspx file for each form and view associated with the list together with the XML files that define the list. 

The schema.xml file is used to store the definition of the list’s fields, views, content types and associated forms.  The entry for a form is defined thus:

<Form Type="NewForm" Url="NewForm.aspx" WebPartZoneID="Main" />

List Definitions are deployed as SharePoint features and therefore the forms are also defined within the List Definition project’s feature.xml file:

<ElementFile Location="<feature name>\NewForm.aspx" />  

In order to make use of customised forms, you need to ensure that the uncustomised form definitions within the schema.xml and feature.xml files are replaced with the customised versions.

Well, that’s at least how it should work.  Unfortunately it doesn’t.

When the Custom List Form is added to the form page, it is created as a DataFormWebPart.  This web part takes a parameter called ListID; the default value of which is the GUID of the associated list.  The problem is that the default value of the ListID is never updated, so all instances of the list created from the template will attempt to use the same GUID.

Compounding this problem is the behaviour of the SharePoint Solution Generator.  In building the List Definition, the Solution Generator stripped the CustomNewForm of any customisations so that it uses the uncustomised ListFormWebPart.

Therefore I can’t recommend that anyone use the SharePoint Solution Generator to produce a list definition when working with forms customised with the Custom List Form web part.

ThreadAbortException, Access Denied and OpenWeb()

August 8, 2008 3 comments

Recently I encountered issues with the SPSite.OpenWeb() method that have revealed some interesting behaviour on the part of SharePoint.

I was working with a web part that attempted to instantiate an SPWeb object for a site using the SPSite.OpenWeb() method in the context of the current user.  It was anticipated that if the user didn’t have permissions on the site, then an UnauthorizedAccessException would be thrown.

// this throws an UnauthorizedAccessException if the
// current user doesn't have access
SPWeb web = site.OpenWeb(webID);

Sure enough, an UnauthorizedAccessException is thrown for this statement and was handled in the code.

try
{
  // this throws an UnauthorizedAccessException if the
  // current user doesn't have access
  SPWeb web = site.OpenWeb(siteID);
}
catch(UnauthorizedAccessException)
{
  // user doesn't have access - exception handling code// some other code

Unfortunately, the SharePoint runtime also aborted the thread and caused a ThreadAbortException to be thrown immediately after the executing the contents of the catch block for the UnauthorizedAccessException.  Thus the “some other code” in the above example was never executed.

The net result of all this was that the user was presented with the SharePoint “Access Denied” page – even though the UnauthorizedAccessException was handled it somehow managed to propogate up the stack, presumably thanks to the thread being aborted.

The solution to this problem was to disable the unauthorised access exception handling for the site collection for the duration of the method:

// the standard SharePoint access denied exception
// handling should be disabled for the duration of this method
currentWeb.Site.CatchAccessDeniedException = false;

This permitted the method to handle the UnauthorizedAccessException without SharePoint causing the thread to be aborted.

Note: care has to be taken to ensure that this setting is changed back to its original value before the method exits.

Enter the weirdness

The situation above makes some degree of sense; although one can argue the merit of SharePoint aborting a thread which throws an UnauthorizedAccessException, we’ll take that as granted.

What is very confusing, however, is that immediately after aborting the thread, SharePoint then created another instance of the object responsible for creating the list of sites and this ran to completion still throwing UnauthorizedAccessExceptions for each site the user did not have access to but without throwing another ThreadAbortException.  This was exactly the same code, run in the same context, and yet exhibited quite different behaviour.

When the thread was aborted, why would SharePoint chose to create a new thread, re-run the same code and permit a different outcome?  Answers on a postcard to…

Internet-facing WSS Sites – redirecting anonymous users

August 4, 2008 2 comments
Most internet sites are aimed at anonymous users which causes a few problems if your site is built using Windows SharePoint Services 3.0.  The anonymous user settings for a WSS site contain the following options:

Anonymous Access - Site Level

Entire Web site permits anonymous users to browse the contents of your web site, including such behind-the-scenes pages such as View all site content.  (You can try this for yourself, just browse to most WSS (and MOSS 2007) internet sites and add /_layouts/viewlsts.aspx to the URL of the site.  Clearly this isn’t good.)

Lists and libraries on the other hand allows each individual list or library on the site to specify whether or not anonymous users are permitted to view its contents.  Much better, I’m sure you’ll agree.

However there is one major problem with the Lists and libraries option which becomes apparent very quickly: anonymous users cannot access the default page of the web site.

The default page for a WSS site is <site>/default.aspx and this cannot be changed using the SharePoint user interface (unlike MOSS).  This page is special as it’s not contained within a library, as such, cannot be made available to anonymous users when using the Lists and libraries option.

Solution

A solution to this problem involves writing an HTTP Module to redirect anonymous users to a page within a document library that has anonymous access turned on. 

HTTP Modules seem to strike fear in the hearts of some developers, however this one is very easy:

namespace AccessControl
{
  public class AnonymousPageRedirector : IHttpModule
  {
    public void Init(HttpApplication context)
    {
      context.PreRequestHandlerExecute +=
        new EventHandler(context_PreRequestHandlerExecute);
    }
    void context_PreRequestHandlerExecute(object sender, EventArgs e)
    {
      // if no path is specified in the request
      if (HttpContext.Current.Request.Url.AbsolutePath.Equals("/",
         StringComparison.InvariantCultureIgnoreCase)
         && !HttpContext.Current.User.Identity.IsAuthenticated)
      {
        // redirect unauthenticated users to the custom welcome page
        HttpContext.Current.Response.Redirect("/Pages/Welcome.aspx", true);
      }
    }
  }
}

This piece of code checks if an anonymous user is attempting to access the site without specifying a target page, and therefore would be otherwise be directed to the unreachable default.aspx page, and instead redirects them to a welcome page within the Pages document libary.

To deploy, drop the dll into the GAC and then add the HTTP Module to the web.config of your WSS site by adding the following to the <httpModules> section:

<add name="AnonymousPageRedirector"
  type="AccessControl.AnonymousPageRedirector,
    AccessControl, Version=1.0.0.0, Culture=neutral,
    PublicKeyToken=<your public key>" />