10

I have an ASP.NET application that presents a simple form to upload files (images). That looks like this:

public IActionResult Process()
{
    return View();
}

[HttpPost]
public IActionResult Process(List<IFormFile> files)
{
    var telemetry = new TelemetryClient();
    try
    {
        var result = files.Count + " file(s) processed " + Environment.NewLine;
        foreach (var file in files)
        {
            result += file.FileName + Environment.NewLine;
            var memoryStream = new MemoryStream();
            file.CopyTo(memoryStream);
            memoryStream.Seek(0, SeekOrigin.Begin);
            var binaryReader = new BinaryReader(memoryStream);
            var bytes = binaryReader.ReadBytes((int)memoryStream.Length);

            var imageInformation = ImageService.ProcessImage(bytes);

            ImageService.SaveImage(imageInformation.Result, bytes, file.FileName.Substring(file.FileName.LastIndexOf(".", StringComparison.Ordinal) + 1));
        }

        return View((object)result);
    }
    catch (Exception ex)
    {
        telemetry.TrackException(ex);
        throw;
    }
}

This form in the application works fine. The problem is that I want to use Microsoft Flow to submit files that come into a SharePoint library over to the web application defined above.

I have the file flow setup and it runs and doesn't error out, but when I look at the body of the HTTP action's result it says 0 files processed and nothing gets done.

The Flow that I have setup is

  1. When a file is created (SharePoint) (this is pointing to a specific document library
  2. Http (Http), Method: Post, Uri (pointing to my app), Body: File Content from the SharePoint step above.

As I mentioned this is posting to the site, but must not be passing in the file in a way that the ASP.NET method can handle, so it is not processing anything. How can I change either the flow or the Post method, so that it will work.

Updated with new information I have tried this with a very small image, so I can get some additional Request information. Using the form in the browser I tried this and going the following Request Raw result using Fiddler:

POST https://os-gbsphotoretain.azurewebsites.net/Image/Process HTTP/1.1
Host: os-gbsphotoretain.azurewebsites.net
Connection: keep-alive
Content-Length: 924
Pragma: no-cache
Cache-Control: no-cache
Origin: https://os-gbsphotoretain.azurewebsites.net
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySjQVgrsvAqJYXmST
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: https://os-gbsphotoretain.azurewebsites.net/Image/Process
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Cookie: _ga=GA1.3.955734319.1501514097; ai_user=UkqSf|2017-07-31T15:17:38.409Z; ARRAffinity=1628d46398b292eb2e3ba76b4b0f1eb1e30abd9bd1036d7a90b9c51f7baa2306; ai_session=/fPFh|1502738361594.15|1502738361594.15

------WebKitFormBoundarySjQVgrsvAqJYXmST
Content-Disposition: form-data; name="files"; filename="printer.jpg"
Content-Type: image/jpeg

     JFIF  ` `     C            



 $.' ",#(7),01444'9=82<.342   C         


2!!22222222222222222222222222222222222222222222222222     "                 

       } !1AQa "q2   #B  R  $3br    
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz                                                                                       

       w !1AQ aq"2 B        #3R br 
$4 % &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz                                                                             ?  
    +X K     21 c Z  ] ӥg v  ; :          P     I f >   m;] ֬u nm   ` Q 1 P6 s 9 |b r|   G  
------WebKitFormBoundarySjQVgrsvAqJYXmST--

Doing the same image through flow I get the following as the body in flow:

{
  "$content-type": "image/jpeg",
  "$content": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD1C9EMuqzGK1juS+3P7rccgc4yMYxjv1q/ol0I4bfTpQVniXaoyDuQHjoTg7ccGsDU7O+0+xEdoJfMUKiKE84MB/dJ5B9mzj6VneFtO1271qx1G+hubaGBjmCSUfMSMZZQNoxzgDnPfGKqcnypEJW1R//Z"
}

So it looks like flow is submitting as JSON. I'm going to try some additional processing now as a test, but if anybody knows what I can put in the Web app to handle this I would greatly appreciate it.

I added a new method see below that works when I run it locally passing in the string that Flow says is the body. But when I run it from flow I get value cannot be null error in the DeserializeObject line. How can I get the information that Flow is passing in.

[HttpPost]
    public IActionResult ProcessJson(string json)
    {
        var telemetry = new TelemetryClient();
        try
        {
            var result = "JSON processed " + Environment.NewLine;
            var details = (dynamic)Newtonsoft.Json.JsonConvert.DeserializeObject(json);
            var content = (string) details["$content"];
            var bytes = Convert.FromBase64String(content);

            ProcessBytes(bytes, "jpeg");
            return View("Process", result);
        }
        catch (Exception ex)
        {
            telemetry.TrackException(ex);
            throw;
        }
    }

I have also tried a method with this signature, but no luck there either it comes in as null

        [HttpPost]
        public IActionResult ProcessJson([FromBody]FlowFile file)
        {
...
        }



 public class FlowFile
    {

        [JsonProperty(PropertyName = "$content-type")]
        public string ContentType { get; set; }
        [JsonProperty(PropertyName = "$content")]
        public string Content { get; set; }
    }

I added some middleware, so that I could get the raw Request.Body and the end result that comes from that is this. I'm not sure what this equates to.

&#xD;&#xA;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#x0;&#x10;JFIF&#x0;&#x1;&#x1;&#x1;&#x0;`&#x0;`&#x0;&#x0;&#xFFFD;&#xFFFD;&#x0;C&#x0;&#x8;&#x6;&#x6;&#x7;&#x6;&#x5;&#x8;&#x7;&#x7;&#x7;&#x9;&#x9;&#x8;&#xA;&#xC;&#x14;&#xD;&#xC;&#xB;&#xB;&#xC;&#x19;&#x12;&#x13;&#xF;&#x14;&#x1D;&#x1A;&#x1F;&#x1E;&#x1D;&#x1A;&#x1C;&#x1C; $.&#x27; &quot;,#&#x1C;&#x1C;(7),01444&#x1F;&#x27;9=82&lt;.342&#xFFFD;&#xFFFD;&#x0;C&#x1;&#x9;&#x9;&#x9;&#xC;&#xB;&#xC;&#x18;&#xD;&#xD;&#x18;2!&#x1C;!22222222222222222222222222222222222222222222222222&#xFFFD;&#xFFFD;&#x0;&#x11;&#x8;&#x0;&#x10;&#x0;&#x10;&#x3;&#x1;&quot;&#x0;&#x2;&#x11;&#x1;&#x3;&#x11;&#x1;&#xFFFD;&#xFFFD;&#x0;&#x1F;&#x0;&#x0;&#x1;&#x5;&#x1;&#x1;&#x1;&#x1;&#x1;&#x1;&#x0;&#x0;&#x0;&#x0;&#x0;&#x0;&#x0;&#x0;&#x1;&#x2;&#x3;&#x4;&#x5;&#x6;&#x7;&#x8;&#x9;&#xA;&#xB;&#xFFFD;&#xFFFD;&#x0;&#xFFFD;&#x10;&#x0;&#x2;&#x1;&#x3;&#x3;&#x2;&#x4;&#x3;&#x5;&#x5;&#x4;&#x4;&#x0;&#x0;&#x1;}&#x1;&#x2;&#x3;&#x0;&#x4;&#x11;&#x5;&#x12;!1A&#x6;&#x13;Qa&#x7;&quot;q&#x14;2&#xFFFD;&#xFFFD;&#xFFFD;&#x8;#B&#xFFFD;&#xFFFD;&#x15;R&#xFFFD;&#xFFFD;$3br&#xFFFD;&#x9;&#xA;&#x16;&#x17;&#x18;&#x19;&#x1A;%&amp;&#x27;()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#x0;&#x1F;&#x1;&#x0;&#x3;&#x1;&#x1;&#x1;&#x1;&#x1;&#x1;&#x1;&#x1;&#x1;&#x0;&#x0;&#x0;&#x0;&#x0;&#x0;&#x1;&#x2;&#x3;&#x4;&#x5;&#x6;&#x7;&#x8;&#x9;&#xA;&#xB;&#xFFFD;&#xFFFD;&#x0;&#xFFFD;&#x11;&#x0;&#x2;&#x1;&#x2;&#x4;&#x4;&#x3;&#x4;&#x7;&#x5;&#x4;&#x4;&#x0;&#x1;&#x2;w&#x0;&#x1;&#x2;&#x3;&#x11;&#x4;&#x5;!1&#x6;&#x12;AQ&#x7;aq&#x13;&quot;2&#xFFFD;&#x8;&#x14;B&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#x9;#3R&#xFFFD;&#x15;br&#xFFFD;&#xA;&#x16;$4&#xFFFD;%&#xFFFD;&#x17;&#x18;&#x19;&#x1A;&amp;&#x27;()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#x0;&#xC;&#x3;&#x1;&#x0;&#x2;&#x11;&#x3;&#x11;&#x0;?&#x0;&#xFFFD;&#xB;&#xFFFD;&#xC;&#xFFFD;&#xFFFD;&#xFFFD;&#x2B;X&#xFFFD;K&#xFFFD;&#xFFFD;&#xFFFD;&#x1C;&#xFFFD;&#xFFFD;21&#xFFFD;c&#xFFFD;Z&#xFFFD;&#xFFFD;]&#x8;&#xFFFD;&#x4E5;&#x5;g&#xFFFD;v&#xFFFD;&#xFFFD;;&#xFFFD;&#x1E;:&#x13;&#xFFFD;&#xFFFD;&#x1C;&#x1A;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#xFFFD;&#x11;&#x1D;&#xFFFD;&#xFFFD;&#xFFFD;P&#xFFFD;&#xFFFD;&#x13;&#xFFFD;&#xC;&#x7;&#xFFFD;I&#xFFFD;&#x1F;f&#xFFFD;&gt;&#xFFFD;&#xFFFD;&#xFFFD;m;]&#xFFFD;&#x5AC;u&#x1B;&#xFFFD;nm&#xFFFD;&#xFFFD;&#xFFFD;`&#xFFFD;Q&#xFFFD;&#x12;1&#xFFFD;P6&#xFFFD;s&#xFFFD;9&#xFFFD;|b&#xFFFD;r|&#xFFFD;&#x10;&#xFFFD;&#xFFFD;G&#xFFFD;
Paul Cavacas
  • 4,194
  • 5
  • 31
  • 60
  • 1
    I have no idea how Flow posts files, but my first step in debugging this would be to check what is actually being sent to the action. If aspnetcore still allows you to see the request stream, dump it to a variable and inspect it and see how it is being sent - it might actually be a JSON object with base64 file data or something like that.. – Michael Coxon Aug 14 '17 at 15:43
  • This is a bit difficult to debug because it has to run in a published server (Azure) for Flow to be able to push to it. I don't see an easy way to read the whole request stream. I am able to check Request.HasFormContentType and this returns false, so there is no Form Content or Files available in the Response. – Paul Cavacas Aug 14 '17 at 16:49
  • 1
    You can publish the debug version to Azure and then attach the remote debugger to the service - this is using App Services. It is slow as hell but worth it when you are in a pinch. The other thing you can try - if Flow allows any public URL - is ngrok. It allows you to proxy a public address to your local machine for debugging. You basically run the service locally and ngrok gives you a public URL to add to Flow. – Michael Coxon Aug 14 '17 at 17:43
  • I'm trying to attach debugger and I get "object reference not sent to an instance of an object error". Also if I go to https://ngrok.com/ I get an error going to that site. – Paul Cavacas Aug 14 '17 at 18:06
  • What middleware did you use to get raw body? Btw. You can read it without any middleware like this: using(var sr = new StreamReader(this.Request.Body)) { var json = sr.ReadToEnd(); } – Alexander Mokin Aug 22 '17 at 23:05
  • I'm not in front of the computer right now, but I just created a custom class that basically said what you have above. – Paul Cavacas Aug 23 '17 at 11:56
  • I know this is a SO no-no ("me-to") but @MichaelCoxon comments are the direction you should be going. Specifically using ngrok. Spend some time learning how to set it up. It makes debugging soooo much easier. Once you have ngrok setup on your dev machine you will be able to easily look at the posted signature coming from Microsoft Flow. Guessing that it would bind to IFormFile. – trevorc Aug 23 '17 at 13:13
  • The last string, is it the data from flow or from the form? It looks like encoded multipart/form-data – Alexander Mokin Aug 23 '17 at 15:04

3 Answers3

1

Well it is a little bit unclear to me how exactly the file is send: is it a json object where the file is converted previously as base64 string or is it a file content? (html headers are the indicators)

If you have json theory you could do:

var parsedFileContent = Newtonsoft.Json.JsonConvert.DeserializeObject<FlowFile>(json);

instead of

var details = (dynamic)Newtonsoft.Json.JsonConvert.DeserializeObject(json);

and it should work, if and only if ;), what you posted is correct

{
  "$content-type": "image/jpeg",
  "$content": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD1C9EMuqzGK1juS+3P7rccgc4yMYxjv1q/ol0I4bfTpQVniXaoyDuQHjoTg7ccGsDU7O+0+xEdoJfMUKiKE84MB/dJ5B9mzj6VneFtO1271qx1G+hubaGBjmCSUfMSMZZQNoxzgDnPfGKqcnypEJW1R//Z"
}

just make sure the json is in single line string (make sure there are no hidden chars like \n or similar)

On the other hand, in your Fidler capture you have:

Content-Type: multipart/form-data;

So the correct way to go with IFormFile

So the information provided are a little bit misleading. Can you try and pass a bigger chuck of the error logs? "object reference not sent to an instance of an object error" is very general and usually those kinds of errors are narrowed down with the stack trace.

theCuriousOne
  • 1,022
  • 10
  • 22
  • The problem is have posted everything I know I have posted what flow says is the body and what is the request stream. I don't have a fiddler because it is come from flow so not and end point I have control over. – Paul Cavacas Aug 18 '17 at 10:36
  • If you have end point control then Fidler should work, or you can even use Wireshark (but that is an overkill). With those tools you can capture traffic going in/out of your computer. – theCuriousOne Aug 21 '17 at 09:40
  • It is not traffic to my computer it goes from Flow to an Azure web app – Paul Cavacas Aug 21 '17 at 10:44
  • And you can not change the end point to go to from Flow to your app running locally in debug mode? I don't know how you manage to work without local debugging :) – theCuriousOne Aug 21 '17 at 14:26
  • According to this it should be possible: https://powerapps.microsoft.com/en-us/tutorials/register-custom-api/ – theCuriousOne Aug 22 '17 at 10:16
  • Not sure what you read, but it specifically says publicly exposed APIs. – Paul Cavacas Aug 22 '17 at 12:14
1

I finally got this work. What I needed to do was to read the Raw Request Stream directly and that stream is just the image. Everything that Flow was saying about submitting the image in a Base64 encoded JSON string was not correct. I could not get it to bind to any parameters or as a Request.Form.Files, but I could read the stream directly and save the image directly from that.

Paul Cavacas
  • 4,194
  • 5
  • 31
  • 60
0

Instead of having the List<IFormFile> parameter, access the Files property in HttpContext.Request.

Ricardo Peres
  • 13,724
  • 5
  • 57
  • 74