In my last post, I explained why we decided to use a Click Once application to solve our business problem. To quickly review, we needed a way for our business users to upload documents to a SharePoint 2007 document library in mass, set the meta data, set the permissions per document, and to do so easily. Let’s look at the pieces that make up our solution. First, we have the Windows Form application. This app is deployed using Click Once and calls SharePoint web services in order to upload files and then calls web services to set the meta data (SharePoint columns and permissions). Second, we have a custom action. The custom action is responsible for providing our users a link that will launch the Windows app, as well as passing values to it via the query string. And lastly, we have the web services that the Windows Form application calls. For our solution, we used both out of the box web services and a custom web service in order to set the column values in the document library as well as the permissions on the documents. Now, let’s look at the technical details of each of these pieces. (All of the code is downloadable from here: ) Windows Form application deployed via Click Once The Windows Form application, called “Custom Upload”, has just a few classes in it: Custom Upload -- the form FileList.xsd -- the dataset used to track the names of the files and their meta data values SharePointUpload -- this class handles uploading the file SharePointUpload uses an HttpWebRequest to transfer the file to the web server. We had to change this code from a WebClient object to the HttpWebRequest object, because we needed to be able to set the time out value. public bool UploadDocument(string localFilename, string remoteFilename)
{
bool result = true;
//Need to use an HttpWebRequest object instead of a WebClient object
// so we can set the timeout (WebClient doesn't allow you to set the timeout!)
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(remoteFilename);
try
{
req.Method = "PUT";
req.Timeout = 60 * 1000; //convert seconds to milliseconds
req.AllowWriteStreamBuffering = true;
req.Credentials = System.Net.CredentialCache.DefaultCredentials;
req.SendChunked = false;
req.KeepAlive = true;
Stream reqStream = req.GetRequestStream();
FileStream rdr = new FileStream(localFilename, FileMode.Open, FileAccess.Read);
byte[] inData = new byte[4096];
int bytesRead = rdr.Read(inData, 0, inData.Length);
while (bytesRead > 0)
{
reqStream.Write(inData, 0, bytesRead);
bytesRead = rdr.Read(inData, 0, inData.Length);
}
reqStream.Close();
rdr.Close();
System.Net.HttpWebResponse response = (HttpWebResponse)req.GetResponse();
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
{
String msg = String.Format("An error occurred while uploading this file: {0}\n\nError response code: {1}",
System.IO.Path.GetFileName(localFilename),
response.StatusCode.ToString());
LogWarning(msg, "2ACFFCCA-59BA-40c8-A9AB-05FA3331D223");
result = false;
}
}
catch (Exception ex)
{
LogException(ex, "{E9D62A93-D298-470d-A6BA-19AAB237978A}");
result = false;
}
return result;
}
The class also contains the LogException() and LogWarning() methods.
When the application is launched, it parses the query string for some initial values. The query string looks like this:
string queryString = "Srv=clickonce&Sec=N&Doc=DMI&SiteName=&Speed=128000&Max=50";
This Srv is the path to the server (my Virtual Machine is name “clickonce”), the Sec is short for security – meaning HTTPS or HTTP, the Doc is the shortcut for which document library to use, and SiteName is the name of the SharePoint site. Speed is used to calculate an estimate for download speed for each file. We added this so our users uploading documents would realize how long it might take for clients in remote locations (using slow WAN connections) to download the documents.
The last value, Max, is the maximum size that the SharePoint site will allow documents to be. This allowed us to give users a warning that a file is too large before we even attempt to upload it.
Another critical piece is the meta data collection. We organized our site using SharePoint content types, so when the app loads, it gets a list of the document library’s content types. The user then select one of the content types from the drop down list, and then we query SharePoint to get a list of the fields that make up that content type. We used both an out of the box web service, and one that we custom built, in order to get these values.
Once we have the content type fields, we then add controls to the form. Which type of control we add depends on the data type of the field. (DateTime pickers for date/time fields, etc) We didn’t write code to cover every data type, since we were working with a limited set of content types and field data types.
Here’s a screen shot of the Form, before and after someone has selected the content types and our code has added the custom controls:
The other piece of meta data we collect is the in the upper right corner of the app, “Users with access”. This box lists the different SharePoint Groups that we have set up and by checking the boxes, the user can set the permissions on the uploaded documents.
All of this meta data is collected and submitted to our custom web service, which then sets the values on the documents on the list. We’ll look at these web services in a future post.
In the next post, we’ll walk through the Custom Action we built.