【导读】最近因维护.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();}
}
主要发现网上一部分资料对于利用FormData上传文件在利用.NET Core接收参数时姿势不是很正确,故而才有此文,在.NET Core中参数的绑定已完全不需要借助请求上下文来获取,其绑定机制灵活且强大