Applies To: SharePoint, VB.NET
SharePoint provides many ways to create directories and to upload documents. One of the oldest, and possibly least understood, ways is to use the SharePoint Foundation RPC Protocol.
Generally, using RPC can be more trouble than it’s worth. However, RPC performs and scales well. It supports uploading your content and setting the metadata in one call. You can also use streams rather than just byte arrays. There are a few other reasons why you might choose RPC over the more common solutions, but I’ll assume you know what you’re trying to accomplish.
You can find a decent overview of options by Steve Curran that might give you a little more insight. If you’re still convinced RPC is the way to go, follow along!
In this post I’ll give you some basics about executing RPC methods and demonstrate by showing you how to create multiple directories in a single call.
Getting Started with RPC
The most frustrating part of working with RPC can be trying to track down working examples. In addition, the documentation is pretty sparse. It can also be difficult to find out exactly how you should be encoding your commands and exactly which part(s) should be encoded. Ultimately, you are just making an HTTP POST, but figuring out the correct payload can take a lot of trial and error. Especially since RPC is a little light on helpful error messages.
I’ve broken things down into several utility functions that should help keep things relatively simple and eliminate a lot of the low level troubleshooting that can slow you down.
I’m using VB.NET because the project I initially integrated these calls into was written in VB.NET. Nearly every example I saw out there was in C# and it shouldn’t be too hard to translate my code as needed. Should you have any difficulty, just leave a comment below. I have also placed all of my code inside a Module named SPUploader for convenience.
Basic Encoding Functions
Imports System.Net Imports System.Text Imports System.Web Imports System.IO Public Module SPUploader Public Function EncodeString(value As String) As String Return HttpUtility.UrlEncode(value).Replace(".", "%2e") End Function Private Function escapeVectorCharacters(value As String) As String Return value.Replace("\", "\\").Replace(";", "\;").Replace("|", "\|").Replace("[", "\[").Replace("]", "\]").Replace("=", "\=") End Function
Above are just a couple of simple functions that help to prepare strings. The characters that need to be escaped and the way in which certain parts are encoded can be difficult to sort through in RPC. We’ll be using these both quite a bit.
The EncodeString function uses the standard UrlEncode method with one additional encoding for periods. Some RPC methods don’t seem to have a problem with periods, but they all work with encoded ones. The escapeVectorCharacters function escapes the following characters \;|[]=
RPC Method Helpers
RPC methods are called using the method name, exact SharePoint version, service name and then any parameters. For instance, the create url-directories method that we will be using to create directories should be called like this:
method=create url-directories:server_extension_version&service_name=/&urldirs=list_of_url_directories
This presents a few challenges. First, we need the exact version of SharePoint before we make any calls. Hardcoding this is just asking for trouble. Second, what is the service_name? (Hint: It generally doesn’t matter and can almost always be left as /) and finally what are the parameters and how should those be included?
We’ll get to the specifics of the create url-directories method, but first let’s look at a series of functions that simplify how we call RPC methods in general:
Public Function SharePointVersion(sharepointURL As String) As String Using client As New WebClient() client.UseDefaultCredentials = True client.DownloadString(sharepointURL) Return client.ResponseHeaders("MicrosoftSharePointTeamServices") End Using End Function
I adapted the above function from Joshua on Stackoverflow. This is a quick call to SharePoint that will give you that exact version string needed in all RPC methods. The general idea is that you can call this before an RPC method and cache the result for additional calls.
Here’s a helper method that takes care of building the properly encoded method string:
Private Function methodValue(method As String, SPVersion As String) As String Return EncodeString(String.Format("{0}:{1}", method, SPVersion)) End Function
This function simplifies generating the method:server_extension_version portion of the command.
Helper Class: RPCParameter
When dealing with additional parameters for methods (anything beyond method and service_name), the encoding of those parameters can get a little tricky. I’ve written a helper class called RPCParameter that can help smooth this trickiness:
Imports System.Web Public Class RPCParameter Public Key As String Public Value As String = String.Empty Public IsMultiValue As Boolean = False Public Encode As Boolean = True Public Sub New(_key As String, _value As String, Optional _isMultiValue As Boolean = False, Optional _encode As Boolean = True) Key = _key Value = _value IsMultiValue = _isMultiValue Encode = _encode End Sub Public Function IsValid() As Boolean Return Not String.IsNullOrEmpty(Key) End Function Public Overrides Function ToString() As String If IsMultiValue Then Return String.Format("{0}=[{1}]", Key, IIf(Encode, SPUploader.EncodeString(Value), Value)) Else Return String.Format("{0}={1}", Key, IIf(Encode, SPUploader.EncodeString(Value), Value)) End If End Function End Class
In general, parameters come exactly as you’d expect with a key=encodedvalue. However, there are some variations that can complicate things. This object may seem a little strange but it will become more obvious when we actually see it used. By default, a simple RPCParameter object is just a Key Value Pair with a custom ToString override that outputs key=encodedvalue.
There are some additional properties, however, that can customize this behavior. You can turn off encoding for the value by specifying the Encode property as false. The IsMultiValue property will insert the square brackets before encoding the value. This is important because often the values need to be encoded, but not the brackets.
Generating the Command
Every RPC method is just a string of parameters (Command String) that we convert to a byte array to upload as part of an HTTP POST. Here are a series of overloaded functions to help generate that command string into a byte array:
Public Function CommandBytes(method As String, SPVersion As String, parameter As RPCParameter, Optional serviceName As String = "/") As Byte() Return Encoding.UTF8.GetBytes(CommandString(method, SPVersion, parameter, serviceName)) End Function Public Function CommandBytes(method As String, SPVersion As String, parameters As List(Of RPCParameter), Optional serviceName As String = "/") As Byte() Return Encoding.UTF8.GetBytes(CommandString(method, SPVersion, parameters, serviceName)) End Function Public Function CommandString(method As String, SPVersion As String, parameter As RPCParameter, Optional serviceName As String = "/") As String Dim parameters As New List(Of RPCParameter) parameters.Add(parameter) Return CommandString(method, SPVersion, parameters, serviceName) End Function Public Function CommandString(method As String, SPVersion As String, parameters As List(Of RPCParameter), Optional serviceName As String = "/") As String Dim command As New StringBuilder command.AppendFormat("method={0}&service_name={1}", methodValue(method, SPVersion), EncodeString(serviceName)) For Each parameter As RPCParameter In parameters If parameter.IsValid Then command.AppendFormat("&{0}", parameter.ToString) End If Next Return command.ToString End Function
The CommandBytes functions (lines 27-33) encode the result string into a byte array and allow you to specify either a single RPCParameter object or a List of RPCParameter objects.
The CommandString fuctions actually build the string (lines 35-50). The only difference between them is that if you specify a single parameter, it gets converted to a List of parameters.
The real work for all 4 of these functions occurs in the final CommandString function (lines 41-50). We build the initial method=method&service_name=/ string that is used for every RPC method (line 43). Notice that we use our methodValue helper function to simplify things and and we always encode the service_name value.
Finally, we loop through the RPCParameter objects and append them if they have keys (IsValid) using the RPCParameter.ToString call that takes into account our individual encoding preferences.
So now that we can build the Command String, how do we actually send it to the server?
Executing an RPC Method
Executing an RPC method is really just sending an HTTP POST to a specific dll using the Command String as the payload. This can be easily done using a WebClient object:
Public Function ExecuteRPC(webURL As String, data As Byte(), Optional creds As NetworkCredential = Nothing) As String Dim result As String = String.Empty If creds Is Nothing Then creds = CredentialCache.DefaultCredentials Using client As New WebClient With {.UseDefaultCredentials = False, .Credentials = creds} client.Headers("Content") = "application/x-vermeer-urlencoded" client.Headers("X-Vermeer-Content-Type") = "application/x-vermeer-urlencoded" client.Headers("user-agent") = "FrontPage" result = Encoding.UTF8.GetString(client.UploadData(webURL & "/_vti_bin/_vti_aut/author.dll", "POST", data)) End Using Return result End Function
The ExecuteRPC function creates a WebClient object and sets the Content, X-Vermeer-Content-Type, and user-agent headers (lines 56-59). The main action happens in line 60 when we call the UploadData method causing the POST to the author.dll (there are additional dlls that can be used depending on your method, but this was the only one I’ve needed so I left it this way for simplicity) using the byte array we generate from the CommandString functions.
So far all we’ve done is setup all the helper functions so that we can call generic RPC methods. Any confusion on how we take advantage of all this code should clear up once we look at actually executing a real method.
Creating Directories
Our goal is to create a directory (or more often, multiple directories) using the create url-directories RPC method. We want to be able to take a folder path and ensure every folder in that path exists or is created. For instance, given the folder path “Some Folder/Sub Folder 1/Some Other Folder” we need to potentially create 3 directories in a single RPC method call.
The create url-directories method has only one parameter: urldirs. This parameter is an array of directories, along with properties for each, that we would like created if they don’t already exist. For our purposes, we’re just going to create standard folders without specifying any additional properties. Here’s what the code looks like:
Public Function CreateFolder(webURL As String, libraryName As String, folderPath As String, SPVersion As String, Optional creds As NetworkCredential = Nothing) As String If Not String.IsNullOrEmpty(folderPath) Then folderPath = folderPath.Trim("/") 'create url-directories method: http://msdn.microsoft.com/en-us/library/ms431862(v=office.14).aspx Return ExecuteRPC(webURL, CommandBytes("create url-directories", SPVersion, New RPCParameter("urldirs", folderPathToURLDirectories(libraryName, folderPath), True)), creds) End If Return "folderPath is Empty!" End Function Private Function folderPathToURLDirectories(libraryName As String, folderPath As String) As String Dim directories As New StringBuilder Dim parent As New StringBuilder folderPath = folderPath.Trim("/") libraryName = libraryName.Trim("/") parent.Append(libraryName & "/") Dim folders As String() = Split(folderPath, "/") For Each folder As String In folders directories.Append("[url=") directories.Append(parent.ToString & escapeVectorCharacters(folder)) directories.Append(";meta_info=[]]") parent.Append(escapeVectorCharacters(folder) & "/") Next Return directories.ToString End Function
The CreateFolder function takes a web URL (this does not have to be the root site in a site collection), the name of the library in which to create the folder(s), the folderpath, the SharePoint version, and optionally the credentials to be used during the call (if not specified, the default credentials are used).
If the folderPath isn’t a blank string (line 67), then we strip off any trailing forward slashes (line 68). We then call our ExecuteRPC function using the bytes generated by calling the CommandBytes function using the method create url-directories.
In our ExecuteRPC function call we pass a single RPCParameter object. This is the urldirs parameter and we specify that it is a MultiValue parameter (this will ensure we have the required square brackets around the value). We set the value of the RPCParameter to the result of the folderPathToURLDirectories function. Finally we return the result from the server.
The folderPathToURLDirectories function is used to build our urldirs parameter. Directories need to be created in the proper order (parents first) and each need to have their full path (including the libraryName) in the form [url=path;meta_info=[]]. So if we have the path “Some Folder/Sub Folder 1/Some Other Folder” for the library Documents we want to end up with:
[url=Documents/Some Folder;meta_info=[]][url=Documents/Some Folder/Sub Folder 1;meta_info=[]][url=Documents/Some Folder/Sub Folder 1/Some Other Folder;meta_info=[]]
We do this by splitting the folderPath (line 85) and tracking the parent (including the libraryName) as we create each entry (lines 86-91).
That’s it! Now we can create a bunch of directories in SharePoint using the RPC Protocol! WOWEE! Stay tuned for my next post where we will add to our code to allow us to upload documents and set metadata all within a single call!
[…] my last post, Using SharePoint RPC to Create Directories, I showed you the basics of using the SharePoint Foundation RPC Protocol. I provided a lot of the […]