.NET和.NET Core Web APi FormData多檔案上傳對比

Jeffcky發表於2020-08-05

前言

最近因維護.NET和.NET Core專案用到檔案上傳功能,雖說也做過,但是沒做過什麼對比,藉此將二者利用Ajax通過FormData上傳檔案做一個總結,通過檢視提交表單太簡單,這裡不做闡述,希望對有需要的童鞋能有力所能及的幫助。

.NET Web APi FormData檔案上傳

我們將引數和檔案都通過FormData來上傳,給出如下HTML程式碼

<div class="form-horizontal" style="margin-top:80px;">
    <div class="form-group">
        <label class="control-label col-md-2" for="caption">標題</label>
        <div class="col-md-10">
            <input name="title" id="title" type="text" />
        </div>
    </div>

    <div class="form-group">
        <label class="control-label col-md-2" for="caption">檔案</label>
        <div class="col-md-10">
            <input name="file" id="file" multiple type="file" />
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" id="btn" value="提交" class="btn btn-success" />
        </div>
    </div>
</div>

恕我有點強迫症,介面好看點,看著也舒服,接下來則是指令碼自然不用多說,利用FormData上傳檔案網上一搜遍地都是

$(function () {
    $('#btn').click(function () {

        var data = new FormData();

        var title = $('#title').val();
        data.append("title", title);

        var files = $('#file')[0].files;;
        for (var i = 0; i < files.length; i++) {
            data.append("file", files[i]);
        }
        $.ajax({
            url: '/api/upload/upload',
            type: "post",
            cache: false,
            contentType: false,
            processData: false,
            data: data,
        });
    });
});

不過需要注意的是,對現代大多瀏覽器都都已支援將上述contentType設定為false後,就是在請求頭中新增multipart/form-data,若是老版本瀏覽器則需要在請求頭中手動新增表單多檔案上傳標識,如下

beforeSend: function (request) {
    request.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + data.boundary);
}

前端我們已經搞完,接下來我們回到後臺,.NET Web APi已提供專門讀取FormData資料的APi,如下:

//檢查請求是否包含multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
    throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}

//將檔案儲存到App_Data資料夾下
var root = HttpContext.Current.Server.MapPath("~/App_Data");

//例項化MultipartFormDat流
var provider = new MultipartFormDataStreamProvider(root);

// 讀取表單資料
await Request.Content.ReadAsMultipartAsync(provider);

若上傳2個檔案,此時上傳App_Data目錄下的檔案,如下這般

 若要讀取提交的表單引數,我們如下獲取

//獲取表單引數資料
var formData = provider.FormData;

那麼我們怎麼將上述類似臨時檔案資料轉換為我們上傳的檔案資料呢?我們只需將上述檔名轉換我們上傳的檔名或者其他自定義檔名稱即可,如下:

// 獲取檔案資料
foreach (MultipartFileData file in provider.FileData)
{
    string fileName = file.Headers.ContentDisposition.FileName;

    if (fileName.StartsWith("\"") && fileName.EndsWith("\""))
    {
        fileName = fileName.Trim('"');
    }
    if (fileName.Contains(@"/") || fileName.Contains(@"\"))
    {
        fileName = Path.GetFileName(fileName);
    }

    //將本地檔案轉換為實際所需檔案
    File.Move(file.LocalFileName, Path.Combine(root, fileName));
}

 

完美解決問題,當然除了通過上述流讀取表單相關資料外,.NET Web APi還提供了記憶體表單流,只是利用此流時,表單引數和檔案放置在一起,我們需要通過檔案相關引數來做區分,然後分別獲取檔案和表單引數,如下:

var provider = new MultipartMemoryStreamProvider();

await Request.Content.ReadAsMultipartAsync(provider);

var formData = new NameValueCollection();

foreach (var httpContent in provider.Contents)
{
    var formFileName = httpContent.Headers.ContentDisposition?.FileName?.Trim('\"');
    var formContentType = httpContent.Headers?.ContentType?.ToString();

    if (!string.IsNullOrEmpty(formFileName) && !string.IsNullOrEmpty(formContentType))
    {
        //檔案資料
        using (var fileStream = new FileStream(root, FileMode.Create))
        {
            await httpContent.CopyToAsync(fileStream);
        }
    }
    else
    {
        //表單引數
        var formFieldName = httpContent.Headers.ContentDisposition.Name;

        var formFieldValue = await httpContent.ReadAsStringAsync();

        formData.Add(formFieldName, formFieldValue);
    }
}

.NET Core Web APi FormData檔案上傳

HTML和指令碼在上述已經提供,這裡我們只需關注APi獲取即可。在.NET Core中沒有專門提供獲取FormData資料的APi,那麼我們是如何獲取的呢?網上找了一大圈大部分是來自廣告網站CSDN,不過這些文章都是轉載的部落格園,都是如下這樣獲取

[Route("api/[controller]/[action]")]
[ApiController]
public class UploadController : ControllerBase
{
    public IActionResult Upload()
    {
        var files = Request.Form.Files;

        return Ok();
    }
}

如上也沒問題,我能說你這思路還停留在.NET Web APi嗎,啥年代了,還通過請求上下文去獲取,.NET Core靈活繫結機制使用起來它不香嗎,通過如下直接繫結豈不完事

此時有的童鞋又有疑問了,上傳不僅僅包括檔案還包括引數,比如上述還有標題,那該如何是好,啊,這個問題,.NET Core的強型別繫結機制它不香嗎,如下定義強型別:

public class ExampleUpload
{
    public string Title { get; set; }
    public List<IFormFile> Files { get; set; }
}

注意:繫結引數時一定要使用[FromForm],否則將出現請求415,同時也要將前端Ajax FormData檔案的引數名和強型別引數名一致。

[Route("api/[controller]/[action]")]
[ApiController]
public class UploadController : ControllerBase
{
    public IActionResult Upload([FromForm]ExampleUpload example)
    {
        return Ok();
    }
}

 

總結

.NET Core用起來就是這麼流暢和舒適,它也是真的香啊,好了,關於此二者多檔案上傳暫且總結於此,我們下節再會。

相關文章