Wednesday, January 28, 2009

Uploading a file to RESTful WCF Services

.NET 3.5 makes it easy to create RESTful services that can be accessed via GETs but POST operations prove more difficult. I wanted to prove out a way to upload a file to a REST service by passing a stream and additional information about the file. The following is one way you can go about accomplishing this.

The Stream object is an easy way to send binary data but when used in a contract it is the only parameter allowed. To pass the additional information I simply made use of parameters passed in to the url.

Our contract:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "UploadFile")]
void UploadFile(Stream data);



Our service method will access the query string and read from the stream object:


public void UpoadFile(Stream data)
{

//get the query string passed in
OperationContext context = OperationContext.Current;
MessageProperties properties = context.IncomingMessageProperties;
HttpRequestMessageProperty requestProperty = properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
string queryString = requestProperty.QueryString;


//put the values into a collection
NameValueCollection coll = HttpUtility.ParseQueryString(queryString);
string fname = coll["filename"];

//now read our data in chunks
int bytesread = 0;
int size = 8192;
using (FileStream fs = new FileStream(string.Format("./{0}", fname), FileMode.Create))
{
byte[] chunk = new byte[size];
bytesread = data.Read(chunk, 0, size);

while (bytesread > 0)
{
fs.Write(chunk, 0, bytesread);
bytesread = data.Read(chunk, 0, size);
}
}
}



Now that we have our service we can invoke it from the client:


//create our client web request object
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(new Uri("http://localhost:49190/uploadservice/Service.svc/UploadFile?filename=test1.jpg"));
req.ContentType = "application/octet-stream";

req.Method = "POST";
Stream s = req.GetRequestStream();


//read our file into an array and write it out to our stream
using (FileStream fs = new FileStream("c:\\test1.jpg", FileMode.Open))
{
byte [] bytes = new byte[fs.Length];
fs.Read(bytes, 0, (int)fs.Length);
s.Write(bytes, 0, bytes.Length);
s.Close();
}


//invoke the request
WebResponse r = req.GetResponse();


Now we have a functioning upload service. The major downfall is the disconnect between the parameters passed in to the query string and the parameters assumed by the service. I'd love to hear other options that people have explored.