MultipartMemoryStreamProvider and reading user data from MultiPart/Form Data

10,583

Solution 1

It seems to me the OP, was really close. This is some code that tries to clearly show how to get the form variables, as well as the file upload data.

First the ApiController:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApplication1.Controllers
{
    public class FormAndFileDataController : ApiController
    {
        private class FormItem
        {
            public FormItem() { }
            public string name { get; set; }
            public byte[] data { get; set; }
            public string fileName { get; set; }
            public string mediaType { get; set; }
            public string value { get { return Encoding.Default.GetString(data); } }
            public bool isAFileUpload { get { return !String.IsNullOrEmpty(fileName); } }
        }

        /// <summary>
        /// An ApiController to access an AJAX form post.
        /// </summary>
        /// <remarks>
        /// 
        /// </remarks>
        /// <returns></returns>
        public async Task<HttpResponseMessage> Post()
        {

            if (!Request.Content.IsMimeMultipartContent())
            {
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
            }

            var provider = new MultipartMemoryStreamProvider();

            await Request.Content.ReadAsMultipartAsync(provider);

            var formItems = new List<FormItem>();

            // Scan the Multiple Parts 
            foreach (HttpContent contentPart in provider.Contents)
            {
                var formItem = new FormItem();
                var contentDisposition = contentPart.Headers.ContentDisposition;
                formItem.name = contentDisposition.Name.Trim('"');
                formItem.data = await contentPart.ReadAsByteArrayAsync();
                formItem.fileName = String.IsNullOrEmpty(contentDisposition.FileName) ? "" : contentDisposition.FileName.Trim('"');
                formItem.mediaType = contentPart.Headers.ContentType == null ? "" : String.IsNullOrEmpty(contentPart.Headers.ContentType.MediaType) ? "" : contentPart.Headers.ContentType.MediaType;
                formItems.Add(formItem);
            }

            // We now have a list of all the distinct items from the *form post*.
            // We can now decide to do something with the items.
            foreach (FormItem formItemToProcess in formItems)
            {
                if (formItemToProcess.isAFileUpload)
                {

                    // This is a file. Do something with the file.  Write it to disk, store in a database.  Whatever you want to do.

                    // The name the client used to identify the *file* input element of the *form post* is stored in formItem.name.
                    // The *suggested* file name from the client is stored in formItemToProcess.fileName
                    // The media type (MimeType) of file (as far as the client knew) if available, is stored in formItemToProcess.mediaType
                    // The file data is stored in the byte[] formItemToProcess.data

                }
                else
                {
                    // This is a form variable.  Do something with the form variable.  Update a DB table, whatever you want to do.

                    // The name the client used to identify the input element of the *form post* is stored in formItem.name.
                    // The value the client input element is stored in formItem.value.

                }
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

    }
}

and the MVC View to test it:

@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
    <script type="text/javascript">

            var hiddenForm, hiddenFile;

            function initialize() {
                // Use a hidden file element so we can control the UI
                // of the file selection interface.  The built in browser
                // UI is not localizable to different languages.
                hiddenFile = document.createElement("input");
                hiddenFile.setAttribute("type", "file");
                hiddenFile.setAttribute("style", "display: none;");

                // We don't need the form really, but it makes it easy to
                // reset the selection.
                hiddenForm = document.createElement("form");
                hiddenForm.appendChild(hiddenFile);

                hiddenFile.onchange = function () {
                    var elementToUpdate = document.getElementById("fileNameToUpload");
                    var filesToUpload = hiddenFile.files;
                    var fileToUpload = filesToUpload[0];
                    elementToUpdate.value = fileToUpload.name;
                }
                document.body.appendChild(hiddenForm);
            }
            function chooseFile() {
                hiddenFile.click();
            }

            function clearFile() {
                var elementToUpdate = document.getElementById("fileNameToUpload");
                elementToUpdate.value = "";
                hiddenForm.reset();
            }
            function testAJAXUpload() {

                // We are going to use the FormData object and jQuery
                // to do our post test.
                var formToPost = new FormData();
                var formVariableNameElement = document.getElementById("variableNameToUpload");
                var formVariableValueElement = document.getElementById("variableValueToUpload");

                var formVariableName = formVariableNameElement.value || "formVar1";
                var formVariableValue = formVariableValueElement.value || "Form Value 1";
                var filesToUpload = hiddenFile.files;
                var fileToUpload = filesToUpload[0];

                formToPost.append(formVariableName,formVariableValue)
                formToPost.append("fileUpload", fileToUpload);

                // Call the Server.
                $.ajax({
                    url: '@Url.HttpRouteUrl("DefaultApi", new { controller = "FormAndFileData" })',
                    type: 'POST',
                    contentType: false,
                    processData: false,
                    data: formToPost,
                    error: function (jqXHR, textStatus, errorThrown) {
                        alert("Failed: [" + textStatus + "]");
                    },
                    success: function (data, textStatus, jqXHR) {
                        alert("Success.");
                    }
                });

            }

    </script>
</head>
<body>
    <input id="variableNameToUpload" type="text" placeholder="Form Variable: Name" />
    <br />
    <input id="variableValueToUpload" type="text" placeholder="Form Variable: Value" />
    <br />
    <input id="fileNameToUpload" type="text" placeholder="Select A File..." /><button onclick="chooseFile()">Select File</button><button onclick="clearFile()">Reset</button>
    <br />
    <button onclick="testAJAXUpload()">Test AJAX Upload</button>
    <script type="text/javascript">
            initialize();
    </script>
</body>
</html>

Solution 2

I had considered adding this to your other post per your comment, but (as you also decided), it is a separate question.

public async Task<HttpResponseMessage> Post()
{
    if (!Request.Content.IsMimeMultipartContent())
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);

    try
    {
        string root = HttpContext.Current.Server.MapPath("~/App_Data");

        var provider = await Request.Content.ReadAsMultipartAsync(new MultipartFormDataStreamProvider(root));

        // file data
        foreach (MultipartFileData file in provider.FileData)
        {
            using (var ms = new MemoryStream())
            {
                var diskFile = new FileStream(file.LocalFileName, FileMode.Open);

                await diskFile.CopyToAsync(ms);

                var byteArray = ms.ToArray();
            }
        }

        // form data
        foreach (var key in provider.FormData.AllKeys)
        {
            var values = provider.FormData.GetValues(key);

            if (values != null)
            {
                foreach (var value in values)
                {
                    Console.WriteLine(value);   
                }
            }
        }

        return Request.CreateResponse(HttpStatusCode.Created);
    }
    catch (Exception ex)
    {
        return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
    }
}
Share:
10,583
user877247
Author by

user877247

Updated on June 13, 2022

Comments

  • user877247
    user877247 almost 2 years

    I have a file and user data that is being posted from Multipart/form data to a post method in my apicontroller class. I am able to read the file without any problems but unable to read user data.

    I tried couple of things like using model binding, passing the individual fields as a method parameter in the post method but i get: No MediaTypeFormatter is available to read an object of type 'FormDataCollection' from content with media type 'multipart/form-data'.

    var provider = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
    foreach (var item in provider.Contents)
    {
        var fieldName = item.Headers.ContentDisposition.Name.Trim('"');
        if (item.Headers.ContentDisposition.FileName == null)
        {
            var data = await item.ReadAsStringAsync();
            if (fieldname == "name")
            { 
                Name = data;
            }
            else
            {
                fileContents = await item.ReadAsByteArrayAsync();
            }
        }
    }
    

    Thanks.

  • user877247
    user877247 almost 9 years
    Thanks. I still dont see any comments in the other question and thought of asking another question since it was taking a different direction than what i started off with.
  • user877247
    user877247 almost 9 years
    I tried adding the above but getting - No MediaTypeFormatter is available to read an object of type 'FormDataCollection' from content with media type 'multipart/form-data'.
  • Rob Davis
    Rob Davis almost 9 years
    @user877247 - it looks like my original answer doesn't actually play well with enctype="multipart/form-data". I think the edited answer above may give you what you are looking for. Unfortunately, I couldn't figure out how to acquire the form data with the MultipartMemoryStreamProvider, so I changed it to the MultipartFormDataStreamProvider.