Inconvenient SPWeb.GetFile(string)


Recently I’ve been working on a piece of code which would obtain an instance of SPFile using either a GUID or the server relative URL. You don’t have to search long to find out that the SPWeb provides a method to do that: GetFile(String). According to the WSS v3 SDK it should return the file object located at the specific URL.

While checking if it was working as I was expecting it to, I have found out that it actually doesn’t. The GetFile method returned an instance of SPFile but the Exists property returned false. The same behavior has been noticed by Julie Lerman among others. As I haven’t found any clear explanation of such behavior, I have decided to have a closer look at it myself.

The method

First of all I have reflected the contents of the GetFile method. Inside I’ve found a rather quite odd choice made by the WSS team. The first thing they do is to obtain a web relative URL of the file you want to get. Passing a URL like “/site/subsite1/Pages/default.aspx” returns “site/subsite1/Pages/default.aspx”. While calling the GetFile method from the SPSite.RootWeb the URL is still valid, right? Calling the GetFile method will return an instance of SPFile but the value of the Exists property will be false: you haven’t got a file at all.

To make things even worse, the SPFile constructor is internal, so you cannot instantiate new SPFile: the only way to go is to get a valid reference.

I could reflect the code even further to exactly find out how the method works, but instead I have run a few tests:

using (SPSite site = new SPSite("http://moss"))
{
  using (SPWeb web = site.RootWeb)
  {
    SPFile f = web.GetFile("/site/subsite1/Pages/default.aspx");
    // f.Exists == false
  }
}

using (SPSite site = new SPSite("http://moss/site/subsite1/"))
{
  using (SPWeb web = site.OpenWeb())
  {
    SPFile f = web.GetFile("Pages/default.aspx");
    // f.Exists == true
  }
}

using (SPSite site = new SPSite("http://moss/site/subsite1/"))
{
  using (SPWeb web = site.RootWeb)
  {
    SPFile f = web.GetFile("Pages/default.aspx");
    // f.Exists == true ?!
  }
}

From what I’ve found out earlier the first two were predictable. But what about the last one? The GetFile method returns actually a valid instance of the default.aspx file! I have done some extra research on the RootWeb object to find out whether it was containing any reference to the /site/subsite1/ URL but no: it’s just seems to be an ordinary instance of the SPWeb class.

Alternative

A method which produces such unpredictable results is not really something I’m very likely to use. Looking at the WSS SDK however, I have found another method: GetFileOrFolderObject(String). To give it a chance I have run the following test:

using (SPSite site = new SPSite("http://moss"))
{
  using (SPWeb web = site.RootWeb)
  {
    object o =
       web.GetFileOrFolderObject("/site/subsite1/Pages/default.aspx");
    if (o is SPFile)
    {
      SPFile f = (SPFile)o;
    }
  }
}

And what do you know: the method returned a valid instance of the SPFile class! If the extra check (if (o is SPFile)) is the price you have to pay to get it all working in a predictable way, it’s OK with me. By the way: why would you need a separate method for getting a file from a URL if you can use one for obtaining both files and folders! If you’re going to intensively use the GetFileOrFolderObject method you might want to create a wrapper method to avoid the control and cast on each call:

public static SPFile GetFileObject(this SPWeb web, string url)
{
  object o = web.GetFileOrFolderObject(url);
  if (o is SPFile)
  {
    return (SPFile)o;
  }
  else
  {
    throw new FileNotFoundException();
  }
}

As you have noticed, the method above is an extension method so you can use the convenient SPWeb.GetFileObject(url) syntax to get the file object from a URL. You might want to return a null value instead of throwing an exception. I have chosen no to for two reasons. First of all we have created this method for convenience and simplicity right? You don’t want to check it for null value each time. In contrary to the GetFile(string) method, the GetFileOrFolderObject(string) method throws a FileNotFoundException exception when there is no file or folder at the given URL. In most cases you will want to catch that exception and handle it in your custom code. Throwing one more shouldn’t be therefore a problem.

Technorati Tags: SharePoint, SharePoint 2007, WSS 3.0

Others found also helpful: