Wednesday, November 14, 2007

Upload a File to a SharePoint Document Library - Part II

In Part I I posted code that used the SharePoint lists service to help in uploading files with meta data to a sharepoint document library. Here is another variation, this time using FrontPage Server Extensions and RPC. Note that this code won't create more than one new folder - if this is a problem you may want to ensure the full folder path with code like that from the first article.

Update: You can download a comprehensive c# class library to automate RPC calls - including uploading files to a SharePoint document library. See this blog post for more information.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Web;
 
namespace DevHoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, object> properties = new Dictionary<string, object>();
            properties.Add("Title", "Test Title");
            //Create or overwrite text file test.txt in 'Docs' document library creating folder 'Test Folder' as required.
            DocLibHelper.Upload("http://localhost/Docs/Test Folder/test.txt", System.Text.Encoding.ASCII.GetBytes("Test text."), properties);
        }
    }
 
    static class DocLibHelper
    {
        static string EncodeMetaInfo(Dictionary<string, object> metaInfo)
        {
            if (metaInfo == null) return "";
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string, object> kvp in metaInfo)
            {
                if (kvp.Value != null)
                {
                    string fieldName = kvp.Key; // note: field names are case sensitive
                    switch (fieldName)
                    {
                        case "Title":
                            fieldName = "vti_title";
                            break;
                    }
                    string data = EscapeVectorChars(kvp.Value.ToString());
                    string dataTypeCode = "S";
                    switch (kvp.Value.GetType().FullName)
                    {
                        case "System.Boolean":
                            dataTypeCode = "B";
                            break;
                        case "System.DateTime":
                            data = ((DateTime)kvp.Value).ToString("s") + "Z";
                            break;
                    }
                    sb.AppendFormat("{0};{1}W|{2};", fieldName, dataTypeCode, data);
                }
            }
            return HttpUtility.UrlEncode(sb.ToString().TrimEnd(';'));
        }
 
        static string EscapeVectorChars(string value)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in value)
            {
                switch (c)
                {
                    case ';':
                    case '|':
                    case '[':
                    case ']':
                    case '\\':
                        sb.Append("\\");
                        break;
                }
                sb.Append(c);
            }
            return sb.ToString();
        }
 
        public static string GetWebURL(string url)
        {
            try
            {
                url = url.Substring(0, url.LastIndexOf("/"));
                try
                {
                    using (WebClient webClient = new WebClient())
                    {
                        webClient.Credentials = CredentialCache.DefaultCredentials;
                        webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                        webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                        byte[] data = Encoding.UTF8.GetBytes("method=open+service%3a12.0.4518.1016&service_name=%2f");
                        string result = Encoding.UTF8.GetString(webClient.UploadData(url + "/_vti_bin/_vti_aut/author.dll", "POST", data));
                        if (result.IndexOf("\n<li>status=327684") == -1)
                            return url;
                        throw new Exception();
                    }
                }
                catch
                {
                    return GetWebURL(url);
                }
            }
            catch
            {
                return null;
            }
        }
 
        public static bool Upload(string destinationUrl, byte[] bytes, Dictionary<string, object> metaInfo)
        {
            string result = "";
            string webUrl = GetWebURL(destinationUrl);
            string documentName = destinationUrl.Substring(webUrl.Length + 1);
            return Upload(webUrl, documentName, bytes, metaInfo, out result);
        }
 
        public static bool Upload(string webUrl, string documentName, byte[] bytes, Dictionary<string, object> metaInfo, out string result)
        {
            string putOption = "overwrite,createdir,migrationsemantics"// see http://msdn2.microsoft.com/en-us/library/ms455325.aspx
            string comment = null;
            bool keepCheckedOut = false;
            string method = "method=put+document%3a12.0.4518.1016&service_name=%2f&document=[document_name={0};meta_info=[{1}]]&put_option={2}&comment={3}&keep_checked_out={4}\n";
            method = String.Format(method, documentName, EncodeMetaInfo(metaInfo), putOption, HttpUtility.UrlEncode(comment), keepCheckedOut.ToString().ToLower());
            List<byte> data = new List<byte>();
            data.AddRange(Encoding.UTF8.GetBytes(method));
            data.AddRange(bytes);
            try
            {
                using (WebClient webClient = new WebClient())
                {
                    webClient.Credentials = CredentialCache.DefaultCredentials;
                    webClient.Headers.Add("Content-Type", "application/x-vermeer-urlencoded");
                    webClient.Headers.Add("X-Vermeer-Content-Type", "application/x-vermeer-urlencoded");
                    result = Encoding.UTF8.GetString(webClient.UploadData(webUrl + "/_vti_bin/_vti_aut/author.dll", "POST", data.ToArray()));
                    if (result.IndexOf("\n<p>message=successfully") < 0)
                        throw new Exception(result);
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
                return false;
            }
            return true;
        }
    }
}
 

Monday, November 5, 2007

Provide Status Updates for Long Running Operation Jobs

A simple way to give users some feedback on the progress status of long running jobs is provided by the Microsoft.SharePoint.Publishing assembly. When a class that inherits from the LongRunningOperationJob class has its start method invoked, a list item is added to the Long Running Operation Status list in the root web. Providing a link to the LongRunningOperationProgress.aspx page along with the guid of this list item will display an update page with a progress bar similar to that shown below:

Long Running Job Status Page

A LongRunningOperationJob might not necessary be designed to run as a SPJobDefinition timer job, although there's no reason you couldn't combine the two to provide more granular information than the _admin/ServiceRunningJobs.aspx page provides.

Some example code follows. Before running it, make sure that your site collection includes a list named 'Long Running Operation Status' at the root web. This list is created when you install the Office SharePoint Server Publishing Infrastructure feature at the Site Collection Features level. The list apparently isn't removed when this feature is uninstalled so it's not necessary to have the Publishing Infrastructure feature activated if you don't want it to be - just activate then deactivate it.

The Microsoft.SharePoint.Publishing assembly can be found here:
%Program Files%\Microsoft Office Servers\12.0\Bin\Microsoft.SharePoint.Publishing.dll

using System;
using System.Diagnostics;
using System.Threading;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Internal;
 
namespace DevHoleDemo
{
    class Program
    {
        const string STATUS_LIST_NAME = "Long Running Operation Status";
        const string PROGRESS_PAGE_URL = "/_layouts/LongRunningOperationProgress.aspx";
        static void Main(string[] args)
        {
            string webURL = "http://localhost/";
            using (SPSite site = new SPSite(webURL))
            {
                using (SPWeb web = site.RootWeb)
                {
                    LongRunningJob longRunningJob = new LongRunningJob();
                    longRunningJob.Title = "Demo Long Running Job";
                    longRunningJob.TotalOperationsToBePerformed = 30;
                    longRunningJob.RedirectWhenFinished = true;
                    longRunningJob.NavigateWhenDoneUrl = web.Url + "/" + STATUS_LIST_NAME;
                    longRunningJob.Start(web);
                    Process.Start("iexplore.exe", string.Format("{0}{1}?JobId={2}", web.Url, PROGRESS_PAGE_URL, longRunningJob.JobId));
                    LongRunningOperationStatus jobStatus;
                    do
                    {
                        jobStatus = LongRunningOperationStatus.GetJob(site, longRunningJob.JobId);
                        Console.WriteLine(jobStatus.StatusDescription);
                        Thread.Sleep(1000);
                    } while (jobStatus.Status != LongRunningOperationJob.OperationStatus.Successful);
                }
            }
        }
    }
 
    class LongRunningJob : LongRunningOperationJob
    {
        public override void DoWork()
        {
            for (this.OperationsPerformed = 0; this.OperationsPerformed < this.TotalOperationsToBePerformed; this.OperationsPerformed++)
            {
                this.StatusDescription = string.Format("im in ur long running job, doing ur work {0} of {1}...", this.OperationsPerformed, this.TotalOperationsToBePerformed);
                this.UpdateStatus();
                Thread.Sleep(1000);
            }
        }
    }
}