0

I have a WebService that is working and receiving files using the POST method, but in which I also need to receive data, simultaneously.

ASP.NET WebApi code:

public Task<HttpResponseMessage> Post()
    {
        HttpRequestMessage request = this.Request;
        if (!request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
        }

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

        var task = request.Content.ReadAsMultipartAsync(provider).
            ContinueWith<HttpResponseMessage>(o =>
            {
                string file1 = provider.FileData.First().LocalFileName;

                return new HttpResponseMessage()
                {
                    Content = new StringContent("File uploaded.")
                };
            }
        );
        return task;
    }

And the client, developed for Android, is sending the file and the data like this (the send of the file is tested and working, the sending of the data is still not tested, as I need it to be working in the server side):

OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"title\""),
                RequestBody.create(null, "Sample Text Content"))
        .addPart(
                Headers.of("Content-Disposition", "form-data; name=\"file\"; filename=\"" + fileName + ".png\""),
                RequestBody.create(MEDIA_TYPE_PNG, bitmapdata))
        .addFormDataPart("fullpath", "test")
        .build();
final com.squareup.okhttp.Request request = new com.squareup.okhttp.Request.Builder()
        .url(url)
        .post(requestBody)
        .build();

How can I change the server to read not only the file but also the data?

Can any one help? Thanks in advance.

Nelson Lopes
  • 117
  • 2
  • 13
  • 1
    This is a similar question to https://stackoverflow.com/questions/19723064/webapi-formdata-upload-to-db-with-extra-parameters I have added a summary in my answer below. – Jason Feb 21 '19 at 07:52
  • 1
    Just addind this two line solved my problem: var result = o.Result; var myParameter = result.FormData.GetValues("fullPath").FirstOrDefault(); – Nelson Lopes Feb 21 '19 at 23:39

1 Answers1

1

The client in this case android is sending additional values in the body like media_type_png. I had to do something similar however the client was angular and not a mobile app, after some searching back then I found code from the following stackoverflow. Which resulted in the code below.

First receive the incoming message and check that you can process it i.e. [IsMimeMultipartContent][1]()

[HttpPost] 
public async Task<HttpResponseMessage> Upload()
{
    // Here we just check if we can support this
    if (!Request.Content.IsMimeMultipartContent())
    {
        this.Request.CreateResponse(HttpStatusCode.UnsupportedMediaType);
    }

    // This is where we unpack the values
    var provider = new MultipartFormDataMemoryStreamProvider();
    var result = await Request.Content.ReadAsMultipartAsync(provider);

    // From the form data we can extract any additional information Here the DTO is any object you want to define
    AttachmentInformationDto attachmentInformation = (AttachmentInformationDto)GetFormData(result);
    // For each file uploaded
    foreach (KeyValuePair<string, Stream> file in provider.FileStreams)
    {
        string fileName = file.Key;
        // Read the data from the file
        byte[] data = ReadFully(file.Value);
        // Save the file or do something with it
    }
}

I used this to unpack the data:

 // Extracts Request FormatData as a strongly typed model
private object GetFormData(MultipartFormDataMemoryStreamProvider result)
{
    if (result.FormData.HasKeys())
    {
        // Here you can read the keys sent in ie
        result.FormData["your key"]
        AttachmentInformationDto data = AttachmentInformationDto();
        data.ContentType = Uri.UnescapeDataString(result.FormData["ContentType"]); // Additional Keys
        data.Description = Uri.UnescapeDataString(result.FormData["Description"]); // Another example
        data.Name = Uri.UnescapeDataString(result.FormData["Name"]); // Another example
        if (result.FormData["attType"] != null)
        {
            data.AttachmentType = Uri.UnescapeDataString(result.FormData["attType"]);
        }
        return data;
    }

    return null;
}

The MultipartFormDataMemoryStreamProvider is defined as follows:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Web;

namespace YOURNAMESPACE
{
    public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
    {
        private readonly Collection<bool> _isFormData = new Collection<bool>();
        private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<string, Stream> _fileStreams = new Dictionary<string, Stream>();

        public NameValueCollection FormData
        {
            get { return _formData; }
        }

        public Dictionary<string, Stream> FileStreams
        {
            get { return _fileStreams; }
        }

        public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
        {
            if (parent == null)
            {
                throw new ArgumentNullException("parent");
            }

            if (headers == null)
            {
                throw new ArgumentNullException("headers");
            }

            var contentDisposition = headers.ContentDisposition;
            if (contentDisposition == null)
            {
                throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part.");
            }

            _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));
            return base.GetStream(parent, headers);
        }

        public override async Task ExecutePostProcessingAsync()
        {
            for (var index = 0; index < Contents.Count; index++)
            {
                HttpContent formContent = Contents[index];
                if (_isFormData[index])
                {
                    // Field
                    string formFieldName = UnquoteToken(formContent.Headers.ContentDisposition.Name) ?? string.Empty;
                    string formFieldValue = await formContent.ReadAsStringAsync();
                    FormData.Add(formFieldName, formFieldValue);
                }
                else
                {
                    // File
                    string fileName = UnquoteToken(formContent.Headers.ContentDisposition.FileName);
                    Stream stream = await formContent.ReadAsStreamAsync();
                    FileStreams.Add(fileName, stream);
                }
            }
        }

        private static string UnquoteToken(string token)
        {
            if (string.IsNullOrWhiteSpace(token))
            {
                return token;
            }

            if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1)
            {
                return token.Substring(1, token.Length - 2);
            }

            return token;
        }
    }
}
Jason
  • 2,555
  • 2
  • 16
  • 17