Thursday, July 3, 2008

Programatically Manage Remote Documents with the FrontPage RPC Library

We've rolled up some of the ideas in previous posts about FrontPage Server Extensions RPC methods into a free CC licensed c# code library.

You can download the source code along with a demo copy of HubKey's SharePoint Client API from HubKey's website - click here for details.

Documentation about the members exposed through the HubKey.Net.FrontPageRPC.WebClient class can be found online here.

Features of the class include:


  • Upload or download many documents in one web request using the 'get documents' and 'put documents' methods.

  • True file streaming (without pre-loading to memory) for faster uploads.

  • Asynchronous methods including an asynchronous MoveDocuments method.

  • Auto detection of FrontPage dll paths and versions.



The following FrontPage Server Extensions RPC Methods are included in the library:


  • checkin document method - remotely check-in a document to source control.

  • checkout document method - remotely check-out a document from source control.

  • getDocsMetaInfo method - download document metadata to a client machine.

  • get document method - remotely download a document with metadata from a web folder or SharePoint document library. Includes an asynchronous method.

  • get documents method - programatically download documents with metadata from a web folder or SharePoint document library in one web request. Includes an asynchronous method.

  • list documents method - obtains a list of files and folders on the web or SharePoint server. Sorts the results into a tree structure to allow for use in a TreeView control.

  • list versions method - retrieves metadata for all the versions of the specified document.

  • move document method - programatically move (rename) or copy a file or folder. Allows for moves between webs or even servers by downloading and then uploading the file.

  • open service method - list service meta information.

  • put document method - programatically upload a file to a SharePoint document library or web server with meta info. Includes an asynchronous method.

  • put documents method - remotely upload files to a SharePoint document library or web server with meta info. Includes a method to asynchronously upload large files. Allows true streaming (without loading into memory first) for large file uploads.

  • remove documents method - delete files or folders from a SharePoint document library or web server.

  • setDocsMetaInfo method - update document meta data from a client computer.

  • uncheckout document method - revert a document to the version before it was checked out.

  • url to web url method - break a url into its web and web relative components.



A very brief code example follows - we'll cover an in depth sample application in a future post:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace HubKey.Net.FrontPageRPC
{
    class Program
    {
        static void Main(string[] args)
        {
            WebClient webClient = new WebClient();
 
            //upload a document and check it in
 
            MetaInfoCollection meta = new MetaInfoCollection();
            meta.Add("vti_title", "My Title");
            UploadDocumentResponse uploadDocumentResponse = webClient.UploadDocument("https://localhost/Docs1/test.txt", @"C:\test.txt", meta);
            if (uploadDocumentResponse.Document.IsCheckedOut)
                webClient.CheckInDocument(uploadDocumentResponse.Document, "uploaded via RPC");
 
            //move a document between servers
 
            MoveDocumentResponse moveDocumentResponse = webClient.MoveDocument("http://localhost/Docs2/test.txt", "https://remoteserver/Docs2/test.txt");
        }
    }
}

38 comments:

Kipper said...

Do item event handlers still run when using RPC methods?

txs8311 said...

Yes, item event handlers do still run when using RPC methods.

Anonymous said...

It was my understanding the RPC methods would trigger SharePoint events however the MoveDocument method does not trigger the insert event for the document on the target library.

MoveDocumentResponse moveDocumentResponse = webClient.MoveDocument(txtMoveFrom.Text, txtMoveTo.Text, true, false, PutOptionEnum.Default);

Is there a problem with how I’m calling the method or does it in fact not trigger the events period. (If not, it would be nice to have this information in the documentation as to what events would be raised, and which wouldn’t, etc).

Thanks

txs8311 said...

It looks like moving a document triggers the ItemFileMoving and ItemFileMoved events. Copying a document triggers the ItemAdding and ItemAdded events.

Adding documentation on the occurrence/sequence of event firing would be nice - somebody has taken a brief look at this here.

Anonymous said...

Do you know if it's possible either via RPC and/or native SharePoint web services to create a new alert on a document or list item?

The alerts.asmx web service only exposes Get and Delete methods ... I assume there has to be some way to create a new alert.

Any thoughts?? Thanks!

txs8311 said...

Sorry, I haven't seen anything that would allow you to do this. You might be left with creating your own web service. Alternatively you could add an event handler to a list that examines document meta data each time a document is added (say in a hidden document library) and then creates an alert depending on parameters in the meta data.

Anonymous said...

Sorry, I haven't seen anything that would allow you to do this. You might be left with creating your own web service. Alternatively you could add an event handler to a list that examines document meta data each time a document is added (say in a hidden document library) and then creates an alert depending on parameters in the meta data.

Thanks for the reply ... what we're looking for is a way to remotely create a new SharePoint alert w/ out needing to "touch" the SharePoint server (no custom web services, no code that needs to be installed on the server, etc).

Does anyone have any ideas? Has anyone tried to simulate a HTTP POST to a SharePoint server? That is, from the "Create New Alert" page could I see what the HTTP POST looks like then try to simulate that from code?

Anonymous said...

This is a great post, but i find out an issue that when i change the field display name say from "abc 1" to "abc 2", i still have to use "abc 1" in order to set the value. So is there a way to get the mapping for the field name and display name?

Mike Graf said...

This is awesome!!

I was wondering if you could help me understand why the checkin method isn't working.


Here is my code:

public static bool UploadSharepointDocument(string url, string title, string User, string fileLocation)
{
bool success = true;

try
{
//New webClient
WebClient webClient = new WebClient();

//Meta collection for doc
MetaInfoCollection meta = new MetaInfoCollection();

//Adds the Title to the document
meta.Add("vti_title", title);

//Upload the Doc
UploadDocumentResponse uploadDocumentResponse = webClient.UploadDocument(url, fileLocation, meta);

//Check In the document
CheckInDocumentRequest chkInDoc = new CheckInDocumentRequest(uploadDocumentResponse.Document);

if (uploadDocumentResponse.Document.IsCheckedOut)
webClient.CheckInDocument(uploadDocumentResponse.Document, "Initial Check In By: " + User);

if (uploadDocumentResponse.Document.IsCheckedOut)
webClient.CheckInDocument(uploadDocumentResponse.Document);

if (uploadDocumentResponse.Document.IsCheckedOut)
webClient.CheckInDocument(chkInDoc);


if (uploadDocumentResponse.HasError)
{
StringBuilder sb = new StringBuilder();

sb.Append("Sharepoint Upload is having problems. \n");
sb.Append("Error Message: " + uploadDocumentResponse.ErrorMessage + "\n \n");
sb.Append("Document Information: \n Document Display Name: " + uploadDocumentResponse.Document.DisplayName + "\n");
sb.Append("Document Directory: " + uploadDocumentResponse.Document.FileInfo.Directory.FullName + "\n");
sb.Append("Document Name: " + uploadDocumentResponse.Document.FileInfo.FullName + "\n");
sb.Append("Document Size: " + uploadDocumentResponse.Document.FileSize.ToString() + "\n \n");
sb.Append("Exception Info\n\n");
sb.Append("Source: " + uploadDocumentResponse.ErrorResponse.Exception.Source + "\n");
sb.Append("Exception Message: " + uploadDocumentResponse.ErrorResponse.Exception.Message + "\n");
sb.Append("Stack Trace: " + uploadDocumentResponse.ErrorResponse.Exception.StackTrace + "\n");

ErrorHandler.HandleError(sb.ToString());
}
}

catch (Exception ex)
{
success = false;
ErrorHandler.HandleError(ex, true, "This is from the UploadSharepointDocument Method in the Document Class");
return success;
}

return success;
}




I have all three overloads there for testing. For some reason notta one of them is checking the file in.

Thanks a lot!
-Mike

txs8311 said...

Hi Mike,

You can check the server response from the CheckInDocument method - see if that helps to locate the problem, e.g.

//Upload the Doc
UploadDocumentResponse uploadDocumentResponse = webClient.UploadDocument(url, fileLocation, meta);

if (uploadDocumentResponse.HasError)
throw new Exception(uploadDocumentResponse.ErrorMessage);

if (uploadDocumentResponse.Document.IsCheckedOut)
{
CheckInDocumentResponse checkInDocumentResponse = webClient.CheckInDocument(uploadDocumentResponse.Document, "Initial Check In By: " + User);
if (checkInDocumentResponse.HasError)
throw new Exception(checkInDocumentResponse.ErrorMessage);
}

txs8311 said...

Re. the DisplayName / Name issue posted by Anon. above:

Yes, as you point out, meta data is mapped to the internal name of the field - not the display name. To map from the display name to the (internal) name you could use the lists web service, e.g.

ListsWebService.Lists ws = new ListsWS.ListsWebService.Lists();
ws.Credentials = System.Net.CredentialCache.DefaultCredentials;
ws.Url = "http://localhost/_vti_bin/Lists.asmx";
XmlNode node = ws.GetList("Docs");

XmlDocument doc = new XmlDocument();
doc.LoadXml(node.OuterXml);
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("soap", "http://schemas.microsoft.com/sharepoint/soap/");

string displayName = "abc 2";
string xpath = string.Format("//soap:Field[@DisplayName='{0}']/@Name", displayName);
string columnName = XmlConvert.DecodeName(doc.SelectSingleNode(xpath, ns).Value);

We'll take a look at incorporating this functionality in future releases of the code. Thanks for your post.

Mike Graf said...

... it's me again... :)


So I've run into a situation. The app works great on my local developement machine. But, when I publish the app to the webserver I can upload files. When I send the document path

It is looking to the server for that file. So if a user is uploading the file - C:\Docs\page.doc I send that to the method


UploadDocumentResponse upDocResponse = webClient.UploadDocument(url, fileLocation, meta, PutOptionEnum.CreateDir, true);

And instead of locating it on the user's machine - it tries to find it on the server. Do you know what I am doing wrong?

Thanks again!

Anonymous said...

txs8311, thanks for your reply for the DisplayName / Name issue

However, I find out that the internal name is not work either. In my case, when i first create the field, the name is "abc 1" and the internal name is "abc_x0020_1". After that, i modify the display name to "abc 2" and the internal name is still "abc_x0020_1" as expected. The problem is, i can only use "abc 1" to set the value. Neither "abc 2" and "abc_x0020_1" are not work! Interesting, isn't it?

txs8311 said...

Yep, but from what you're saying - the internal name works - just not when it's xml encoded (e.g. "abc 1" rather than "abc_x0020_1"). It seems that the column name is encoded on the server when the server processes the request, so encoding it on the client, e.g. by sending "abc_x0020_1", will result in the server trying to reference "abc_x005F_x0020_1". That's the reason for putting the XmlConvert.DecodeName in the lists web service code above - you'll get "abc 1" returned as columnName which is what you should be using. Hope that makes sense.

Toni said...

Hi!

How can I upload a file using your code to a server using form authenication and persistent cookie? Thanks

txs8311 said...

Sorry Toni - we haven't yet had a chance to test with forms authentication (just NT and basic). One thing to try would be to turn off the custom web request object and use the .net WebRequest object instead, by either setting

Request.UseFrontPageRPCWebRequest = false;

(see code below) or removing the

this.UseFrontPageRPCWebRequest = true;

line in the UploadDocumentRequest and UploadDocumentsRequest contructors.

Upload example:

WebClient webClient = new WebClient();

string url = "http://localhost/docs/test.txt";
string path = @"c:\temp\test.txt";
MetaInfoCollection metaInfo = new MetaInfoCollection();
metaInfo.Add("vti_title", "My Document");

UrlToWebUrlRequest urlRequest = new UrlToWebUrlRequest(url);
UrlToWebUrlResponse urlResponse = webClient.UrlToWebUrl(urlRequest);
if (urlResponse.HasError)
throw new Exception(urlResponse.ErrorMessage);
Document document = new Document(urlResponse.FileUrl, new FileInfo(path), metaInfo);

UploadDocumentRequest request = new UploadDocumentRequest(document);
request.WebUrl = urlResponse.WebUrl;

request.UseFrontPageRPCWebRequest = false;

UploadDocumentResponse response = webClient.UploadDocument(request);
if (response.HasError)
throw new Exception(response.ErrorMessage);

Toni said...

Hi! I tried that now and it didn´t work directly. Get a 401 unauthorized message.
I need to set the cookiecontainer of the httpwebrequest or the header somehow..

mkraus81 said...

Hi,

we have a Problem. Our Sharepoint-Services installed on Server in DMZ (other DMZDomain). If i use the code on my Client in Domain (clientDomain)

uploadResp = webClient.UploadDocument(url,
filename, meta);

I get an Error it dont work...
The code works if the Sharepoint-Services installed in the same domain.

Is it a general Problem? Who has a solution for me?

Thanks

txs8311 said...

Hi - what's the error message you're receiving? If it's an authentication type error and your default network credentials are not sufficient to log on to the site, have you tried setting the applicable credentials explicitly? E.g.

webClient.Credentials = new System.Net.NetworkCredential("user", "password", "DOMAIN");

mkraus81 said...

error Message is

The remote server returned an error: (407) Proxy authentication required.

txs8311 said...

If you're using a proxy server, try setting the request's UseFrontPageRPCWebRequest to false (see the September 5, 2008 example above) and then set the webclient proxy, e.g.

webClient.Proxy = new System.Net.WebProxy(url, port);
webClient.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

We haven't yet tested with a proxy server so any feedback would be appreciated.

If you don't have any luck it might pay to also check your SharePoint Proxy Server Settings.

Thanks

mkraus81 said...

with code

webClient.Proxy = new System.Net.WebProxy(url, port);
webClient.Proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

i tried yesterday, but i get the same error...

Sumanth Kollipara said...

We have the "Require documents to be checked out before they can be edited" option set to True in Versioning Settings of our Sharepoint and the CheckInDocument method is throwing an error. I believe it is not able to find the file as the initial check in hasn't taken place yet.

Can some one please help.

Sumanth Kollipara said...

I solved my own problem from my previous comment. The example code didn't work at the line

webClient.CheckInDocument(uploadDocumentResponse.Document, "uploaded via RPC");

The reason is the document has the webrelative url and I got a FrontPageRPCException saying the file is not available.

Instead I used
webClient.CheckInDocument(fullURL);
where fullURL is the complete URL of file that was just uploaded.

Thank you for writing all those classes to make our FrontPage RPC calls easy.

Anonymous said...

Hi, looks like a great post - something I could very well use! Could you help me however in one thing, do you reckon it is possible (and if so how) to call C# code from an Excel (2007) macro (VBA)? I have experience in (simple) VBA and in .NET, but not in interaction/mixing them...

txs8311 said...

Hi SchoutenCC,

The short answer is yes - it's possible, but you'll have some work ahead of you.

You'll need to do several steps which are covered in this post.

Steps 1-3 can be avoided if you use the FrontPageRPC project properties tabs to 1 - sign the assembly (Signing tab), and 2 - register for COM interop (Build tab).

You'll need to add the GuidAttribute and ClassInterface attributes to the WebClient class to make it visible to VBA's object browser and intellisense.

Also, make the assembly visible by setting [assembly: ComVisible(true)] in the AssemblyInfo.cs file.

Once you've built the assembly (and registered it if doing steps 1-3), you can add a reference in Excel.

The following code demonstrates uploading a file:

Sub UploadDocument()
On Error GoTo ErrUploadDocument

Dim webClient As New webClient
Dim response As UploadDocumentResponse

Set response = webClient.UploadDocument("http://localhost/Docs/test.txt", "C:\Temp\test.txt")

If response.HasError Then
MsgBox response.ErrorMessage, vbCritical
End If

ExitUploadDocument:
Exit Sub

ErrUploadDocument:
MsgBox Err.Description, vbCritical
Resume ExitUploadDocument

End Sub


One problem you may run into: any class that is derived from another class will give you some grief. E.g. the MetaInfoCollection class (derived from List<(MetaInfo)>). The only methods that will show up will be for IEnumberable (the last interface in the base class). To get around this you'd have to do some playing around - e.g. implement another helper class to expose the methods you need (MetaInfoCollection.Add for example) - or change the MetaInfoCollection class to implement IList<(MetaInfo)>.

Hopefully that sets you on the right track...

Unknown said...

I'm trying to get the meta info out of a file:

WebClient webClient = new WebClient("http://ndie510");
DownloadDocumentRequest downloadDocumentRequest = new DownloadDocumentRequest("/Docs/wtf/again/test.txt");
DownloadDocumentResponse downloadDocumentResponse = webClient.DownloadDocument(downloadDocumentRequest);
Document document = DownloadDocumentResponse.Document;
Console.ReadLine();

Document ends up being null. What am I doing incorrectly?

txs8311 said...

Dave, your code looks ok and runs correctly on my machine. (You've got a typo in the second to last line but that won't be your issue). Are you getting an error response from the server? i.e. what's the value of downloadDocumentResponse.ErrorMessage?

Unknown said...

txs8311: Thanks! I got it to work with this code:
WebClient webClient = new WebClient("http://ndie510");
DownloadDocumentResponse downloadDocumentResponse = webClient.DownloadDocument("http://ndie510/Docs/wtf/again/test.txt");

Then just read the metainfo attached to the document!

Here's another task I would really like to undertake:
One of my metadata columns is a lookup. Is it possible with this library (or with web services at all) to read values from a lookup and add values to a lookup?

txs8311 said...

Glad you got that working Dave.

To read a list of values available in a lookup field you would need to build a query to return unique values from the lookup list. It is possible to do this with the native SharePoint web services. We've developed a beta helper API that makes working with the web services a bit easier - please see this post for more details.

Unknown said...

txs8311: Thanks for the quick reply. Yep! I'm using that library. It makes development possible in our environment. I was just asking if the functionality to query/update lookup lists was available in this library (if it is I haven't found it). It sounds like your library can do this. Can you point me in the right direction or docs?

txs8311 said...

Check out this post for some pointers.

Anonymous said...

I would like to use this library to upload GigaByte files into a SharePoint document library. Is there some sample code? When I click the following link I just get an empty page:
"Documentation about the members exposed through the HubKey.Net.FrontPageRPC.WebClient class can be found online here."

Thank you for your inspiring pages.

Maddy said...

Hi,

I am from .Net background and I am given the job to write custom webdav .I looked in net to get a starting point but still struggling can u please assist me the things to start to create a custom webdav.And also i have a question does sharepoint and word communicates using webdav protocol or FPRPC

txs8311 said...

Hi Maddy,

Download our API from API - there's a link to some source code for our FrontPageRPC library that includes FrontPage RPC methods along with a few WebDAV methods. That should get you pointed in the right direction.

Anonymous said...

In case anyone runs into an intermittent 401.2 when running from a console application, here's the fix:

In the FrontPageRPCWebRequest class, ReadResponseString method, add a Thread.Sleep(1) in the while loop before the available check. see below:

string ReadResponseString()
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[_receiveBufferSize];
int nread = 0;
while ((nread = _readStream.Read(buffer, 0, _receiveBufferSize)) != 0)
{
sb.Append(Encoding.UTF8.GetString(buffer, 0, nread));
Thread.Sleep(1);
if (_tpcClient.Available == 0) break;
}
_lastResponse = sb.ToString();
return _lastResponse;
}

faisal said...

I am getting execption while calling this method

WebClient webClient = new WebClient("http://faisal/");

or

WebClient webClient = new WebClient();

Exception
"Could not load file or assembly 'HubKey.Web.Services.SharePoint, Version=1.1.0.11, Culture=neutral, PublicKeyToken=97f6204956a6ed42' or one of its dependencies. The system cannot find the file specified."

Need your kind help :)

sb said...

i am trying to move a document and all its versions and want to make sure that the metadata for all the versions is also maintained. I tried using the
movedocument ( base verson ) and then checkin but metadata is not preserved. is there an easier better way.

appreicate your help