1

I feel like I am missing something obvious here. I have the following code that executes when a user submits a checkout form online:

Stream body = Request.InputStream;
Encoding encoding = Request.ContentEncoding;
StreamReader reader = new StreamReader(body, encoding);
string json = reader.ReadToEnd();

dynamic order = JObject.Parse(json);

This works great and it allows me to then easily reference all of the properties in the stream simply by referencing the order object. (e.g. order.CustomerName).

The Problem

My client has added the requirement that the user may upload a file such as a purchase order. I have added the code to upload the file without issue, but now the code block I posted above barfs if there is a file in the InputStream. Specifically, the error is "Input string '------' is not a valid number" which is thrown here:

dynamic order = JObject.Parse(json);

So, I assume that I need to somehow isolate Request.Params when there is a file in the InputStream and then run that through the code block above, but I am having a hard time getting down that road.

Any pointers, hints, or advice is welcome. Thanks.

Community
  • 1
  • 1
Matt Cashatt
  • 23,490
  • 28
  • 78
  • 111
  • How is the file being mixed in with the JSON? As part of the JSON object (and if so, how do you deal with escaping?), usng multipart/form-data, or something else? – Jon Hanna May 29 '14 at 22:10
  • @JonHanna--It is getting mixed in because I am reading the entire `InputStream` which leads to the question: how do I isolate the the `params` and just run those through the `StreamReader`? I can't put them directly into the `StreamReader` because `Request.Params` is a `NameValueCollection` and not a `stream`. Thanks. – Matt Cashatt May 29 '14 at 22:20

2 Answers2

1

Have you considered sending the JSON and file up as multipart/form-data, the same as you would if you were POSTing a file upload from an HTML form?

That way you would have what looks to the server as 1 or more HttpPostedFile objects in Request.Files (1 for the JSON, 1 each more for each file). Each of those HttpPostedFile objects would have its own InputStream. That for the first "file" can be used for body in code as per your question, and will work pretty much as it does now, while the other HttpPostedFile objects can be used much as is normal for file uploads.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
  • Thanks Jon, I really appreciate your input but ended up going a different direction in order to keep the client-side code from changing. I will post my ultimate approach as well in case it can help someone else. Again, thanks for your time. – Matt Cashatt May 30 '14 at 15:23
  • Actually, I am going to just accept your answer as it certainly isn't wrong. Thanks again. If someone else is reading this based on a similar problem, consider my answer below as well. Thanks again Jon! – Matt Cashatt May 30 '14 at 15:36
  • Cool. The approach you took isn't too far from my suggestion here anyway, in that both approaches involve examining the Files collection. Someone taking my approach could well end up with code very close to how yours is, bar only a few minor details. – Jon Hanna May 30 '14 at 15:38
1

Here is the solution I ultimately came up with thanks to the help of this Stack Overflow question:

If the POST included an uploaded file, then my code for handling the parameters changed from this:

dynamic order = new ExpandoObject();

...

Stream body = Request.InputStream;
Encoding encoding = Request.ContentEncoding;
StreamReader reader = new StreamReader(body, encoding);
string json = reader.ReadToEnd();

dynamic order = JObject.Parse(json);

To this:

dynamic order = new ExpandoObject();

...

 if (hasFile)
        {
            attachment = HttpContext.Current.Request.Files[0];

            var dict = NvcToDictionary(HttpContext.Current.Request.Form, false);
            foreach (var item in dict)
            {
                var _json = item.Value.ToString();
                ((IDictionary<string, object>)order)[item.Key] = JObject.Parse(_json);
            }    
        }

Note the use of the ExpandoObject type and the ((IDictionary<string, object>)order) part. This was necessary so that I could set the order variable dynamically.

Now that I look at this with a fresh brain, I see that I could have probably used a common approach--regardless of the presence of a file--for casting the form params to a dynamic object (using the hasFile logic). So if you are in need of a solution for the same problem, maybe just do that.

Here is the NvcToDictionary helper method from the related question:

static Dictionary<string, object> NvcToDictionary(NameValueCollection nvc, bool handleMultipleValuesPerKey)
    {
        var result = new Dictionary<string, object>();
        foreach (string key in nvc.Keys)
        {
            if (handleMultipleValuesPerKey)
            {
                string[] values = nvc.GetValues(key);
                if (values.Length == 1)
                {
                    result.Add(key, values[0]);
                }
                else
                {
                    result.Add(key, values);
                }
            }
            else
            {
                result.Add(key, nvc[key]);
            }
        }

        return result;
    }

Cheers!

Community
  • 1
  • 1
Matt Cashatt
  • 23,490
  • 28
  • 78
  • 111