0

My question is similar to this one: webapi-file-uploading-without-writing-files-to-disk

However, this and most of the other similar questions stop at reading the file data. I have made that bit work fine, but I am also wanting to read the rest of the form data in the form which has other elements for the upload such as 'Title' etc. This comes from the solution in the aforementioned question:

            var filesToReadProvider = await Request.Content.ReadAsMultipartAsync();

The filesToReadProvider is a collection of HTTPContent objects, so I tried:

        List<HttpContent> uploadedstuff = filesToReadProvider.Contents.ToList();
        Image image = new Image(); ;        // The image object we will create
        Stream filestream;  // The file stream object to use with the image
        foreach (var thing in uploadedstuff)
        {
            try
            {
                string name = thing.Headers.ContentDisposition.Name.Replace("\"", ""); // String is quoted "\""namestring"\"" so need it stripped out
                List<NameValueHeaderValue> parameters = thing.Headers.ContentDisposition.Parameters.ToList();
                if (name == "file")
                {
                    image.LocalFileName = thing.Headers.ContentDisposition.FileName;
                    filestream = await thing.ReadAsStreamAsync();
                }
                if (name == "Title")
                {
                    // vvv- this line causes an exception.
                    NameValueCollection titleData = await thing.ReadAsFormDataAsync();
                }
            }
            catch (System.Exception e)
            {
                var message = "Something went wrong";
                HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
                return ResponseMessage(err);
            }
        }

Any ideas what I should be doing to get to eg: the 'Title' form data? I feel I am close, but may be taking the wrong approach? Many thanks.

Community
  • 1
  • 1
Brett JB
  • 687
  • 7
  • 24
  • 1
    Each part of a form multipart content has its own content type. Try `thing.ReadAsStringAsync()` and see what returns. I will elaborate more if it's what I think it is – Emad Oct 12 '19 at 10:07
  • 1
    BRILLIANT and THANK YOU!... yes, that's it. I felt I was close and this gets it. Spot on. – Brett JB Oct 12 '19 at 12:18

1 Answers1

1

Now that it is sorted out in the comments I shall post this answer to help others maybe.

form multipart content sends a data back to server like this"

---------------------------acebdf13572468
Content-Disposition: form-data; name="file"; 
Content-Type: image/*

<@INCLUDE *App.jpg*@>
---------------------------acebdf13572468
Content-Disposition: form-data; name="TextField";
Content-Type: application/octet-stream

Text Value
---------------------------acebdf13572468
Content-Disposition: form-data; name="JsonField";
Content-Type: application/json

{
"Json" : "Object"
}
---------------------------acebdf13572468--

Each part that is separated with the separator line (i.e. ---------------------------acebdf13572468) is a content of it's own, hence, multipart.

You can send data as json in one field as shown above or send text or anything. Usually browsers send each control in a single part of it's own. You can read this kind of data via ASP.Net model binder by specifying [FromForm] in controller arguments.

Or in this particular case you might read it with thing.ReadAsStreamAsync(); and thing.ReadAsStringAsync();

--Edit--

So the aforementioned method is useful when directly reading data or otherwise not MVC projects. If you are using MVC you can easily read the like this.

Let's imagine you have a model like

public class Model
{
    public string Title { get; set; }
}

then you would create a controller and action like this:

public class MainController : Controller
{
    [HttpPost]
    public async Task<IActionResult> UploadImage([FromForm]Model model)
    {
        var files = Request.Files;
        var title = model.Title
        //And you can save or use files and content at the same time.
    }
}

This is usually what you would do in an MVC scenario and if you use Razor or Pages in the client side you wouldn't even need to specify the [FromForm] attribute.

I hope this helps you.

Disclaimer. I have written all this in browser. May contain syntax and other errors.

Emad
  • 3,809
  • 3
  • 32
  • 44
  • Thanks again @Emad... so I would ask you a small further question, because ideally, I would have liked to have had an API signature something like: public async Task UploadImage(ImageFile, ImageDTO image) – Brett JB Oct 12 '19 at 17:11
  • ... are you saying I can do that more automatically by adding [Fromform] in the signature.. in the same way as [FromBody]? If so that would make it a lot simpler. I had abandoned that approach because I couldnt find any hopeful docs to describe how. I predict this will become a well read post. I will give it a try so forgive me if I come back again. Thanks – Brett JB Oct 12 '19 at 17:20
  • Please see the edit. I have tried my best to explain. – Emad Oct 12 '19 at 18:01
  • Hi Emad, Just a note to say I tried this this morning and can't get it to work. Its kind of back where I started but I note that [FromForm] isn't provided and I think you must be referring to a facility in .NET Core. I am targeting .NET 4.6 at present, ready to migrate to core 3 shortly, but thats another can I need to address later.. I will go back to my working solution for now and maybe look again at Core 3 time. Thanks again. – Brett JB Oct 14 '19 at 10:08
  • This is different in .Net framework. I sort of forgot how it was back then or I would edit the answer to include that. From what I remember. You need to have an object with exactly the properties that you send in each part. Please test this if it works tell me to the edit – Emad Oct 14 '19 at 14:58
  • Yes, that's what I tried. The only thing in uncertain about is the file object.. I used Stream as the type for that.. the others are all strings. In MVC I have used HttpPostedFileBase but this is not recommended outside an mvc context and though I am using mvc for the browser side .. this is the mobile app api. Any thoughts on the type for file and I'll give it a go.. – Brett JB Oct 14 '19 at 15:30
  • No the file would not be in the model binder. You should get the file from `Request` in the controller. – Emad Oct 14 '19 at 15:44