Wednesday, November 26, 2008

SharePoint API: A Client-side Object Model

One common issue (see question #2) that developers using Microsoft's SharePoint platform are faced with is that the SharePoint object model can only be used when the application is run on a server in the SharePoint farm.

The following is code that demonstrates using HubKey's Client-side API for SharePoint. This API wraps many of SharePoint Server 2007's Web Services (Lists Web Service, Webs Web Service, etc.) to provide a familiar object model that can be used on remote client machines without having to process XML requests and responses. In this case the code from the MSDN article How to: Upload a File to a SharePoint Site from a Local Folder was copied verbatim, and it compiles and runs successfully on a client computer, even though the SharePoint DLLs are not referenced or installed.

Update: You can download a demo copy of the API - click here for information.

Update II: The API now includes a demo application - see this post for details.



using System;
using System.IO;
// Using HubKey.Web.Services.SharePoint in place of Microsoft.SharePoint
using HubKey.Web.Services.SharePoint;
 
namespace HubKey.DevelopmentHole
{
    class Program
    {
        static void Main(string[] args)
        {
            Program program = new Program();
            program.RunDemo();
        }
 
        public void RunDemo()
        {
            string webUrl = "http://remoteserver";
            SPSite site = new SPSite(webUrl);
            SPWeb web = site.OpenWeb();
 
            //create a new document library
            Guid id = web.Lists.Add("Temp Doc Lib", "A temp document library.", SPListTemplateType.DocumentLibrary);
            SPList list = web.Lists[id];
 
            //add a field
            list.Fields.Add("Upload Source", SPFieldType.Text, false);
            list.Update();
 
            string fileUrl = webUrl + "/Temp Doc Lib/New Folder/test.txt";
            string localFileName = @"D:\Temp\test.txt";
 
            string localFileContents = "A temp file uploaded by HubKey's SharePoint client-side API.";
 
            File.WriteAllText(localFileName, localFileContents);
 
            //Upload the file using the API code from 'How to: Upload a File to a SharePoint Site from a Local Folder'
            //see http://msdn.microsoft.com/en-us/library/ms454491.aspx
            UploadFile(localFileName, fileUrl);
 
            //Check out
            SPFile file = web.GetFile(fileUrl);
            file.CheckOut();
 
            //update the title and new upload source field by using the file's SPListItem property...
            file.Item["Title"] = "A temp title";
            file.Item["Upload Source"] = "HubKey's SharePoint client-side API.";
            file.Item.Update();
 
            //Check in
            file.CheckIn("Checked in by HubKey's SharePoint client-side API");
 
            //Get the file contents
            string serverFileContents = web.GetFileAsString(fileUrl);
            System.Diagnostics.Debug.Assert(string.Equals(localFileContents, serverFileContents));
 
            //Tidy up
            list.Delete();
        }
 
        public void UploadFile(string srcUrl, string destUrl)
        {
            if (!File.Exists(srcUrl))
            {
                throw new ArgumentException(String.Format("{0} does not exist",
                    srcUrl), "srcUrl");
            }
 
            SPWeb site = new SPSite(destUrl).OpenWeb();
 
            FileStream fStream = File.OpenRead(srcUrl);
            byte[] contents = new byte[fStream.Length];
            fStream.Read(contents, 0, (int)fStream.Length);
            fStream.Close();
 
            EnsureParentFolder(site, destUrl);
            SPFile file = site.Files.Add(destUrl, contents);
            SPFolder folder = file.ParentFolder;
        }
 
        public string EnsureParentFolder(SPWeb parentSite, string destinUrl)
        {
            destinUrl = parentSite.GetFile(destinUrl).Url;
 
            int index = destinUrl.LastIndexOf("/");
            string parentFolderUrl = string.Empty;
 
            if (index > -1)
            {
                parentFolderUrl = destinUrl.Substring(0, index);
 
                SPFolder parentFolder
                    = parentSite.GetFolder(parentFolderUrl);
 
                if (!parentFolder.Exists)
                {
                    SPFolder currentFolder = parentSite.RootFolder;
 
                    foreach (string folder in parentFolderUrl.Split('/'))
                    {
                        currentFolder = currentFolder.SubFolders.Add(folder);
                    }
                }
            }
            return parentFolderUrl;
        }
    }
}

Tuesday, November 18, 2008

Get a Valid SharePoint File Or Folder Name

If you're programatically uploading files to a SharePoint document library or creating folders, the file or folder name must not contain certain invalid characters. If it does, you'll get an error, for example:

The file or folder name ".." contains invalid characters. Please use a different name. Invalid characters include the following: ~ " # % & * : < > ? / \ { | }. The name cannot begin or end with dot and cannot contains consecutive dots.

File or folder names must also be less than or equal to 128 characters in length. In addition, the total unescaped URL length (less the length of the URI authority e.g. http://localhost:2008/) must not exceed 260 characters.

The following code uses a regular expression to validate a file or folder name before it is uploaded or created (without checking the total URL length):

using System;
using System.Web;
using System.Text.RegularExpressions;
 
namespace DevHoleDemo
{
    class Program
    {
        static Regex illegalPathChars = new Regex(@"^\.|[\x00-\x1F,\x7B-\x9F,"",#,%,&,*,/,:,<,>,?,\\]+|(\.\.)+|\.$", RegexOptions.Compiled);
 
        static void Main(string[] args)
        {
            string fileName = "/new%20file.txt";
            string validFileName = fileName;
 
            if (GetValidFileOrFolderName(ref validFileName))
                Console.WriteLine("A valid file or folder name for '{0}' is '{1}'.", fileName, validFileName);
            else
                Console.WriteLine("Could not get a valid file or folder name.");
        }
 
        public static bool GetValidFileOrFolderName(ref string path)
        {
            return GetValidFileOrFolderName(ref path, '_');
        }
 
        public static bool GetValidFileOrFolderName(ref string path, char replacementChar)
        {
            if (path == null)
                return false;
            path = illegalPathChars.Replace(HttpUtility.UrlDecode(path.Trim()), replacementChar.ToString());
            if (path.Length > 128)
            {
                path = path.Substring(0, 128);
                return GetValidFileOrFolderName(ref path, replacementChar);
            }
            return path.Length > 0;
        }
    }
}