Files Flying Over Wires
It is common to pass arguments inside the URI to REST methods, to wit:
http://localhost:21608/api/boomerangingTelescopicPhraseMaze/gus/gus/42
(where the "/gus/gus/42" part at the end of the URI are arg vals that are unpacked on the server, perhaps to be used as criteria vals in a query statement).
What if you need to send something, though, that is too long to fit within a URI, such as an entire file? You can theoretically use arcane and quasi-magical gyrations such as the "[FromBody]" and "[FromURI]" tags, but I discovered/uncovered a much easier way. The file is embedded within a stream and passed along with the HttpWebRequest
and then simply read (and saved to file, in the code I show) in a Controller within the server.
That about says it all, I reckon; here's the code:
Client Code
private void buttonIn_Click(object sender, EventArgs e)
{
String fullFilePath = @"C:\Platypi\Duckbills_JennyLind_CA.XML";
String uri = @"http://localhost:21608/api/dplat/sendxml";
SendXMLFile(fullFilePath, uri, 500);
}
public static string SendXMLFile(string xmlFilepath, string uri, int timeout)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.KeepAlive = false;
request.ProtocolVersion = HttpVersion.Version10;
request.ContentType = "application/xml";
request.Method = "POST";
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(xmlFilepath))
{
String line;
while ((line = sr.ReadLine()) != null)
{
sb.AppendLine(line);
}
byte[] postBytes = Encoding.UTF8.GetBytes(sb.ToString());
if (timeout < 0)
{
request.ReadWriteTimeout = timeout;
request.Timeout = timeout;
}
request.ContentLength = postBytes.Length;
try
{
Stream requestStream = request.GetRequestStream();
requestStream.Write(postBytes, 0, postBytes.Length);
requestStream.Close();
using (var response = (HttpWebResponse)request.GetResponse())
{
return response.ToString();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
request.Abort();
return string.Empty;
}
}
}
Bonus for Those Stuck in the Past
For older versions of .NET that do not allow the "using
" construct, you can do it this way-- add the following code between "requestStream.Close()
" and the brace at the end of the try
block:
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
return response.ToString();
}
finally
{
IDisposable disposableResponse = response as IDisposable;
if (disposableResponse != null) disposableResponse.Dispose();
}
Thanks to ctacke (AKA "The Windows CE/Compact Framework Whisperer") for the code above.
Server Code
public class DuckbillXMLController : ApiController
{
. . .
[Route("api/dplat/sendXML")]
public async void SendInventoryXML()
{
XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync());
String saveLoc = @"C:\Duckbills\PlatypiOfJennyLindCalifornia.xml";
doc.Save(saveLoc);
}
}
Not So Fast There!
As you can see, the URI and file paths/names are hard-coded; fixing this to be more elegant/flexible is left as an exercise to the hopefully veracious and hopelessly voracious reader.
Client Determines Saved Filename
Okay, I'll show a little of the "exercise left to the reader" code; If you want the Client to specify the file name to be saved on the server, you could do it the following way if you want to use the same name for the file:
Client Code
private void buttonIn_Click(object sender, EventArgs e)
{
String fullFilePath = @"C:\Platypi\Duckbills_JennyLind_CA.XML";
string justFileName = Path.GetFileNameWithoutExtension(fullFilePath);
String uri = String.Format(@"http://localhost:21608/api/dplat/sendXML/{0}", justFileName);
SendXMLFile(fullFilePath, uri, 500);
}
Note: Passing the entire filename (including the extension (.xml)) discombobulates the Controller method and causes it to fail/not be found. That is why Path.GetFileName()
is not good enough, and Path.GetFileNameWithoutExtension()
had to be used above.
Alternatively, you could generate the file name by which to save the file on the server and pass that.
Server Code
[Route("api/dplat/sendXML/{filename}")]
public async void SendInventoryXML(String fileName)
{
XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync());
String saveLoc = String.Format(@"C:\Duckbill\{0}.xml", fileName);
doc.Save(saveLoc);
}
Server Determines Saved Filename
If you want the Server to generate/determine the file name to use, you can leave the Client code as originally shown, and change the Server code to something like this:
[Route("api/dplat/sendXML")]
public async void SendInventoryXML()
{
XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync());
String fileName = "someEnchantedXMLFileName"; String saveLoc = String.Format(@"C:\Duckbill\{0}.xml", fileName);
doc.Save(saveLoc);
}
Not So Slow There!
If you find that this tip saves you time, recognizing that time is money, please send me 3.14% of the gross or 42% of the net amount thereby saved. If that is problematic, impossible, or out of the question, a homemade gooseberry pie would suffice.