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.




January 3rd, 2009 at 7:52 pm
I think the 'this' is a typo in your method?
public static SPFile GetFileObject(this SPWeb web, string url)
Nice article though. Saved me some trouble.
January 3rd, 2009 at 8:33 pm
@Donal: No it's not. It's being used to define an Extension Method. It's a .NET 3x feature but you can use it in .NET 2.0 with Visual Studio 2008 as well (more info @ http://geekswithblogs.net/robp/archive/2007/12/20/using-extension-methods-in-.net-2.0-with-visual-studio-2008.aspx)
October 9th, 2009 at 1:49 am
Were you able to retrieve default.aspx using web.GetFileOrFolderObject(\\"/site/subsite1/Pages/default.aspx\\")?
My problem is similar but in may case the file actually exists in the subsite but GetFileOrFolderObject() method is throwing exception.
October 9th, 2009 at 6:34 am
@MJ: As far as I can remember, the above piece code worked correctly in my test.
December 26th, 2009 at 12:55 am
Hi Waldek, the post is a bit old but I wondered something in your 'obscure' example (the 3rd one), site.RootWeb always gets the SPWeb that is also a SPSite (in your case, http://moss, independent of the level of URLs you sent to the constructor), wouldnt the http://moss/Pages/default.aspx exists also?
Also, thanks for the alternate version, might come in handy
May 17th, 2010 at 4:29 pm
Thank you for this article, you have just saved my day heheh :)
My problem was a bit different, with SPFolder.Files[url] property, it was finding the newly upload file just fine, but file's Item property was null. Same code was working on two other Document Libraries just fine with same security settings, permissions etc…
September 3rd, 2010 at 12:05 pm
Hey Waldek,
I used to use the following construct:
SPWeb web = site.OpenWeb(fileUrl, false);
SPFile = web.getFile(fileUrl);
This has worked till today, where the filename contained an underscore (_myfile.jpg) – bad idea…
I tried your method now and got an \"Incorrect function\" exception on the first try, when using SPContext.Current.Site.RootWeb. It seems that you have to explicitly open your Web by using SPContext.Current.Site.OpenWeb(\"/\"). Pretty strange though, but now it works.
Cheers, Rob
September 3rd, 2010 at 4:00 pm
@Robert: thanks for the feedback, Robert.
September 19th, 2012 at 9:49 am
Hi Waldek
Many thanks for the article. Implemented the call to GetFileOrFolderObject method and fixed an intermittent issue I had with GetFile with the exact scenario you described.
Kind regards,
Lee