I've currently got a requirement to transfer files over a web service. I would normally have used MTOM, but after reading Richardson & Ruby's excellent RESTful Web Services and especially their description of the Amazon S3 service, I decided to see how easy it would be to write a simple file upload service using a custom HttpHandler on the server and a raw HttpWebRequest on the client. It turned out to be extremely simple.
First implement your custom HttpHandler:
public class FileUploadHandler : IHttpHandler { const string documentDirectory = @"C:\UploadedDocuments"; public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { string filePath = Path.Combine(documentDirectory, "UploadedFile.pdf"); SaveRequestBodyAsFile(context.Request, filePath); context.Response.Write("Document uploaded!"); } private static void SaveRequestBodyAsFile(HttpRequest request, string filePath) { using (FileStream fileStream = File.Open(filePath, FileMode.Create, FileAccess.Write)) using (Stream requestStream = request.InputStream) { int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int byteCount = 0; while ((byteCount = requestStream.Read(buffer, 0, bufferSize)) > 0) { fileStream.Write(buffer, 0, byteCount); } } } }
As you can see, it's simply a question of implementing IHttpHandler. The guts of the operation is in the ProcessRequest message, and all we do is save the input stream straight to disk. The SaveRequestBodyAsFile method just does a standard stream-to-stream read/write.
To get your handler to actual handle a request, you have to configure it in the handers section of the Web.config file, for examle:
<httpHandlers> <add verb="*" path="DocumentUploadService.upl" validate="false" type="TestUploadService.FileUploadHandler, TestUploadService"/> </httpHandlers>
Here I've configured every request for 'DocumentUploadService.upl' to be handled by the FileUploadHandler. There are a couple of more things to do, first, if you don't want to configure IIS to ignore requests for non-existent resources you can put an empty file called 'DocumentUploadService.upl' in the root of your web site. You also need to configure IIS so that requests for .upl files (or whatever extension you choose) are routed to the ASP.NET engine. I usually just copy the settings for .aspx files.
On the client side you can execute a raw HTTP request by using the HttpWebRequest class. Here's my client code:
public void DocumentUploadSpike() { string filePath = @"C:\Users\mike\Documents\somebig.pdf"; string url = "http://localhost:51249/DocumentUploadService.upl"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Accept = "text/xml"; request.Method = "PUT"; using(FileStream fileStream = File.OpenRead(filePath)) using (Stream requestStream = request.GetRequestStream()) { int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int byteCount = 0; while ((byteCount = fileStream.Read(buffer, 0, bufferSize)) > 0) { requestStream.Write(buffer, 0, byteCount); } } string result; using (WebResponse response = request.GetResponse()) using (StreamReader reader = new StreamReader(response.GetResponseStream())) { result = reader.ReadToEnd(); } Console.WriteLine(result); }
Here too we simply stream a file straight into the request stream and then call GetResponse on the HttpWebRequest. The last bit just writes the response text to the console. Note that I'm using the HTTP method PUT rather than POST, that's because we're effectively adding a resource. The resource location I'm adding should be part of the URL. For example, rather than:
http://localhost:51249/DocumentUploadService.upl
it should really look more like:
http://localhost:51249/mikehadlow/documents/2345
Indicating that user mikehadlow is saving a file to location 2345. To make it work would simply be a case of implementing some kind of routing (the MVC Framework would be ideal for this).