如何用多个文件解析XHR多部分/表单数据请求

本文关键字:表单 数据 请求 多部 XHR 何用多 文件 | 更新日期: 2023-09-27 18:06:28

我在网上看到了多个多部分/表单数据解析器的例子,但它们都不能在我的网站上工作。我使用Kendo上传控件设置为异步模式,并启用了批量上传,生成的请求看起来像这样:

BOUNDARY
Content-Disposition: form-data; name="Param_1"
Param_1 Value
BOUNDARY
Content-Disposition: form-data; name="Param_2"
Param_2 Value
BOUNDARY
Content-Disposition: form-data; name="files[]"; filename="A.docx"
Content-Type: application/octet-stream
[Binary Data Here]
BOUNDARY
Content-Disposition: form-data; name="files[]"; filename="B.docx"
Content-Type: application/octet-stream
[Binary Data Here]
BOUNDARY--

我在网上找到的每个库都成功地检索了参数和第一个文件,但有些库从未看到第二个文件,有些库保存错误。有一个类似的问题,在SO WCF多部分/表单数据与多个文件,但该解决方案只适用于文本文件,而不是二进制文件。

如何用多个文件解析XHR多部分/表单数据请求

使用二进制文件的其他解决方案的问题是,它们将响应转换为字符串以解析它,然后将该字符串转换回文件,这不适用于二进制数据。我提出了一个解决方案,我没有将响应转换为字符串来解析它,而是将其保留为字节[],并使用这里找到的代码将其分割为字节[]。一旦完成,将每个部分转换为字符串,看看它是参数还是文件,如果是参数,则读取它,否则将其作为文件写入。下面是工作代码,假设您将Stream作为字节[],并将分隔符作为字符串:

// given byte[] streamByte, String delimiterString, and Encoding encoding
Regex regQuery;
Match regMatch;
string propertyType;
byte[] delimiterBytes = encoding.GetBytes(delimiterString);
byte[] delimiterWithNewLineBytes = encoding.GetBytes(delimiterString + "'r'n");
// the request ends DELIMITER--'r'n
byte[] delimiterEndBytes = encoding.GetBytes("'r'n" + delimiterString + "--'r'n");
int lengthDifferenceWithEndBytes = (delimiterString + "--'r'n").Length;
// seperate by delimiter + newline
// ByteArraySplit code found at https://stackoverflow.com/a/9755250/4244411
byte[][] separatedStream = ByteArraySplit(streamBytes, delimiterWithNewLineBytes);
streamBytes = null;
for (int i = 0; i < separatedStream.Length; i++)
{
    // parse out whether this is a parameter or a file
    // get the first line of the byte[] as a string
    string thisPieceAsString = encoding.GetString(separatedStream[i]);
    if (string.IsNullOrWhiteSpace(thisPieceAsString)) { continue; }
    string firstLine = thisPieceAsString.Substring(0, thisPieceAsString.IndexOf("'r'n"));
    // Check the item to see what it is
    regQuery = new Regex(@"(?<=name'='"")(.*?)(?='"")");
    regMatch = regQuery.Match(firstLine);
    propertyType = regMatch.Value.Trim();
    // get the index of the start of the content and the end of the content
    int indexOfStartOfContent = thisPieceAsString.IndexOf("'r'n'r'n") + "'r'n'r'n".Length;
    // this line compares the name to the name of the html input control, 
    // this can be smarter by instead looking for the filename property
    if (propertyType != "files")
    {
        // this is a parameter!
        // if this is the last piece, chop off the final delimiter
        int lengthToRemove = (i == separatedStream.Length - 1) ? lengthDifferenceWithEndBytes : 0;
        string value = thisPieceAsString.Substring(indexOfStartOfContent, thisPieceAsString.Length - "'r'n".Length - indexOfStartOfContent - lengthToRemove);
        // do something with the parameter
    }
    else
    {
        // this is a file!
        regQuery = new Regex(@"(?<=filename'='"")(.*?)(?='"")");
        regMatch = regQuery.Match(firstLine);
        string fileName = regMatch.Value.Trim();
        // get the content byte[]
        // if this is the last piece, chop off the final delimiter
        int lengthToRemove = (i == separatedStream.Length - 1) ? delimiterEndBytes.Length : 0;
        int contentByteArrayStartIndex = encoding.GetBytes(thisPieceAsString.Substring(0, indexOfStartOfContent)).Length;
        byte[] fileData = new byte[separatedStream[i].Length - contentByteArrayStartIndex - lengthToRemove];
        Array.Copy(separatedStream[i], contentByteArrayStartIndex, fileData, 0, separatedStream[i].Length - contentByteArrayStartIndex - lengthToRemove);
        // save the fileData byte[] as the file
    }
}