Custom WebDAV Server Using .NET

As I discussed in a previous post "ASP.NET Web Application without ASPX Extension", I’ve been working on a custom WebDAV server for a client. The initial proof-of-concept was just to see if we could get some .NET code that would respond to all paths for a given web application. From there I had to make WebDAV work.

What is WebDAV

To recap: WebDAV is an IETF Standard Protocol. It is an extension to the ubiquitous HTTP standard, adding on request methods to get property information about files and folders, make folders (called collections in WebDAV parlance), change properties, lock files, etc. Basically anything that you would want on a distributed file system. WebDAV is natively supported by Windows 2000 and XP, Mac OS X, and Linux (using davfs). When using this native support, you’re able to interact with WebDAV just like you are using a local filesystem or a network share. There are also many standalone client implementations that act more like FTP clients.

Why Would You Write Your Own DAV Server?

Sharing files over a network is nothing new of course. Using Windows File Sharing, many people are used to using network shares to store files on a server and to share with their coworkers. The limitation of most of these file sharing systems is that they share exactly what is on the filesystem. What if you want to integrate file sharing with an application that stores file meta-data in a database? What if you want to programmatically control the files and folders that you show to your users? By building your own WebDAV server you can query a database to get all of the meta-data (and even file contents) to serve to your users.

How To Build a WebDAV Server

I did quite a bit of searching and found WebDAV server implementation in Python, Ruby, and Java, but could not find anything done in a .NET language which my client uses for all of their application infrastructure. So, I built one!

As I mentioned in my previous post, we can take over the complete HTTP request for all requested paths. I use a Command Pattern to handle the different types of WebDAV requests. The Commands are responsible for getting information from the HTTP Request that they need to fulfill the request and for writing the proper HTTP Response. Each of the Commands delegates the actual File manipulation to a set of provider classes. Those classes are accessed through an Abstract Factory which allows for me to easily change the backend implementation. This allows for a good separation of concerns. The WebDAV Service layer knows how to read and write WebDAV and the File Provider layer knows how to get and store information about files and folders. (The abstraction between the Commands and the File Providers is a bit leaky still, but I’m confident I’ll be able to refactor it into a cleaner separation.)

The first backend provider that I created for testing was a FileSystem provider. I’ve mapped the WebDAV server to a specific directory and use standard System.IO commands to manipulate files. This is a lot easier because it allowed me to focus on implementing the protocol without worrying about interacting with a less familiar data access layer.

Test Your Implementation

The WebDAV folks have built a great test suite called Litmus that you can use to validate your server implementation. It performs all of the basic operations on files and collections to validate that the server performs to the specification. Start with Litmus and you’ve got your entire functional test harness written for you. That is some TDD in action!
Litmus goes a long way to get you to protocol compliance, but it took a bit of cleaning and tweaking before Windows Explorer would happily mount the Web Folder.

In addition to Litmus I found a couple of other tools really handy for debugging:

  • Ethereal – a very nice protocol analyzer that will let you inspect a series of requests and responses.
  • DAVExplorer – a Java WebDAV client that will write detailed logs of requests and responses.

Don’t be afraid to write your own WebDAV server, it’s really fun!

References

WebDAV Reference Book:
WebDAV: Next-Generation Collaborative Web Authoring by Lisa Dusseault

89 thoughts on “Custom WebDAV Server Using .NET”

  1. Anton,
    Please take a look at my other WebDAV article for information and links about the Mini-Redirector.

    404 is a Not Found error
    403 is Forbidden

    I can’t comment specifically on Germinal’s code as I haven’t looked at it much. If it’s not allowing PROPFIND on a folder, then it is broken. I would recommend you see if there is a mailing list for that and see if you can ask a question.

    I would double check the IIS mappings for the IHttpModule though. By default IIS is only going to pass requests to things that have an extension. You need to map the IIS request to handle all incoming request or using URL rewriting to get that to work. See my article about mapping asp.net applications without extensions for some information on that.

  2. Hi Zorched.
    I’ve been developing my own web dav server for a while. A few days ago I tried to deploy it to IIS 6.0 under windows 2003.

    A few days of pain, and finally I managed to see webdav folder contents through DavExplorer.
    Then I tried to map the drive using Windows Explorer. And here Im stuck now. Explorer actually mounts a network drive, but for some reason it is not a web folder but a usual network folder \\server\folder. So when I try to see its contents from explorer, it does not try to communicate with it as with web folder using mini redirector.
    At the same time I can successfully mount a directory from the URL created by Visual Studio.

    Any ideas how to fix that issue?

  3. I’ve built a fully functional WebDAV server (though it may not be fully up to the specifications at webdav.org; I’ve built it for Windows XPs WebDAV client) using the techniques you’ve described and I must say I’m excited when I see the result! Works great together with SQL Server, and with a web interface to manage versions it will be a great end-user experience.

    But it’s when I wish to deliver the service to an end-user I run into problems. I determine if an incoming request is a normal request or a WebDAV request using a flag (eg. “filelibrary”) in the request URL. This works great on my development machine, but when I set it up on another server and add a web folder in windows to a public URL, it does not work. Let me clarify with an example: If I use a local webbserver, and add a web folder with a url like “http://localhost:1067/TemplateBusiness/filelibrary” it works great. The “filelibrary”-flag is found in the URL and therefore the request is a WebDAV request. But when I place the application on a production server, and try to add a webb folder using a URL like “http://www.mydomain.com/filelibrary” I have no luck at all. Internet Explorer tells me it’s not a WebDAV server (up yours, IE =), because I know it is) without even sending a request to my server! I log all requests, but there are no incoming requests at all! You have any ideas why this happens? What I know is that it got nothing to do with DNS or other problems, all other requests for web pages goes fine (and is logged).

    Thanks in advance!

  4. Thanks for your reply.

    Yes, I’ve seen that post, but the strange part is that the client does not even send a OPTIONS-request to the root node of the URL. I’ve placed Request.SaveAs(“file.txt”) in my BeginRequest-eventhandler but there is nothing in that file after the webfolder mapping fails.

    Normal HTTP requests to the same website is being saved in the file though…

  5. Jonas,
    Well, the OPTIONS command will get sent to the ROOT of the web server, so it will not get sent to your code or handler. I had to manually modify the properties of IIS to return PROPFIND, etc when an OPTIONS command is issued to any path.

    So it won’t send it to “http://www.mydomain.com/filelibrary/”, but to “http://www.mydomain.com/” instead. You probably don’t have .NET code running there to capture the request?

    If that’s not what you meant, then I’m not entirely sure…

  6. You’re right, because of some strange reason my .NET-code does not catch request sent to the root. If I enter the domain http://www.mydomain.com/ it redirects to http://www.mydomain.com/Default.aspx though. Seems IIS takes care of all root requests so that it can redirect them to the correct standard page. Makes no difference if I disable standard pages either.

    Thanks for your help this far; would be great if you gave a little more details on how you configured IIS to respond to OPTIONS-requests, that seems to be what I’m missing here. Alternatively I should get the .NET-runtime to handle root requests as well, but I can’t find out what I’m missing there.

  7. Aaha, I hadn’t seen that article. Now it works perfectly. Looked a little different here compared to those screenshots though, but basically I did what you did. Removed all “interpreters” and then added the aspnet_isapi.dll to all. Now my .NET HttpModule catches ALL incoming request, no matter what path is requested. I did not configure IIS to answer to certain verbs though (like OPTIONS or PROPFIND), I let .NET handle everything instead.

    Thanks for your help and continue blogging! (you might consider tagging up that article you tipped me about with the “webdav” tag aswell, might help others)

  8. Hi Geoff,

    Under IIS 6, mapping an isapi extension to .* doesn’t work. How can I get around that? And could you help me on the way with some DAV Server source code?
    Thanks!

  9. Marc,
    Under Windows Server 2003: .* is not allowed, there is an extra ‘all extensions’ section where you can add the aspnet_isapi.dll.

    Let me know what questions you have about the code, I hope I can help.

  10. Geoff, did you build your webdav server with asynchronous transfers? I’m a bit curios on how to do that. As it is now, im using Response.BinaryWrite to send a file to the client and Request.InputStream to receive a file. When receiving a file, the client does not show any progress on the current file.

    Any tips or ideas would be greatly appreciated.

  11. Jonas,
    The HttpContext is handled asynchronously in the fact that it responds to BeginRequest events. Actually writing the data doesn’t need to be done asynchronously though. All that would do would allow your application to remain responsive, but since it’s already handling each request in a separate thread, that’s not an issue.

    How are you sending data?

    The following code snippet is a standard method of writing buffered data in chunks to an output stream. Maybe that will help?


    byte[] buf = new byte[1024];
    int read = 0;
    using (Stream outs = resp.OutputStream) {
    do {
    read = content.Read(buf, 0, buf.Length);
    outs.Write(buf, 0, read);
    } while (read > 0);
    outs.Flush();
    }

  12. This is how I am sending data to the client:

    if (!data.IsBinaryDataNull) {
    response.BinaryWrite(data.GetBinaryDataReference());

    And this gives no status what-so-ever to the client, so I will try the code you suggested instead.

    But that still leaves me with the problem when the client sends (puts) data to the server… can I receive that in chunks aswell? I suppose it doesn’t matter if the Request.InputStream is read in chunks… anyhow, this is my code (almost pseudo-code, there are some more logic but this is the core):

    // Get the data from the request
    data = new byte[request.InputStream.Length];
    request.InputStream.Read(data, 0, data.Length);
    request.InputStream.Close();
    request.InputStream.Dispose();
    // Save the data into the database through the filedata-object
    filedata.SetBinaryDataReference(ref data);
    filedata.Insert();

    Thanks for your quick answers and your patience!

  13. Jonas,
    If you make your byte array the same length as the input stream, then you are not reading it in chunks. If you notice in my previous example, I read/write in a 1k chunk. That’s done in a loop so you get the whole thing in 1k chunks.

    Basically you just change the input stream and the output stream to change from reading to writing.

    When dealing with IO, you should also always use a using() block or a try/finally block and close the stream in the finally block, otherwise if an exception is thrown the IO won’t get closed properly.

  14. I just recently discovered that SharePoints WebDAV does not show status on ongoing transfers either (on large, single files that is). Seems to be a limitation in the Windows WebDAV-client.

    I am aware of that my data is not read/written in chunks. What I am wondering is why I would bother – there is no difference to the client, right? Also, I think I will have a hard time writing data in chunks into my database, but I used your example when reading and outputting data to the client. That could be done with the GetBytes()-method on the SqlDataReader-object.

    I do not have try-catch-blocks in my code because I am using a database as the back data store.

    Have you tried your WebDAV-solution on Windows Vista? Mine did not work :P

  15. I have a problem with WebDav on Windows Vista Enterprice (in Windows XP all work normal) Did you meet the same problem

  16. A sharepoint web folder contains a nicer looking interface then the basic web dav – web folder. Do you have any idea how they accomplish this. It looks like an html page or a stylesheet. does webdav support sending a folder template to use?

  17. Steve,
    I don’t think that there is any way to change the look of a WebDAV folder. The protocol does not have any means of returning view related information back to the client. Basically all it does is describe the files and folders, it’s up to the WebDAV client how to display that information. Generally that display is the same as if you were looking at local files. So on Windows it looks like Explorer, and on Mac OS X it looks like the Finder.

  18. Steve Thomas: I think the SharePoint web folder looks different because it “pretends to be” a normal windows file share.

    I’m having problems with Windows Vista (as Pascali does) and WIndows Server 2003, really strange. My IIS-logs shows nothing, seems the client never even tries to connect.

  19. Hi,

    I have been developing a webdav server in .net with a backend SQL store. It all works great ( thanks to a number of your tips ). I was wondering however if you (or any of your readers) had worked out how to get the properties out of a word document. If I load a word document from my webdav server and then select file->Properties then MS word fires a PROPFIND back to my server asking for a “save-profile-form-location” property, and word load a single “OK” dialog that says “Verifying Web Properties”.

    Word sends a PROPFIND request that looks like this.


    and I am current replying as below but have tried lots of different schemas

    test.doc

    HTTP/1.1 200 OK

    /WDClient/profile.doc

    I have tried to return all sorts of data (href, htm , aspx etc etc ) back to word via my webdav server to get word to do anything but it seems to totally ignore my servers PROPFIND response for this property and loads the properties dialog as normal. I was hoping that this would give me some way of prompting the user for custom properties that I could save on the server.

    Just wondering if you had come across this. I am using Office 2003.

  20. Sorry my propfind headers got mangled here they are again, with [ ]

    Word Sends

    [a:propfind xmlns:a="DAV:" xmlns:b="urn:schemas-microsoft-com:datatypes"]
    [a:prop xmlns:c="urn:schemas-microsoft-com:office:office"]
    [c:save-profile-form-location /]
    [/a:prop]
    [/a:propfind]

    I am replying

    [D:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882" xmlns:MS="urn:schemas-microsoft-com:office:office" xmlns:D="DAV:"]
    [D:response]
    [D:href]test.doc[/D:href]
    [D:propstat]
    [D:status]HTTP/1.1 200 OK[/D:status]
    [D:prop]
    [MS:save-profile-form-location]/WDClient/profile.doc[/MS:save-profile-form-location]
    [/D:prop]
    [/D:propstat]
    [/D:response]
    [/D:multistatus]

  21. Simon,
    To start with, you might want to use the same namespace that is being sent to you in your reply. So, instead of MS:save-profile-form-location, try using c:save-profile-form-location.
    Second, you’ll need to find out what it means – what data it expects to come back. It’s likely that it doesn’t just want a path to a file, but something else.

    Your best bet is to probably call Microsoft support. I couldn’t find any details about it on the web.

  22. Geoff,

    May I ask how you solved the authentication with your WebDAV-server? In my implementation I am using forms authentication with a “remember me” option. Then Windows XPs and Windows 2000s WebDAV clients sends the remembered authentication information, and the user is identified. But in Windows Vista, this forms authentication is not sent when accessing the WebDAV-folder. Vista uses a new client (Mini-Redirector-6 or something like that), and I guess that is why it does not work anymore.

    Is there another way to authenticate users? I wish to provide a single-point-of-logon, and that I can do today with forms authentication.

    Thanks in advance!

  23. Jonas,
    I used Digest authentication. You could also use HTTP Simple Authentication. Digest is more secure if you are running on an unencrypted connection. The WebDAV spec says that “WebDAV applications MUST support the Digest authentication scheme”. So, I’d got that route if I was you. If you send an HTTP 401 Unauthorized response to the client with the proper WWW-Authenticate header, Windows should automatically show a dialog box.

    HTTP Basic and Digest Authentication RFC

    I think it’s a bit of an accident that a user that logged into a web application and got a cookie would be able to authenticate against your WebDAV server. I wouldn’t rely on that.

  24. Thanks for the advice, I’ve now implemented Digest Authentication according to the RFC specifications and it works great! I removed all form-based logins so now Http Authentication is used for the whole web application and for the WebDAV-server, looks exactly like SharePoint.

  25. I’m adding this post because this blog post is the one I first saw when searching around for some code and information to help implement a webdav server. The solution I implemented will not suit everyone but for those that may benefit the code is on CodeProject (http://www.codeproject.com/useritems/NET_WebDAV_Server.asp).

    I’ve no doubt there’s better code out there but it may give someone a head start.

    I think I’ve the answer to a couple of the questions posted here. One is about being able to use a pretty file list that Excel and Word present when interacting with Sharepoint. The short answer is that you can’t. Although Sharepoint is a WebDAV server, it’s also Frontpage server and, in fact, the Office tools use the Frontpage RPC protocol when communicating with Sharepoint. You can see this if you install Fiddler2 (www.fiddler2.com) a free local proxy implemented by a Microsoft employee that allows you to see all headers and body information sent to and received from an HTTP server. Because this is a proprietary protocol, Microsoft can and do extend it to do things that are useful to them such as return a nice file listing.

    Whenever any of Microsoft’s re-directors are used they will default to use Frontpage and this probably explains why the MS header must be returned in response to the OPTIONS verb.

    One of the regular question is about installing a WebDAV server. The example WebDAV server in the CodeProject article can be installed either at an IIS root or at a virtual directory. In principle it shouldn’t matter where the WebDAV is installed. The Microsoft WebDAV clients will issue an OPTIONS request to the URL specified and issue a PROPFIND if the OPTIONS response is satisfactory. If it’s not, they will then query the web server root. The chances are that this will generate a good response and so Excel or Word will try to work with it though the result amy not be what you expect.

    As suggested here, the WebDAV server is implemented as a IhttpHandler so that the WebDAV serve can be installed using a wildcard map.

  26. I have configured WebDAV Application on my server win 2003 and everything working fine but i am not able to set password protectet. plz help me how to over come this problem. ASA

  27. Hi

    I want to send documents generated by a buisiness process to different clients by FTP over SSL. That applicaion is working fine. But a few of the clients do not have FTP server but using secure HTTP (WebDAV). I need to copy files from folders on local drive or IIS virtual folders to remote web site folders.

    The code I found on MSDN is for copying files from one folder to another on the same web site. In my case I need to copy it either from a local folder or virtual folder in localhost. In the MSDN code I couldn’t figure out a way of providing different credential for accessing resources from localhost and remote host.
    Can someone help me to fix the code. Or can someone suggest a different way of doing it in .NET?

    [STAThread]
    static void Main(string[] args)
    {
    System.Net.HttpWebRequest Request;
    System.Net.WebResponse Response;
    System.Net.CredentialCache MyCredentialCache;
    string strSourceURI = “http://localhost/Extracts/Client1/test.csv”;
    string strDestURI = “https://200.150.100.50/Uploads/test.csv”;
    string strUserName = “UserName”;
    string strPassword = “password”;
    string strDomain = “Domain”;

    try
    {
    // Create a new CredentialCache object and fill it with the network
    // credentials required to access the server.
    MyCredentialCache = new System.Net.CredentialCache();
    MyCredentialCache.Add( new System.Uri(strSourceURI),
    “NTLM”,
    new System.Net.NetworkCredential(strUserName, strPassword, strDomain)
    );

    // Create the HttpWebRequest object.
    Request = (System.Net.HttpWebRequest)HttpWebRequest.Create(strSourceURI);

    // Add the network credentials to the request.
    Request.Credentials = MyCredentialCache;

    // Specify the COPY method.
    Request.Method = “COPY”;

    // Specify the destination URI.
    Request.Headers.Add(“Destination”, strDestURI);

    // Specify that if a resource already exists at the
    // destination URI, it will not be overwritten. The
    // server would return a 412 Precondition Failed status code.
    Request.Headers.Add(“Overwrite”, “F”);

    // Send the COPY method request.
    Response = (System.Net.HttpWebResponse)Request.GetResponse();

    // Close the HttpWebResponse object.
    Response.Close();

    Console.WriteLine(“Item successfully copied.”);

    }
    catch(Exception ex)
    {
    // Catch any exceptions. Any error codes from the COPY
    // method request on the server will be caught here, also.
    Console.WriteLine(ex.Message);
    }
    }
    }

    Many thanks.

  28. Hi Zorched,

    I am a .Net guy and i am given the job to create a custom webdav which will display contents anf folders from the content management repository.I googled a lot but still i am struggling to get the sarting point .From the knowledge i gained from broswing there a two protocols namels FPRPC and webdav which exposes contents as webfolders and Office products can understand the req from these protocols .Can u please help to me create a custom webdav where i have to start with

    Thanks,
    Maddy

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>