28

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?

1
  • +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
    Commented Mar 14, 2013 at 18:21

4 Answers 4

16

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);

clientContext.ExecuteQuery();

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/

1
  • You don't need clientContext.ExecuteQuery() when using OpenBInaryDirect.
    – Chris
    Commented Mar 23, 2018 at 3:57
5

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; 
    }
}
1
  • 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
    Commented Mar 15, 2013 at 7:53
4

Try something like this

Uri filename = new Uri(@"file full url");
var f = this.Web.GetFileByServerRelativeUrl(filename.AbsolutePath);
var stream = f.OpenBinaryStream();
this.Context.Load(f);
this.Context.ExecuteQuery();
using (var reader = new StreamReader(stream.Value, Encoding.UTF8))
{
    string value = reader.ReadToEnd();
    // Do something with the value
}
1

In CSOM you could utilize the following method to resolve ClientContext by request Url:

public static class ClientContextUtilities
{
    /// <summary>
    /// Resolve client context  
    /// </summary>
    /// <param name="requestUri"></param>
    /// <param name="context"></param>
    /// <param name="credentials"></param>
    /// <returns></returns>
    public static bool TryResolveClientContext(Uri requestUri, out ClientContext context, ICredentials credentials)
    {
        context = null;
        var baseUrl = requestUri.GetLeftPart(UriPartial.Authority);
        for (int i = requestUri.Segments.Length; i >= 0; i--)
        {
            var path = string.Join(string.Empty, requestUri.Segments.Take(i));
            string url = string.Format("{0}{1}", baseUrl, path);
            try
            {
                context = new ClientContext(url);
                if (credentials != null)
                    context.Credentials = credentials;
                context.ExecuteQuery();
                return true;
            }
            catch (Exception ex) {}
        }
        return false;
    }

}

Then the following example demonstrates how to get File by absolute url via CSOM:

ClientContext context;
if (ClientContextUtilities.TryResolveClientContext(requestUri, out context, null))
{
     using (context)
     {
         var baseUrl = requestUri.GetLeftPart(UriPartial.Authority);
         var fileServerRelativeUrl = requestUri.ToString().Replace(baseUrl, string.Empty);                  
         var file = context.Web.GetFileByServerRelativeUrl(fileServerRelativeUrl);
         context.Load(file);
         context.ExecuteQuery();
     }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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