I recently spoke with a great customer in India, and he was experimenting with the code from my Sending WebDAV Requests in .NET blog post. He had a need to send the WebDAV LOCK/UNLOCK commands, so I wrote a quick addition to the code in my original blog post to send those commands, and I thought that I'd share that code in an updated blog post.
First of all, you may need to enable WebDAV locks on your server. To do so, follow the instructions in the following walkthrough:
How to Use WebDAV Locks http://learn.iis.net/page.aspx/596/how-to-use-webdav-locks/
If you were writing a WebDAV client, sending the LOCK/UNLOCK commands would help to avoid two clients attempting to author the same resource. So if your WebDAV client was editing a file named "foo.txt", the flow of events would be something like the following:
The updated code sample in this blog post shows how to send most of the common WebDAV requests using C# and common .NET libraries. In addition to adding the LOCK/UNLOCK commands to this version, I also changed the sample files to upload/download Classic ASP pages instead of text files; I did this so you can see that the WebDAV requests are correctly accessing the source code of the ASP pages instead of the translated output.
Having said that, I need to mention once again that I create more objects than are necessary for each section of the sample, which creates several intentional redundancies; I did this because I wanted to make each section somewhat self-sufficient, which helps you to copy and paste a little easier. I present the WebDAV methods the in the following order:
Here is the source code for the updated sample application:
using System; using System.Net; using System.IO; using System.Text; class WebDavTest { static void Main(string[] args) { try { // Define the URLs. string szURL1 = @"http://localhost/foobar1.asp"; string szURL2 = @"http://localhost/foobar2.asp"; string szURL3 = @"http://localhost/foobar3"; // Some sample code to put in an ASP file. string szAspCode1 = @"<%=Year()%>"; string szAspCode2 = @"<%=Time()%>"; // Some XML to put in a lock request. string szLockXml = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<D:lockinfo xmlns:D='DAV:'>" + "<D:lockscope><D:exclusive/></D:lockscope>" + "<D:locktype><D:write/></D:locktype>" + "<D:owner><D:href>mailto:someone@example.com</D:href></D:owner>" + "</D:lockinfo>"; // Define username, password, and lock token strings. string szUsername = @"username"; string szPassword = @"password"; string szLockToken = null; // --------------- PUT REQUEST #1 --------------- // // Create an HTTP request for the URL. HttpWebRequest httpPutRequest1 = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpPutRequest1.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpPutRequest1.PreAuthenticate = true; // Define the HTTP method. httpPutRequest1.Method = @"PUT"; // Specify that overwriting the destination is allowed. httpPutRequest1.Headers.Add(@"Overwrite", @"T"); // Specify the content length. httpPutRequest1.ContentLength = szAspCode1.Length; // Optional, but allows for larger files. httpPutRequest1.SendChunked = true; // Retrieve the request stream. Stream putRequestStream1 = httpPutRequest1.GetRequestStream(); // Write the string to the destination as text bytes. putRequestStream1.Write( Encoding.UTF8.GetBytes((string)szAspCode1), 0, szAspCode1.Length); // Close the request stream. putRequestStream1.Close(); // Retrieve the response. HttpWebResponse httpPutResponse1 = (HttpWebResponse)httpPutRequest1.GetResponse(); // Write the response status to the console. Console.WriteLine(@"PUT Response #1: {0}", httpPutResponse1.StatusDescription); // --------------- LOCK REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpLockRequest = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpLockRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpLockRequest.PreAuthenticate = true; // Define the HTTP method. httpLockRequest.Method = @"LOCK"; // Specify the request timeout. httpLockRequest.Headers.Add(@"Timeout", "Infinite"); // Specify the request content type. httpLockRequest.ContentType = "text/xml; charset=\"utf-8\""; // Retrieve the request stream. Stream lockRequestStream = httpLockRequest.GetRequestStream(); // Write the lock XML to the destination. lockRequestStream.Write( Encoding.UTF8.GetBytes((string)szLockXml), 0, szLockXml.Length); // Close the request stream. lockRequestStream.Close(); // Retrieve the response. HttpWebResponse httpLockResponse = (HttpWebResponse)httpLockRequest.GetResponse(); // Retrieve the lock token for the request. szLockToken = httpLockResponse.GetResponseHeader("Lock-Token"); // Write the response status to the console. Console.WriteLine( @"LOCK Response: {0}", httpLockResponse.StatusDescription); Console.WriteLine( @" LOCK Token: {0}", szLockToken); // --------------- GET REQUEST #1 --------------- // // Create an HTTP request for the URL. HttpWebRequest httpGetRequest1 = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpGetRequest1.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpGetRequest1.PreAuthenticate = true; // Define the HTTP method. httpGetRequest1.Method = @"GET"; // Specify the request for source code. httpGetRequest1.Headers.Add(@"Translate", "F"); // Retrieve the response. HttpWebResponse httpGetResponse1 = (HttpWebResponse)httpGetRequest1.GetResponse(); // Retrieve the response stream. Stream getResponseStream1 = httpGetResponse1.GetResponseStream(); // Create a stream reader for the response. StreamReader getStreamReader1 = new StreamReader(getResponseStream1, Encoding.UTF8); // Write the response status to the console. Console.WriteLine( @"GET Response #1: {0}", httpGetResponse1.StatusDescription); Console.WriteLine( @" Response Length: {0}", httpGetResponse1.ContentLength); Console.WriteLine( @" Response Text: {0}", getStreamReader1.ReadToEnd()); // Close the response streams. getStreamReader1.Close(); getResponseStream1.Close(); // --------------- PUT REQUEST #2 --------------- // // Create an HTTP request for the URL. HttpWebRequest httpPutRequest2 = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpPutRequest2.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpPutRequest2.PreAuthenticate = true; // Define the HTTP method. httpPutRequest2.Method = @"PUT"; // Specify that overwriting the destination is allowed. httpPutRequest2.Headers.Add(@"Overwrite", @"T"); // Specify the lock token. httpPutRequest2.Headers.Add(@"If", String.Format(@"({0})",szLockToken)); // Specify the content length. httpPutRequest2.ContentLength = szAspCode1.Length; // Optional, but allows for larger files. httpPutRequest2.SendChunked = true; // Retrieve the request stream. Stream putRequestStream2 = httpPutRequest2.GetRequestStream(); // Write the string to the destination as a text file. putRequestStream2.Write( Encoding.UTF8.GetBytes((string)szAspCode2), 0, szAspCode1.Length); // Close the request stream. putRequestStream2.Close(); // Retrieve the response. HttpWebResponse httpPutResponse2 = (HttpWebResponse)httpPutRequest2.GetResponse(); // Write the response status to the console. Console.WriteLine(@"PUT Response #2: {0}", httpPutResponse2.StatusDescription); // --------------- GET REQUEST #2 --------------- // // Create an HTTP request for the URL. HttpWebRequest httpGetRequest2 = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpGetRequest2.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpGetRequest2.PreAuthenticate = true; // Define the HTTP method. httpGetRequest2.Method = @"GET"; // Specify the request for source code. httpGetRequest2.Headers.Add(@"Translate", "F"); // Retrieve the response. HttpWebResponse httpGetResponse2 = (HttpWebResponse)httpGetRequest2.GetResponse(); // Retrieve the response stream. Stream getResponseStream2 = httpGetResponse2.GetResponseStream(); // Create a stream reader for the response. StreamReader getStreamReader2 = new StreamReader(getResponseStream2, Encoding.UTF8); // Write the response status to the console. Console.WriteLine( @"GET Response #2: {0}", httpGetResponse2.StatusDescription); Console.WriteLine( @" Response Length: {0}", httpGetResponse2.ContentLength); Console.WriteLine( @" Response Text: {0}", getStreamReader2.ReadToEnd()); // Close the response streams. getStreamReader2.Close(); getResponseStream2.Close(); // --------------- UNLOCK REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpUnlockRequest = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpUnlockRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpUnlockRequest.PreAuthenticate = true; // Define the HTTP method. httpUnlockRequest.Method = @"UNLOCK"; // Specify the lock token. httpUnlockRequest.Headers.Add(@"Lock-Token", szLockToken); // Retrieve the response. HttpWebResponse httpUnlockResponse = (HttpWebResponse)httpUnlockRequest.GetResponse(); // Write the response status to the console. Console.WriteLine( @"UNLOCK Response: {0}", httpUnlockResponse.StatusDescription); // --------------- COPY REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpCopyRequest = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpCopyRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpCopyRequest.PreAuthenticate = true; // Define the HTTP method. httpCopyRequest.Method = @"COPY"; // Specify the destination URL. httpCopyRequest.Headers.Add(@"Destination", szURL2); // Specify that overwriting the destination is allowed. httpCopyRequest.Headers.Add(@"Overwrite", @"T"); // Retrieve the response. HttpWebResponse httpCopyResponse = (HttpWebResponse)httpCopyRequest.GetResponse(); // Write the response status to the console. Console.WriteLine(@"COPY Response: {0}", httpCopyResponse.StatusDescription); // --------------- MOVE REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpMoveRequest = (HttpWebRequest)WebRequest.Create(szURL2); // Set up new credentials. httpMoveRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpMoveRequest.PreAuthenticate = true; // Define the HTTP method. httpMoveRequest.Method = @"MOVE"; // Specify the destination URL. httpMoveRequest.Headers.Add(@"Destination", szURL1); // Specify that overwriting the destination is allowed. httpMoveRequest.Headers.Add(@"Overwrite", @"T"); // Retrieve the response. HttpWebResponse httpMoveResponse = (HttpWebResponse)httpMoveRequest.GetResponse(); // Write the response status to the console. Console.WriteLine(@"MOVE Response: {0}", httpMoveResponse.StatusDescription); // --------------- DELETE FILE REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpDeleteFileRequest = (HttpWebRequest)WebRequest.Create(szURL1); // Set up new credentials. httpDeleteFileRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpDeleteFileRequest.PreAuthenticate = true; // Define the HTTP method. httpDeleteFileRequest.Method = @"DELETE"; // Retrieve the response. HttpWebResponse httpDeleteFileResponse = (HttpWebResponse)httpDeleteFileRequest.GetResponse(); // Write the response status to the console. Console.WriteLine(@"DELETE File Response: {0}", httpDeleteFileResponse.StatusDescription); // --------------- MKCOL REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpMkColRequest = (HttpWebRequest)WebRequest.Create(szURL3); // Set up new credentials. httpMkColRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpMkColRequest.PreAuthenticate = true; // Define the HTTP method. httpMkColRequest.Method = @"MKCOL"; // Retrieve the response. HttpWebResponse httpMkColResponse = (HttpWebResponse)httpMkColRequest.GetResponse(); // Write the response status to the console. Console.WriteLine(@"MKCOL Response: {0}", httpMkColResponse.StatusDescription); // --------------- DELETE FOLDER REQUEST --------------- // // Create an HTTP request for the URL. HttpWebRequest httpDeleteFolderRequest = (HttpWebRequest)WebRequest.Create(szURL3); // Set up new credentials. httpDeleteFolderRequest.Credentials = new NetworkCredential(szUsername, szPassword); // Pre-authenticate the request. httpDeleteFolderRequest.PreAuthenticate = true; // Define the HTTP method. httpDeleteFolderRequest.Method = @"DELETE"; // Retrieve the response. HttpWebResponse httpDeleteFolderResponse = (HttpWebResponse)httpDeleteFolderRequest.GetResponse(); // Write the response status to the console. Console.WriteLine(@"DELETE Folder Response: {0}", httpDeleteFolderResponse.StatusDescription); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }
When you run the code sample, if there are no errors you should see something like the following output:
PUT Response #1: Created LOCK Response: OK LOCK Token: <opaquelocktoken:4e616d65-6f6e-6d65-6973-526f62657274.426f62526f636b73> GET Response #1: OK Response Length: 11 Response Text: <%=Year()%> PUT Response #2: No Content GET Response #2: OK Response Length: 11 Response Text: <%=Time()%> UNLOCK Response: No Content COPY Response: Created MOVE Response: No Content DELETE File Response: OK MKCOL Response: Created DELETE Folder Response: OK Press any key to continue . . .
If you looked at the IIS logs after running the sample application, you should see entries like the following example:
#Software: Microsoft Internet Information Services 7.5 #Version: 1.0 #Date: 2011-10-18 06:49:07 #Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip sc-status sc-substatus sc-win32-status 2011-10-18 06:49:07 ::1 PUT /foobar1.asp - 80 - ::1 401 2 5 2011-10-18 06:49:07 ::1 PUT /foobar1.asp - 80 username ::1 201 0 0 2011-10-18 06:49:07 ::1 LOCK /foobar1.asp - 80 username ::1 200 0 0 2011-10-18 06:49:07 ::1 GET /foobar1.asp - 80 username ::1 200 0 0 2011-10-18 06:49:07 ::1 PUT /foobar1.asp - 80 username ::1 204 0 0 2011-10-18 06:49:07 ::1 GET /foobar1.asp - 80 username ::1 200 0 0 2011-10-18 06:49:07 ::1 UNLOCK /foobar1.asp - 80 username ::1 204 0 0 2011-10-18 06:49:07 ::1 COPY /foobar1.asp http://localhost/foobar2.asp 80 username ::1 201 0 0 2011-10-18 06:49:07 ::1 MOVE /foobar2.asp http://localhost/foobar1.asp 80 username ::1 204 0 0 2011-10-18 06:49:07 ::1 DELETE /foobar1.asp - 80 username ::1 200 0 0 2011-10-18 06:49:07 ::1 MKCOL /foobar3 - 80 username ::1 201 0 0 2011-10-18 06:49:07 ::1 DELETE /foobar3 - 80 username ::1 200 0 0
Since the code sample cleans up after itself, you should not see any files or folders on the destination server when it has completed executing. To see the files and folders that are actually created and deleted on the destination server, you would need to step through the code in a debugger.
This updated version does not include examples of the WebDAV PROPPATCH/PROPFIND methods in this sample for the same reason that I did not do so in my previous blog - those commands require processing the XML responses, and that is outside the scope of what I wanted to do with this sample.
I hope this helps!