C# 带有额外参数的 Webapi 表单数据上传(到 DB)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19723064/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 15:44:02  来源:igfitidea点击:

Webapi formdata upload (to DB) with extra parameters

c#file-uploadasp.net-web-apiform-data

提问by renathy

I need to upload file sending extra paramaters.

我需要上传文件发送额外的参数。

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

我在 stackoverflow 中找到了以下帖子:Webapi ajax formdata upload with extra parameters

It describes how to do this using MultipartFormDataStreamProvider and saving data to fileserver. I do not need to save file to server, but to DB instead. And I have already working code using MultipartMemoryStreamProvider, but it doesn't use extra parameter.

它描述了如何使用 MultipartFormDataStreamProvider 执行此操作并将数据保存到文件服务器。我不需要将文件保存到服务器,而是保存到数据库。而且我已经使用 MultipartMemoryStreamProvider 编写了代码,但它没有使用额外的参数。

Can you give me clues how to process extra paramaters in webapi?

你能给我一些线索如何处理 webapi 中的额外参数吗?

For example, if I add file and also test paramater:

例如,如果我添加文件并测试参数:

data.append("myParameter", "test"); 

Here is my webapi that processes fileupload without extra paramater:

这是我的 webapi,它在没有额外参数的情况下处理文件上传:

if (Request.Content.IsMimeMultipartContent())
{               
    var streamProvider = new MultipartMemoryStreamProvider();
    var task = Request.Content.ReadAsMultipartAsync(streamProvider).ContinueWith<IEnumerable<FileModel>>(t =>
    {
        if (t.IsFaulted || t.IsCanceled)
        {
            throw new HttpResponseException(HttpStatusCode.InternalServerError);
        }

        _fleDataService = new FileDataBLL();
        FileData fle;

        var fleInfo = streamProvider.Contents.Select(i => {         
            fle = new FileData();
            fle.FileName = i.Headers.ContentDisposition.FileName;

            var contentTest = i.ReadAsByteArrayAsync();
            contentTest.Wait();
            if (contentTest.Result != null)
            {
                fle.FileContent = contentTest.Result;
            }                       

            // get extra parameters here ??????

            _fleDataService.Save(fle);

            return new FileModel(i.Headers.ContentDisposition.FileName, 1024); //todo
        });
        return fleInfo;
    });
    return task;
}

采纳答案by gooid

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

您可以通过实现一个自定义DataStreamProvider来以一种不太干净的方式实现这一点,该自定义复制用于从MultipartFormDataStreamProvider.

I'm not quite sure why the decision was made to subclass MultipartFormDataStreamProviderfrom MultiPartFileStreamProviderwithout 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.

我不太确定为什么在不提取识别和公开 FormData 集合的代码的情况下决定对MultipartFormDataStreamProviderfrom进行子类化,MultiPartFileStreamProvider因为它对于涉及多部分数据的许多任务非常有用,而不仅仅是将文件保存到磁盘。

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.

无论如何,以下提供商应该可以帮助解决您的问题。您仍然需要确保在迭代提供程序内容时忽略任何没有文件名的内容(特别是streamProvider.Contents.Select()您尝试将表单数据上传到数据库的风险的语句)。因此,要求提供者的代码是 HttpContent IsStream(),这有点小技巧,但最简单的是我能想到这样做。

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).

请注意,它基本上是从源头开始的剪切和粘贴斧头工作MultipartFormDataStreamProvider- 它尚未经过严格测试(受此答案的启发)。

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):

它可以按如下方式使用(使用 TPL 语法来匹配您的问题):

[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:

我使用以下 HTML 表单对其进行了测试:

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

回答by Mark Seefeldt

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.

扩展 gooid 的答案,我将 FormData 提取封装到提供程序中,因为我在引用它时遇到了问题。在我看来,这只是提供了更好的实现。

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.

这就是我如何使用它。请注意,我使用了 await,因为我们使用的是 .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));
    }

回答by user1477388

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

回答by Mark Vickery

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

我真的需要上传文件的媒体类型和长度,所以我修改了@Mark Seefeldt 对以下内容的回答:

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