1

I am trying to call a Web API method from a handheld device (Compact Framework) with this code:

// "fullFilePath" is a value such as "\Program Files\Bla\abc.xml"
// "uri" is something like "http://localhost:28642/api/ControllerName/PostArgsAndXMLFile?serialNum=8675309&siteNum=42"
SendXMLFile(fullFilePath, uri, 500);
. . .
public static string SendXMLFile(string xmlFilepath, string uri, int timeout) 
{
    uri = uri.Replace('\\', '/');
    if (!uri.StartsWith("/"))
    {
        uri = "/" + uri;
    }
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);

    request.KeepAlive = false;
    request.ProtocolVersion = HttpVersion.Version10;

    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;
        request.KeepAlive = false;

        request.ContentType = "application/x-www-form-urlencoded"; // not "text/xml" correct?

        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;
        }
    }
}

Somewhere in SendXMLFile(), it is failing with "NotSupportedException" though... As it's running on a handheld device, I can't put a breakpoint in it and step through it; I could sprinkle a bunch of debug statements throughout (MessageBox.Show()), but I'd rather not do that.

The server code never even reaches the breakpoint I put on the "XDocument doc =" line below:

[Route("api/ControllerName/PostArgsAndXMLFile")]
public void PostArgsAndFile([FromBody] string stringifiedXML, string serialNum, string siteNum)
{
    XDocument doc = XDocument.Parse(stringifiedXML); 

Is it that the Compact framework can't call a (RESTful) Web API method for some reason? Obviously, the client (handheld/Compact Framework) compiles and runs, it just refuses to actually follow through with the runtime realities of it all.

Does my code require a small alteration for it to fit, or do I need to take a completely different tack?

B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • Haven't reviewed your code yet but right off the bat...1. why can't you steo through the code on the device? I do that all the time. 2. What does Fiddler tell you when run on the server-side during the request? Did a request come through at all? – tcarvin Mar 07 '14 at 21:20
  • If `uri is something like http://localhost:28642/api/ControllerName/PostArgsAndXMLFile?serialNum=8675309&siteNum=42` and you then execute `uri = "/" + uri;`, doesn't that yield a malformed URL? – tcarvin Mar 07 '14 at 21:24
  • @tcarvin: "The server code never even reaches the breakpoint" – B. Clay Shannon-B. Crow Raven Mar 07 '14 at 22:02
  • @tcarvin: "steo through the code on the device? I do that all the time" My only response to that is: "Luck-E!" My travails of trying to do that were well documented on this site several months ago, and I never was able to do that. I can put breakpoints in the server app, but not on the handheld device. – B. Clay Shannon-B. Crow Raven Mar 07 '14 at 22:28
  • Don't recall that particular thread, but my condolences on not being able to debug on the device. – tcarvin Mar 10 '14 at 17:11
  • I would only expect an elephant to remember that; I barely recall it myself. – B. Clay Shannon-B. Crow Raven Mar 10 '14 at 17:15

2 Answers2

2

Web API is not going to be able to handle your body content. You declared it as application/x-form-urlencoded, but it is actually XML formatted and your method signature is expecting it to be a XMLDataContract serialized string.

Instead of using the parameter stringifiedXML, instead, just read the body inside your method..

[Route("api/ControllerName/PostArgsAndXMLFile")]
public async void PostArgsAndFile(string serialNum, string siteNum)
{
    XDocument doc = XDocument.Parse(await Request.Content.ReadAsStringAsync()); 
}

Or event better, use a stream directly.

[Route("api/ControllerName/PostArgsAndXMLFile")]
public async void PostArgsAndFile(string serialNum, string siteNum)
{
    XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync()); 
}

This way, you can put the ContentType on the client back to application/xml as it should be.

Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • The first example actually causes previously working code to fail. XDocument doc = XDocument.Load(await Request.Content.ReadAsStreamAsync()); System.Xml.XmlException was unhandled by user code HResult=-2146232000 Message=Root element is missing. – B. Clay Shannon-B. Crow Raven Mar 10 '14 at 21:13
  • 1
    @B.ClayShannon That can sometimes be caused because BOM in UTF encoding not getting processed correctly. I always have that problem when I try using WebClient to download XML as a string. – Darrel Miller Mar 10 '14 at 23:05
  • This looks good/promising, but what would the client side code look like? How is XML sent without referencing it in the REST URL? – B. Clay Shannon-B. Crow Raven Aug 22 '14 at 17:55
  • 1
    @B.ClayShannon Technically you should set the content-type to be appliication/xml however, that shouldn't change anything. The client code you show should send the the bytes from the file just fine. – Darrel Miller Aug 22 '14 at 18:56
0

Using Darrel's code on the server side (I'm using the second one, the Stream), this works on the Client side:

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;
        request.KeepAlive = false;

        request.ContentType = "application/x-www-form-urlencoded";

        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;
        }
    }
}

Which can be called like so:

private void buttonNose_Click(object sender, EventArgs e)
{
    String fullFilePath = @"C:\McMurtry\LonesomeDove.XML";
    String uri = @"http://localhost:21608/api/inventory/sendxml/ff/gg/42";
    SendXMLFile(fullFilePath, uri, 500);
}
B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862
  • That is to say, this works in a test app running on the same machine as the server; this is a "proof-of-concept" for the handheld code only, which must be "dumbed down" and cannot reference "localhost" (which is itself, the handheld device, not the PC to which it is attached). – B. Clay Shannon-B. Crow Raven Sep 11 '14 at 22:14