About WebDAV: do it yourself ..

Facing an integration task using WebDAV brought me to take a look closer at WebDAV and corresponding tools for .Net platform. This is part of a short series:

-----

Why pay others do have fun and use the protocol? How hard could be to implement just the few pieces I need ..

What do I need ?

The simplest scenario you can get from WebDAV:

  • List folder contents, get name/urls, size, differentiate files & subfolders
  • upload a file from memory
  • download a file to memory

For testing purposes I use IIS 7.5 as my WebDAV server.

What IS WebDAV ?

Basically an extension of HTTP protocol to use web servers as file systems. It is described in IETF RFC 4918. A Quick overview can be get from wiki and there’s an official-like site webdav.org which provides more links and resources. 

how to Get List of contents ?

I could simply make an HTTP GET request and a html page from get web server containing links to its contents. But this is ugly and there is the webdav way using PROPFIND instead of GET.

PROPFIND relies on xml query sent with http request. RFC contains also some example queries. The query I need would be something like this:

<?xml version="1.0" encoding="utf-8"?>
<D:propfind xmlns:D="DAV:" xmlns:ns0="DAV:">
<D:prop>
<ns0:displayname/>
<ns0:iscollection/>
<ns0:getcontentlength/>
</D:prop>
</D:propfind>

To send the query to web server using PROPFIND in simple:

var request = (HttpWebRequest)HttpWebRequest.Create(targetUri);
request.UseDefaultCredentials = true;
request.Method = "PROPFIND";

var bytes = Encoding.UTF8.GetBytes(query);
using (var requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
request.ContentType = "text/xml";

But now you’ll get an error which is not the most helpful:
System.Net.WebException: The remote server returned an error: (403) Forbidden.

The resolve is to read the spec and find the mandatory “Depth” header which determines if you want properties only on the targeted item (value 0), with direct children (value 1) or recursively (value “infinity”). To add that header:

request.Headers.Add("Depth", "1");

Now the request is set up, reading the response gives as requested data in xml format:

using (var response = (HttpWebResponse)request.GetResponse())
using (var responseStream = response.GetResponseStream())
using (var responseContents = new MemoryStream(2048))
{
responseStream.CopyTo(responseContents, 2048);
return Encoding.UTF8.GetString(responseContents.ToArray());
}

The result could be something like this:

<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>http://imrepyhvel/webDavTest/</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:displayname>webDavTest</D:displayname>
<D:getcontentlength>0</D:getcontentlength>
<D:iscollection>1</D:iscollection>
</D:prop>
</D:propstat>
</D:response>
<D:response>
<D:href>http://imrepyhvel/webDavTest/bb.txt</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:displayname>bb.txt</D:displayname>
<D:getcontentlength>4</D:getcontentlength>
<D:iscollection>0</D:iscollection>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>

The tricky part is that different servers may return data in slightly different ways. For example folders may not have some properties (like “getcontentlength”) in which case there’s another “propstat”-block with different “D:status". RFC describes the possible statuses.

Another suggestion is to help clean up the HTTP communication and use these optional settings for HTTP request which obviously remove HTTP 100 and continuous handshaking:

request.PreAuthenticate = true;
request.ServicePoint.Expect100Continue = false;

How to download A file ?

Heey, you already know that. That's what HTTP GET method is about ..

How to upload a file?

This is less common but still simple:

WebRequest request = WebRequest.Create(targetPath);
request.UseDefaultCredentials = true;
request.Method = "PUT";

using (Stream stream = request.GetRequestStream())
{
contents.CopyTo(stream, 2048);
}
request.GetResponse();

What do I think ?

Most is simple HTTP action. The only tricky part is working with metadata (PROPFIND) but with help of RFC and google, you won't get stuck. If you are on low budget or only need simple scenarios then doing WebDav by yourself is not a problem.

The good:

  • what you do yourself is free
    • No licence fees
    • no limitations on deployment
    • no copyright or copyleft unless you want to create one.
  • Seems simple enough
  • know-how can be applied using every language capable of creating-sending-receiving HTTP traffic
  • Performance & traffic is as efficient as you write it
    • 3 tasks = auth challenge + 3 request-response pairs
    • approx 41 ms

image

The bad:

  • Requires some time to explore RFC and write/google the proof of concept codes
    • Some hours will do for simple scenarios.
  • Requires more time in case of problems
    • no support/bugfixes from outside
    • the reasons behind those uninformative 403s are hard to track down.
  • Requires more time to test
    • there will be quirks on different WebDAV servers as experienced by running my code in both IIS and on livelink folders.

About WebDAV: using Independentsoft client ..

Facing an integration task using WebDAV brought me to take a look closer at WebDAV and corresponding tools for .Net platform. This is part of a short series:

-----

Another commercial client - WebDAV .NET from Independentsoft, priced 299 EUR and up.

What do I need ?

The simplest scenario you can get from WebDAV:

  • List folder contents, get name/urls, size, differentiate files & subfolders
  • upload a file from memory
  • download a file to memory

For testing purposes I use IIS 7.5 as my WebDAV server.

What Does it do?

Homepage promises compatibility to WebDAV RFC 2518.

Only interesting fact from homepage is support for ANY .Net Framework, including Compact Framework and Mono.

What did I get ? / How did I do it ?

Once again, I achieved my supersimple goals.

Initializing the client:

var session = new WebdavSession();
session.Credentials = CredentialCache.DefaultNetworkCredentials;
var resource = new Resource(session);

This Resource thingy is weird, but I hope it makes sense to somebody. Now, fetching contents and showing only files ..

var selectProperties = new PropertyName[] { DavProperty.DisplayName, DavProperty.IsCollection, DavProperty.GetContentLength };
foreach (var item in resource.List(folderUri.ToString(), selectProperties))
{
if (item.Properties[DavProperty.IsCollection].Value == "1") { continue; }
Console.WriteLine("{0} ({1} bytes)",
item.Properties[DavProperty.DisplayName].Value,
item.Properties[DavProperty.GetContentLength].Value
);
}

I really like that I can enumerate the properties I’m interested in and the choice of valid names are accessible using DavProperty container. Downside is that the seemingly simple and universal API requires me to check for the existance of given properties in output (not done in sample) and parse-interpret the values. It is straightforward considering the protocol, but the resulting code is not the cleanest. Now, to downloading..

using (var stream = new MemoryStream())
{
resource.Download(folderUri.ToString() + "/bb.txt", stream);
var contents = Encoding.Default.GetString(stream.ToArray());
}

Almost as simple as it gets – download that URI into that stream. How about upload?

var newFileName = "NewFile_" + DateTime.Now.ToString("dd-HHmmss") + ".txt";
var content = "Whatever content";

using (var stream = new MemoryStream(Encoding.Default.GetBytes(content)))
{
resource.Upload(folderUri.ToString() + "/" + newFileName, stream);
}

Once again elegant -  data from this stream to this url.

What do I think ?

Simple, straightforward, efficient – exactly the way you want your libraries to be. On the downside for trickier parts you better (get to) know what happens in the protocol level. Using the default configuration it provided seemingly optimal traffic. If I had the licence, I would use it for my WebDAV needs.

The good:

  • decent API
    • default scenarios are made quite simple.
    • with xmldoc.
  • Commercial product but
    • Has evaluation option (30 days)
    • even entry level licence has royalty-free redistribution with your software.
    • source code option is available (for extra fee)
  • Seems fast
    • For example: list properties you are interested.
    • with default settings: 3 tasks = auth challenge + 3 request-response pairs in HTTP track
    • approx 42 ms

image

  • Supports Compact Framework & Mono
    • official Mono support is no common. A good cause!
  • Nice attitude from support
    • On evaluation request I got a mail from a specific developer in Independentsoft stating that if I have problems or need examples then he’ll try to help (“usually in few minutes”). It was an automated message, but still – encouraging!

The bad:

  • Commercial product
    • 300 EURs for starter is a bit heavy for simple needs in a single project
    • They do want your email for evaluation version.
  • Homepage says it is based on RFC 2518 which is an obsolete specification and is marked to be superseded by RFC 4918.
    • Not sure what’s the difference in real world but it is a sign of worry, because RFC 4918 seems to be from June 2007.
  • Code may be straightforward but not always as clean as it could be.
    • for example : reading property values after PROPFIND

About WebDav: using IT Hit .Net library ..

Facing an integration task using WebDAV brought me to take a look closer at WebDAV and corresponding tools for .Net platform. This is part of a short series:

-----

Even if inventing your own wheel could be fun, it’s not always reasonable thing to do. I couldn’t find any freeware trustworthy-looking clients with brief googling so the first candidate is a commercial client - WebDAV client library from IT Hit, whose pricing starts from 350 USD.

What do I need ?

The simplest scenario you can get from WebDAV:

  • List folder contents, get name/urls, size, differentiate files & subfolders
  • upload a file from memory
  • download a file to memory

For testing purposes I use IIS 7.5 as my WebDAV server.

What Does it do?

Homepage promises compatibility to WebDAV RFC 2518.

More interesting features include:

  • requires .Net Framework 2.0 or .Net Compact Framework 2.0 SP1
  • high-level API
  • versioning support (DeltaV)
  • resumable uploads-downloads

What did I get ? / How did I do it ?

As expected, I achieved my supersimple goals.

Initializing the client:

string license = File.ReadAllText("WebDav-.net client_License.xml");
var session = new WebDavSession(license);
session.Credentials = CredentialCache.DefaultNetworkCredentials;
IFolder folder = session.OpenFolder(folderUri); //work in this folder.

Note that licence information has to be applied in-code in xml format.

Creating a IFolder is not mandatory for all tasks but as I happen to work in a single specific folder then I’m reusing the same object and declaring it next to the master-object session. Now, let’s see what’s in that folder..

foreach (IHierarchyItem item in folder.GetChildren(false))
{
if (item.ItemType != ItemType.Resource) { continue; }
Console.WriteLine("{0} ({1} bytes)",
item.DisplayName,
((IResource)item).ContentLength
);
}

Note that API is not the MOST readable: that “false” means non-recursive, files are called “resource” and even though most common properties are present in IHierarchyItem, file size is not. Code sample excerpts on home page were of no use. Also, I could not find it from object during runtime because the assembly code is obfuscated. Whatever, solution was found only after digging into IT Hit sample application. Let’s go download a text file contents..

var downloadResource = folder.GetResource("bb.txt");
using (Stream stream = downloadResource.GetReadStream())
using (var reader = new StreamReader(stream))
{
var contents = reader.ReadToEnd();
}

There was sample code present and the simple task gets simple solution. I’m getting used to looking for Resources. Possibility to get a file by local name (as opposed to always working with full URI) is a nice touch. Next task is to upload a file ..

var newFileName = "NewFile_" + DateTime.Now.ToString("dd-HHmmss") + ".txt";
var content = "Whatever content";

var uploadResource = folder.CreateResource(newFileName);
using (var datastream = new MemoryStream(Encoding.Default.GetBytes(content)))
using (Stream uploadstream = uploadResource.GetWriteStream(datastream.Length))
{
datastream.CopyTo(uploadstream);
}

Create resource in folder and get stream to write to that resource – seems reasonable. The downside is that if I do not know my data stream length in advance then I could be in trouble. There probably are some workarounds, but these API calls just happen to have that limitation. Just beware.

What do I think ?

The API is high level and using code exercpts in IT Hit home page you could get most of your tasks done relatively fast. I would trust it to do the job, espectially trickier stuff.  Still - I didn’t get the feeling that “this is The Tool to use for WebDAV”. Far from it.

The good:

  • decent API
    • you really don’t need to know much about webDAV.
    • Some helpful abstractions and hiding of webdav properties are a helpful for quickstarters
  • Commercial product but
    • Has evaluation option (1 month)
    • even entry level has royalty-free redistribution with your software.
    • source code option is available (for extra fee ofc)
  • Seems reliable
    • I ran into very few error messages and the ones I did see were informative.
    • testapp worked without modifications also on Livelink WebDAV folder.
  • support Compact Framework

The bad:

  • Commercial product
    • You can’t even get evaluation version without registration.
  • Homepage says it is based on RFC 2518 which is an obsolete specification and is marked to be superseded by RFC 4918.
    • Not sure what’s the difference in real world but it is a sign of worry, because RFC 4918 seems to be from June 2007.
  • no xml doc
    • I’m not going to go to their website or open up a separate help-file just to see comments on parameters and to find out that class PropertyName is “WebDAV property name”. I want intellisense so I wouldn’t waste my time!
  • API could be better
    • I could not perform ANY tasks by intuition alone. Not even the “download that file”-task. Even worse – I had to dive into sample application code just to know how to get file size.
  • Code is obfuscated
    • runtime debugging won’t help you much (af.g==false? Oh, really?!)

image

  • default protocol usage is chatty and non-optimal.
    • for example: it queries for all properties when you care only about a few.
    • example of chattiness for the 3 simple tasks (took approx 100 ms)

image