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

48 comments:

Anonymous said...

Hi! Great post!
I have a question for you...
Is it possible to update the metadata called "vti_modifiedby" ?

I have tryed to wrote it as:
vti_modifiedby;SR|MYDOMAIN\user ...
or
vti_modifiedby;SW|MYDOMAIN\user ...
or
vti_modifiedby;SR|MYDOMAIN\\user ...

but nothing appened.

Is only the title that must be referenced as "vti_title"?

Lorenzo

txs8311 said...

I don't think you can modify the vti_modifiedby meta-key in this way because the access modifer is read-only (see here for information on this particular key, and here for a description of access codes and data types).

One approach to consider in this particular case would be to impersonate the user when you create the new document, e.g.

webClient.Credentials = new NetworkCredential("user", "password", "MYDOMAIN");

Note that with the migrationsemantics put_option parameter set (as it is in the code above), any update to an existing document will preserve the original author and modified by information (as long as the credentials passed are administrator credentials).

Lorenzo said...

There is also a problem with the equal char =

Try to upload a file with a = in a field. It will fail.

The only solutions seems to be to replace the character.

I think that also the # char gets some problems.

Lorenzo

Anonymous said...

I solved adding the = char in the 'EscapeVectorChars' method.

Lorenzo

Don Dwoske said...

This post has helped me tremendously. I have about 700 documents to upload, with metadata on each of them.

I ran into a problem/question, being the SharePoint newbie that I am.

Some of my metadata fields are lookup values into lists, and one is a Person or Group. I blindly just tried putting string text like "mouse" into the lookup metadata field, but it didn't work. I clearly need to put an ID or some reference to the item in there - I just have no idea what to put or how to find out.

Any guidance would be helpful.

txs8311 said...

I’ve just posted here which explains a bit about how the Person or Group data type is formatted. Basically it’s a lookup value - so to include this as meta data using the code above you’re going to need to know the principal id for each user in question. If you take a look at the Xml property of a document in your document library by using the SharePoint API (e.g. web.Lists["Docs"].Items[0].Xml) you’ll see how the meta data is encoded, which for this data type is as an integer (the principal id), e.g.:

ows_MetaInfo='70;#vti_parserversion:SR|12.0.0.4518
personColumn:IW|1
lookupColumn:IW|72

So to get the code above to work, you’d need to update the EncodeMetaInfo method above to encode integers, e.g.: case "System.Int32": dataTypeCode = "I"; break;
Lookups are similar, in this case the number is the ID of the list item. You can find out this ID without code by changing the view of the list to display the ID.

txs8311 said...

The link to the post on the formatting of the Person or Group data type is missing above. It should be http://geek.hubkey.com/2008/01/searching-for-users-or-groups-using.html.

Anonymous said...

Thank you for posting, very slick.
I have a question...

I was able to upload *.txt, and *.doc files. When I try to post an *.mht I receive and error message.

msg=Could not process the file Product Handling Specs/TEST.MHT as a Single File Web Page

Do we need to change any of the settings to save a MHT file?

Thanks

txs8311 said...

You shouldn't need to change any of the settings to get this to work with valid .mht files. The server does do a check to see that the file content matches the file type in this case, so for example you'll get that sort of message if you attempt to write the simple text string in the code example, i.e.

DocLibHelper.Upload("http://localhost/Docs/Test Folder/test.mht", System.Text.Encoding.ASCII.GetBytes("Test text."), properties);

Instead, when I tested it with a document previously saved as .mht in Word, it worked no problem, e.g.

DocLibHelper.Upload("http://localhost/Docs/Test Folder/test.mht", File.ReadAllBytes(@"D:\Temp\test.mht"), properties);

Anonymous said...

Thanks txs8311. Looking at your example I was able to fix the mht issue.
Carolina

Anonymous said...

Would you happen to have some code to delete a specific document from a document library?

txs8311 said...

Here's an example of using the remove documents RPC method.

Anonymous said...

Thank you very much for the delete code.
Would you recomend any book to help coding for WSS 2007?

txs8311 said...

Here's a whole list - not that I've actually read any of them!

sunitha said...

what would be error handling strategy for different errors that the RPC would return. Do you have a list of error codes or text that we can check?

txs8311 said...

To handle specific errors, you'd need to parse the text in the return html string. I haven't seen a list of error codes anywhere - the error message format is described briefly on msdn here. You could extract a list of all error messages from the file \%Program Files%\Common Files\Microsoft Shared\web server extensions\12\BIN\1033\FPEXT.MSG.

Eugene Rosenfeld said...

Fantastic post! Thanks!

Khumza said...

great post...helped me a lot..

sunder said...

Hi,

How to upload mutiple documents in document libarary by using RPC methods.(put document or put documents)please explain with live example.

Thank in advance.
Sunder Chhokar

Martin Kwong said...

hi there it work fine but i got a problem, when update the metadata , it should call UpdateListItems() which take a xml to update the columns. i can only get "title" columns to be updated. all other columns cant be updated, even, when called alone. if i update "title" together with another column, both will not be updated.


pls help, i am really new to sharepoint

txs8311 said...

Martin - you're using the code from Part I of this post, so take a look at InnerXml of the response you're getting from the Lists web service - it may give you some clues. If you're trying to update a date field - you may need to provide it in a special format - e.g. myDate.ToString("s") + "Z"

Other than that, try using the RPC code in this post (Part II) - it uses a different method of updating the meta data - see if it gives you the same problem. If you have access to run code localy on the SharePoint server you could also test updating the fields with some API code which might help you track down the problem.

bob said...

when i use webClient.Credentials = CredentialCache.DefaultCredentials it works fine in localhost but not on server but when i pass username and password it works fine even on server. any idea?

Anonymous said...

Thanks, dude, you rock!

Evan said...

Fantastic!! I've been searching for this for days!

Anonymous said...

Thanks, it is fabulous! do you know how can I add a date to a column which property is set as "Date only" (MM-DD-YY) in sharepoint?

oreng said...

Great post,Thanks alot !
I've encountered a problem escaping field names with special chars (e.g equal sign "=")
Any Ideas ?
Thanks,
Oreng

txs8311 said...

oreng - anonymous above had the same issue and solved it by adding an equals to the EscapeVectorChars method above. For a more comprehensive treatment of RPC methods than the brief demo code above, you can download a free library of methods from our website. See this post for more details. Thanks.

Anonymous said...

Excellent Post, but when i use DefaultCredentials for webClient.Credentials on production server, its not working, anonymous access denied...any idea anyone.

Pradip said...

Use webClient.Credentials = new NetworkCredential("username", "password", "domain");

Pradip.

dave said...

Hi, this is great, thanks very much. I also need to add "FileRef" (like in your web service example) - how would this be done using RPC?

Anonymous said...

Hi,

Great post.

Just a quick question. Will this method unable me to upload file greater than 50mb? I've tried the uploaddate web client and the copy webservice and get (413) ENTITY TOO LARGE. Neither will upload a file over 50mb. Using the OM is probably not an option at the moment.

Will this method work for larger files. Up to 150mb approx?

Thanks,

Mark

txs8311 said...

Hi Mark - regarding files >50mb: yes, it'll work, although you may need to configure large file support if you haven't done so already. In particular you'll need to increase the maximum upload size on each virtual server's general settings page in SharePoint Central Administration. You may also need to change the IIS httpRuntime maxRequestLength and/or executionTimeout - see this KB article for details.

Anonymous said...

Outstanding work! Just wanted to thank you for sharing this - Shawn.

daniel said...

Thanks for the code txs8311.
I'm noticing that when I upload a word document in this manner, that the event receivers I've attached to that document library don't fire, such as ItemAdded, ItemCheckedIn. However, they do fire if I upload a new document through the sharepoint UI. I'm using :
"...keep_checked_out={0}", true.ToString().ToLower()

Any thoughts on whether I should expect these eventreceiver events to be caught?

txs8311 said...

Hi Daniel - they should fire - check out the first few comments on this post for more details. Thanks.

daniel said...

Correction: I''m using
"...keep_checked_out={0}", false.ToString().ToLower()

which should check the document in but doesn't seem to. I can manually check the document in using the UI

Jonathan said...

Hi txs8311 for the great code.

Could you possibly modify it to incorporate the method you described for people type updating? I'm having trouble doing what you described.

Clevinger said...

Sorry, can anyone help me...

I'm not understanding where I specify the document library to upload to? Or perhaps I don't understand the purpose to this program...

I'm using it in a asp.net app, and I need to upload a file that a user chooses with a FileUploader (FileUpload1):

Upload("http://sharepointsite/DocLib/" + FileUpload1.FileName,File.ReadAllBytes(FileUpload1.PostedFile.FileName),null);

(another thing I don't understand what to put in the properties parameter). When I choose the file I attempt to use the function I get "Could not find file ..."

Daniel Flippance said...

Hi txs8311,

We're up loading documents to a SHarePoint dcoument library using this method - thanks! - and that document library is using a Content Type to describe our metadata fields. We are experiencing an unusual problem - The document is uploaded and the metadata columns seem to be set (you can see their values in the document library and when you look at the Edit Properties) but when the document is opened in Word 2007, it complains that these required metadata fields are not filled in.

If I go to Edit Properties, change the metadata to somethign else, save, then change them back to the same values, when I open the document again in Word 2007, everything is fine.

Any ideas what's going wrong with the metadata field during the upload?

Thanks,
Daniel

txs8311 said...

Hi Daniel

I'm not sure that the "put document" RPC method will write anything other than a "Document" content type - to update the content type you'll need to use the setDocsMetaInfo RPC method after you upload the file.

Alternatively you could use our FrontPageRPC library:

WebClient wc = new WebClient("https://remote");
MetaInfoCollection meta = new MetaInfoCollection();

meta.Add("ContentType", "MyContentType");
meta.Add("MyRequiredField", "HubKey");

UploadDocumentResponse uploadRespone = wc.UploadDocument("https://remote/Docs/test.docx", @"D:\Docs\test.docx", meta);
SetMetaInfoResponse setResponse = wc.SetMetaInfo("https://remote/Docs/test.docx", meta);


or, using our SharePoint API:

SPWeb web = new SPSite("https://remote").OpenWeb();
SPListItem item = web.Files.Add("https://remote/Docs/test.docx", File.ReadAllBytes(@"D:\Docs\test.docx")).Item;
item["ContentType"] = "MyContentType";
item["MyRequiredField"] = "HubKey";
item.Update();

Eve said...

thanks so much for this - doesn't quite make it for what i need though. do you know of any way to put a document and it's metadata all in one go? The frontpage "put document" won't do content types, the copy.copyintoitems webservice won't do lookups... any other ideas? it's to get around creating two versions in a versioning enabled document library. any ideas would be much appreciated...

Doğan said...

My Test application was a console application and worked properly. I moved the code into a web application which is hosted on IIS 7.0. And returned an exception.


method=put document:12.0.0.6318
status=
• status=589830
• osstatus=0
• msg=There is no file with URL 'xyz.xml' in this Web.
• osmsg=


What is the problem, please help!

sohailkhan said...

i am interested to upload a document with its modified the created date.

I tried to update this two fields for creation date.





to set field value i am using Name attribute. (Created_x0020_Date,Created).

Please provide some solution for it.

DavidBarrows said...

Hello, thanks for your nice work.

I'm doing a simple test using the code and although the "keepCheckedOut" flag is set to false, the file remains checked out after the upload. Any thoughts as to why that might be happening, and/or how I might fix it?

Regards, -Dave

Eugene Rosenfeld [2-time MOSS MVP] said...

Doğan, you might be running into the "double hop" issue. The following code authenticates the request to SharePoint:

webClient.Credentials = CredentialCache.DefaultCredentials

This will only work if the process calling the code has access to the user's credentials. If this is running in a web site, the site must be configured to use delegable authentication, such as Kerberos or Basic Auth.

There's lots of documentation on this:
http://www.google.com/search?q=sharepoint+double+hop

HTH,
-Eugene

dev_18 said...

Hi, I used this post to upload files but i was unable to update the meta deta i.e. title of the uploaded document. When i am using just a string to upload same as ur code it works and update the meta info too but when i use File.ReadAllBytes(a.xlsb) it does not update meta data.

Zee said...

Thanks for this awesome post. I used it as an InfoPath Solution on SP 2007. It worked fined until we migrated to SP 2010, Url is changed to https (Older http) and the web application is also configured as FBA. This solution is not working any more the GetWebURL() is returning Null . Any help will be greatly appreciated.
Thanks,
ZEE

MariusD said...

First of all, great post, thank you!!! Would anybody know, can vti_timecreated be updated? If yes, what do I need to modify in this code, only the EncodeMetaInfo? Thanks!!!