5

I'm currently developing a REST web service using Web API.

Now I am trying to receive stream file and object through the post.

When I sent it without the JobPosition object I received the file correctly, also when I sent the JobPosition without the file I received the JobPosition correctly.

But when I sent the stream file and the object through the postman I receive the error.

I would appreciate your help to understand if this is possible, and if so, the direction that will help me.

public class JobPosition
{
    public int Id { set; get; }
    public string Title { set; get; }
}

[HttpPost]
[Route("Job")]
public async Task<bool> Post(JobPosition job)
{
    var multipartMemoryStreamProvider = new MultipartMemoryStreamProvider();
    await Request.Content.ReadAsMultipartAsync(multipartMemoryStreamProvider, new CancellationToken());
    var stream = await multipartMemoryStreamProvider.Contents[0].ReadAsStreamAsync();
    // implementaion
    return true;
}

Postman request: enter image description here enter image description here

I tried all the possible combinations with the Content-Type with no success.

Misha Zaslavsky
  • 8,414
  • 11
  • 70
  • 116
Jenia Finkler
  • 61
  • 1
  • 1
  • 5
  • Please add Postman's response to your question directly instead of linking to an image. Cheers. – Quality Catalyst Feb 16 '18 at 11:09
  • Also, mind you to explain what role the `NewsItem` plays; see Postman's error message? – Quality Catalyst Feb 16 '18 at 11:12
  • @QualityCatalyst The `NewsItem` is JobPosition actually. He changed the type for making the example more simple for the Stackoverflow users. The `NewsItem` object is much bigger then JobPosition but the same error occurs with JobPosition. Hope you understand me. – Misha Zaslavsky Feb 18 '18 at 07:51
  • I am pretty sure this is a duplicate: https://stackoverflow.com/questions/28369529/how-to-setup-a-webapi-controller-for-multipart-form-data – Jeremy Thompson Feb 19 '18 at 02:42
  • @MishaZaslavsky is using multipart/form-data required in your case? Because I'd personally pass id and title in query string (even though that is a POST request - it's still ok to do that), and use body for the file contents. – Evk Feb 19 '18 at 04:36
  • @Evk I removed some properties from the object to make it look simpler. Actually, the object includes 7 properties and I think it would be bad practice to send it in the query. – Misha Zaslavsky Feb 19 '18 at 07:04
  • 1
    @MishaZaslavsky if you need multipart\form-data and you want model binding, I guess you need to implement custom media formatter, like this one: https://gist.github.com/Danielku15/bfc568a19b9e58fd9e80 – Evk Feb 19 '18 at 07:33
  • You could try and make a view model for JobPosition that will also contain the file as a property ? (As a type of byte[ ] or [HttpPostedFile](https://msdn.microsoft.com/en-us/library/system.web.httppostedfile(v=vs.110).aspx) ) – Nick Polyderopoulos Feb 25 '18 at 00:32

3 Answers3

4

Remove the (JobPosition job) action function parameter, and instead read that with your own code via the multipartMemoryStreamProvider object.

await multipartMemoryStreamProvider.Contents[0].ReadAsStreamAsync(); //Stream
await multipartMemoryStreamProvider.Contents[1].ReadAsStreamAsync(); //JSON

Something like that. The order will be important. It's best if you instead assume it could come in any order, loop over the Contents collection, and handle according to the name of the variable.

Kind Contributor
  • 17,547
  • 6
  • 53
  • 70
  • Multipart and File Uploads are the dark side of the web. When architecting for file uploads, I always POST files on their own (any context variables can be set as URI params, and/or HTTP Headers) and never multipart. The upload file is stored by a single central service, which responds with a relative-path or key. Back on the client-side that path/key can be stored along-side the broader web-form. Never multi-part #neveragain. – Kind Contributor Feb 25 '18 at 22:58
2

If you are using .NET core, can you try this?

public class JobPosition
{
    public int Id { set; get; }
    public string Title { set; get; }
    public IFormFile jobFile {set; get; }
}

[HttpPost]
[Route("Job")]
public async Task<bool> Post(JobPosition job)
{
    if (!Request.ContentType.Contains("multipart/form-data"))
    {
        return BadRequest();
    }

    // your implementation
    // you can access the file by job.jobFile
    // you can read the contents of the file by opening a read stream from the file object and read it to a byte[]
    return true;
}

More on IFormFile here.

Priyan Perera
  • 560
  • 1
  • 7
  • 20
2

Maybe you can use the FormData provided by the MultipartFormDataStreamProvider to extract the JobPosition.

    public async Task<IHttpActionResult> PostUploadFile()
    {
        // Check if the request contains multipart/form-data.
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

        string root = HttpContext.Current.Server.MapPath("~/App_Data");
        var provider = new MultipartFormDataStreamProvider(root);

        try
        {
            // Read the form data.
            await Request.Content.ReadAsMultipartAsync(provider);

            NameValueCollection formdata = provider.FormData;

            JobPosition jobPosition = new JobPosition()
            {
                Id = formdata["Id"],
                Title = bool.Parse(formdata["Title"])
            };

            foreach (MultipartFileData file in provider.FileData)
            {
                var fileName = file.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
                byte[] documentData = File.ReadAllBytes(file.LocalFileName);


                /// Save document documentData to DB or where ever
                /// --- TODO
            }
            return Ok(jobPosition);
        }
        catch (System.Exception e)
        {
            return BadRequest(e.Message);
        }
    }
Gert Botha
  • 175
  • 1
  • 7