Take the 2-minute tour ×
SharePoint Stack Exchange is a question and answer site for SharePoint enthusiasts. It's 100% free, no registration required.

We have a database with absolute (full) links to SharePoint files. If a user wants to download or get checkoutstatus information about the file, our program needs lookup that information in SharePoint.

We have done this easily in the past with the following code:

string url = "http://server/site/documentlibrary/testdoc.docx";

using(SPSite site = new SPSite(url))
{
    using(SPWeb web = site.OpenWeb())  
    {
        SPFile file = web.GetFile(url); 

    }

}

source: http://sacarter.wordpress.com/2011/11/28/retrieve-spfile-object-by-full-url/

Now we want to use the SharePoint Client Object Model instead of the server object model and do the same.

Microsoft.SharePoint.Client.ClientContext clientContext = new Microsoft.SharePoint.Client.ClientContext(@"http://server/site/documentlibrary/testdoc.docx");
Microsoft.SharePoint.Client.File file = clientContext.Web.GetFileByServerRelativeUrl(@"http://server/site/documentlibrary/testdoc.docx");

clientContext.Load(file);
clientContext.ExecuteQuery();

Debug.Print("file: {0}", file.Name);
Debug.Print("checkout status: {0}", file.CheckOutType.ToString());

But then I get the following exception with loading the clientcontext:

Cannot contact site at the specified URL http://server/site/documentlibrary/testdoc.docx. There is no Web named "/site/documentlibrary/testdoc.docx/_vti_bin/sites.asmx".

The clientcontext needs a website url, but I don't know which part of the url is a website, site collection, library or folder. That's something SharePoint should know.

A (brute force) workaround I can think of is to try to create a clientcontext until it doesn't give any exceptions.

Thus first try the link longest link (without filename) and traverse the urlparts:

http://server/site/documentlibrary  (<-- exception because it is a document library)
http://server/site                  (<-- bingo, this is a website)

This should also work for more complicated url's, like:

http://server/sites/site1/subsite/doclib/folder1/folder2  (exception, because it is a folder)
http://server/sites/site1/subsite/doclib/folder1  (exception, because it is a folder)
http://server/sites/site1/subsite/doclib  (exception, because it is a document libarary)
http://server/sites/site1/subsite (bingo, its a subsite within a site collection which is placed in a managed path 'sites' with wildcard inclusion)

Now we have the website url and can create the clientcontext and the server relative url.

Is there a more elegant/efficient way to get the job done?

share|improve this question
    
+1 excellent question, I am afraid try and fail is the only answer as you cannot tell the difference between subsite, library and folder just from looking at the url. You're suggesting a bottom-up approach, the other way (maybe cleaner) would be top-down. –  Christophe Mar 14 '13 at 18:21
add comment

3 Answers

If I only want to download the stream of the file then the ClientContext object isn't that picky about the website url. The file can be easily downloaded and written to a file.

Uri filename = new Uri(@"http://server/sites/site1/subsite/doclib/folder1/folder2/prettyimage.jpg");
string server = filename.AbsoluteUri.Replace(filename.AbsolutePath, "");
string serverrelative = filename.AbsolutePath;

Microsoft.SharePoint.Client.ClientContext clientContext = new Microsoft.SharePoint.Client.ClientContext(server);
Microsoft.SharePoint.Client.FileInformation f = Microsoft.SharePoint.Client.File.OpenBinaryDirect(clientContext, serverrelative);

using (var fileStream = new FileStream(@"d:\prettyimage.jpg", FileMode.Create))
f.Stream.CopyTo(fileStream);

The Stream.CopyTo() method is an extension method.

public static class StreamExtensions
{
    public static void CopyTo(this System.IO.Stream src, System.IO.Stream dest)
    {
        if (src == null)
            throw new System.ArgumentNullException("src");
        if (dest == null)
            throw new System.ArgumentNullException("dest");

        System.Diagnostics.Debug.Assert(src.CanRead, "src.CanRead");
        System.Diagnostics.Debug.Assert(dest.CanWrite, "dest.CanWrite");

        int readCount;
        var buffer = new byte[8192];
        while ((readCount = src.Read(buffer, 0, buffer.Length)) != 0)
            dest.Write(buffer, 0, readCount);
    }
}

source: http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/dc7776e0-84b7-444c-9b82-15ff5ac5db41/

share|improve this answer
add comment

I would probably host a web service on the SharePoint server that returned the SPWeb.Url

using (SPSite site = new SPSite(fileUrl))
{
    using (SPWeb web = site.OpenWeb())
    {
      return web.Url;
    }
}

But if you were going through that much trouble you could build a service to bring back the file info you needed instead of just the site URL.

If that's not an option and this will be executed multiple times within the same session you can trim a little time by keeping a collection of valid relative site urls. This will probably only help for a very large number of files.

class Program
{

    public static HashSet<string> validRelativeUrls = new HashSet<string>();

    static void Main(string[] args)
    {
        string fileUrl = @"http://server/apps/doclib/This-is-a-test.docx";
        Uri fileUri = new Uri(fileUrl);

        ClientContext clientContext = new ClientContext(GetRelativeSitePath(parseUrlsToTest(fileUri)));

        Microsoft.SharePoint.Client.File file = clientContext.Web.GetFileByServerRelativeUrl(fileUri.AbsolutePath);

        clientContext.Load(file);
        clientContext.ExecuteQuery();

        Debug.Print("file: {0}", file.Name);
        Debug.Print("checkout status: {0}", file.CheckOutType.ToString());

    }


   public static List<string> parseUrlsToTest(Uri fileUri)
    {
        List<string> urlsToTest = new List<string>();
        List<string> urlSplit = fileUri.AbsolutePath.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ToList<string>();

        //Trim last 2 b/c the document library path will be at least 2 deep
        for (int i = 0; i < urlSplit.Count - 2; i++)
        {
            if (i == 0)
            {
                urlsToTest.Add(String.Format("{0}://{1}/{2}", fileUri.Scheme, fileUri.Host, urlSplit[i]));
            }
            else
            {
                urlsToTest.Add(urlsToTest[i - 1] + "/" + urlSplit[i]);
            }
        }
        return urlsToTest;
    }

    public static string GetRelativeSitePath(List<string> urlsToTest)
    {
        //Sort longest to shortest
        var descUrlsToTest = from path in urlsToTest
                             orderby path descending
                             select path;

        foreach (string path in descUrlsToTest)
        {
            if (validRelativeUrls.Contains(path))
            {
                return path;
            }
            try
            {
                ClientContext clientContext = new ClientContext(path);
                clientContext.ExecuteQuery();    //make sure the path has access to _vti_bin/sites.asmx  (this will catch subfolders in path) 
                validRelativeUrls.Add(path);
                return path;
            }
            catch (Exception ex)
            {
                //do nothing b/c it's not a valid site or site collection
            }

        }
        return null; 
    }
}
share|improve this answer
    
Thanks for your answer, I guess its the way to go for requesting File objects when checkout / properties information is needed. For downloading the file the ClientContext object isn't that picky. –  Robin Mar 15 '13 at 7:53
add comment

The easiest way would be to use Web.GetFileByServerRelativeUrl

See msdn

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.