Webapi formdata upload (to DB) with extra parameters

后端 未结 4 1717
谎友^
谎友^ 2020-11-28 04:47

I need to upload file sending extra paramaters.

I have found the following post in stackoverflow: Webapi ajax formdata upload with extra parameters

It descri

相关标签:
4条回答
  • 2020-11-28 05:09

    You can achieve this in a not-so-very-clean manner by implementing a custom DataStreamProvider that duplicates the logic for parsing FormData from multi-part content from MultipartFormDataStreamProvider.

    I'm not quite sure why the decision was made to subclass MultipartFormDataStreamProvider from MultiPartFileStreamProvider without at least extracting the code that identifies and exposes the FormData collection since it is useful for many tasks involving multi-part data outside of simply saving a file to disk.

    Anyway, the following provider should help solve your issue. You will still need to ensure that when you iterate the provider content you are ignoring anything that does not have a filename (specifically the statement streamProvider.Contents.Select() else you risk trying to upload the formdata to the DB). Hence the code that asks the provider is a HttpContent IsStream(), this is a bit of a hack but was the simplest was I could think to do it.

    Note that it is basically a cut and paste hatchet job from the source of MultipartFormDataStreamProvider - it has not been rigorously tested (inspired by this answer).

    public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
    {
        private readonly Collection<bool> _isFormData = new Collection<bool>();
        private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
    
        public NameValueCollection FormData
        {
            get { return _formData; }
        }
    
        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)
            {
                _isFormData.Add(String.IsNullOrEmpty(contentDisposition.FileName));
                return base.GetStream(parent, headers);
            }
    
            throw new InvalidOperationException("Did not find required 'Content-Disposition' header field in MIME multipart body part.");
        }
    
        public override async Task ExecutePostProcessingAsync()
        {
            for (var index = 0; index < Contents.Count; index++)
            {
                if (IsStream(index))
                    continue;
    
                var formContent = Contents[index];
                var contentDisposition = formContent.Headers.ContentDisposition;
                var formFieldName = UnquoteToken(contentDisposition.Name) ?? string.Empty;
                var formFieldValue = await formContent.ReadAsStringAsync();
                FormData.Add(formFieldName, formFieldValue);
            }
        }
    
        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;
        }
    
        public bool IsStream(int idx)
        {
            return !_isFormData[idx];
        }
    }
    

    It can be used as follows (using TPL syntax to match your question):

    [HttpPost]
    public Task<string> Post()
    {
        if (!Request.Content.IsMimeMultipartContent())
            throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "Invalid Request!"));
    
        var provider = new MultipartFormDataMemoryStreamProvider();
    
        return Request.Content.ReadAsMultipartAsync(provider).ContinueWith(p =>
        {
            var result = p.Result;
            var myParameter = result.FormData.GetValues("myParameter").FirstOrDefault();
    
            foreach (var stream in result.Contents.Where((content, idx) => result.IsStream(idx)))
            {
                var file = new FileData(stream.Headers.ContentDisposition.FileName);
                var contentTest = stream.ReadAsByteArrayAsync();
                // ... and so on, as per your original code.
    
            }
            return myParameter;
        });
    }
    

    I tested it with the following HTML form:

    <form action="/api/values" method="post" enctype="multipart/form-data">
        <input name="myParameter" type="hidden" value="i dont do anything interesting"/>
        <input type="file" name="file1" />
        <input type="file" name="file2" />
        <input type="submit" value="OK" />
    </form>
    
    0 讨论(0)
  • 2020-11-28 05:13

    Expanding on gooid's answer, I encapsulated the FormData extraction into the provider because I was having issues with it being quoted. This just provided a better implementation in my opinion.

    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;
        }
    }
    

    And here's how I'm using it. Note that I used await since we're on .NET 4.5.

        [HttpPost]
        public async Task<HttpResponseMessage> Upload()
        {
            if (!Request.Content.IsMimeMultipartContent())
            {
                return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, "Unsupported media type.");
            }
    
            // Read the file and form data.
            MultipartFormDataMemoryStreamProvider provider = new MultipartFormDataMemoryStreamProvider();
            await Request.Content.ReadAsMultipartAsync(provider);
    
            // Extract the fields from the form data.
            string description = provider.FormData["description"];
            int uploadType;
            if (!Int32.TryParse(provider.FormData["uploadType"], out uploadType))
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest, "Upload Type is invalid.");
            }
    
            // Check if files are on the request.
            if (!provider.FileStreams.Any())
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest, "No file uploaded.");
            }
    
            IList<string> uploadedFiles = new List<string>();
            foreach (KeyValuePair<string, Stream> file in provider.FileStreams)
            {
                string fileName = file.Key;
                Stream stream = file.Value;
    
                // Do something with the uploaded file
                UploadManager.Upload(stream, fileName, uploadType, description);
    
                // Keep track of the filename for the response
                uploadedFiles.Add(fileName);
            }
    
            return Request.CreateResponse(HttpStatusCode.OK, "Successfully Uploaded: " + string.Join(", ", uploadedFiles));
        }
    
    0 讨论(0)
  • 2020-11-28 05:17

    Ultimately, the following was what worked for me:

    string root = HttpContext.Current.Server.MapPath("~/App_Data");
    
    var provider = new MultipartFormDataStreamProvider(root);
    
    var filesReadToProvider = await Request.Content.ReadAsMultipartAsync(provider);
    
    foreach (var file in provider.FileData)
    {
        var fileName = file.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);
        byte[] documentData;
    
        documentData = File.ReadAllBytes(file.LocalFileName);
    
        DAL.Document newRecord = new DAL.Document
        {
            PathologyRequestId = PathologyRequestId,
            FileName = fileName,
            DocumentData = documentData,
            CreatedById = ApplicationSecurityDirector.CurrentUserGuid,
            CreatedDate = DateTime.Now,
            UpdatedById = ApplicationSecurityDirector.CurrentUserGuid,
            UpdatedDate = DateTime.Now
        };
    
        context.Documents.Add(newRecord);
    
        context.SaveChanges();
    }
    
    0 讨论(0)
  • 2020-11-28 05:24

    I really needed the media type and length of the files uploaded so I modified @Mark Seefeldt answer slightly to the following:

    public class MultipartFormFile
    {
        public string Name { get; set; }
        public long? Length { get; set; }
        public string MediaType { get; set; }
        public Stream Stream { get; set; }
    }
    
    public class MultipartFormDataMemoryStreamProvider : MultipartMemoryStreamProvider
    {
        private readonly Collection<bool> _isFormData = new Collection<bool>();
        private readonly NameValueCollection _formData = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
        private readonly List<MultipartFormFile> _fileStreams = new List<MultipartFormFile>();
    
        public NameValueCollection FormData
        {
            get { return _formData; }
        }
    
        public List<MultipartFormFile> 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
                    var file = new MultipartFormFile
                    {
                        Name = UnquoteToken(formContent.Headers.ContentDisposition.FileName),
                        Length = formContent.Headers.ContentLength,
                        MediaType = formContent.Headers.ContentType.MediaType,
                        Stream = await formContent.ReadAsStreamAsync()
                    };
    
                    FileStreams.Add(file);
                }
            }
        }
    
        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;
        }
    }
    
    0 讨论(0)
提交回复
热议问题