需求背景:
在需要通过服务端请求传递文件二进制文件流数据到相关的服务端保存时,如对接第三方接口很多情况下都会提供一个上传文件的接口,但是当你直接通过前端Ajax的方式将文件流上传到对方提供的接口的时候往往都会存在跨域的情况,这时候我们就需要通过服务端提交文件流来解决这个跨域的情况。本篇的主角就是使用HttpClient进行Http请求,提交二进制文件流到文件服务器中。
HttpClient简单介绍:
HttpClient类实例充当发送 HTTP 请求的会话。 HttpClient实例是对该实例执行的所有请求应用的设置的集合。 此外,每个 HttpClient 实例都使用其自己的连接池,并从其他实例所执行的请求隔离其请求 HttpClient 。
使用注意点:HttpClient对象比较特殊,虽然继承了IDisposable这个接口但是它可以被共享实例,并且使用完不能立即关闭连接、性能消耗严重。所以我们在使用的时候,需要主动调用Dispose
方法来释放它。可以使用using如下所示:
using(var client = new HttpClient())
{
//do something with http client
}
网上说.NET Core版本的HttpClient存在比较多的问题(不过我自己一直在使用HttpClient做一些http请求),大家也可以HttpClientFactory,ASP.NET Core中使用HttpClientFactory官方教程:
前端使用Ajax-FormData对象上传文件:
注意点:
FormData:对象用以将数据编译成键值对,以便用
XMLHttpRequest
来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据(keyed data),而独立于表单使用。contentType:需设置为false,在Ajax中contentType 设置为false 是为了避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件。
processData:需设置为false,默认为true,表示以对象的形式上传的时候会默认把对象转化为字符串的形式上传。
<div class="text-center"> <p><input type="file" id="imageFile" onchange="uploadImage(this)" /></p> </div> <div id="imageBox"> </div> <script type="text/javascript"> //图片上传 function uploadImage(fileObject) { var formData = new FormData(); var files = $(fileObject).prop('files'); //获取到文件列表【$("#imageFile").get(0)通过id获取文件列表】 formData.append("files", files[0]);//图片文件流 console.log('formData=>>>', formData, files); $.ajax({ async: true, url:"@Url.Action("UploadImage", "ImageFileManage")", type: 'post', data: formData, //https://segmentfault.com/a/1190000007207128?utm_source=tag-newest //在 ajax 中 contentType 设置为 false 是为了避免 JQuery 对其操作,从而失去分界符,而使服务器不能正常解析文件 contentType: false, //告诉jQuery不要去处理发送的数据 processData: false, success: function (res) { console.log(res); if (res.code == 0) { $("#imageBox").append("<img width='100' height='100' src=" + res.msg.completeFilePath+">"); } } }); } </script>
接收Ajax传递的文件流,并转化为转化字节类型:
/// <summary> /// 图片文件管理 /// </summary> public class ImageFileManageController : Controller { /// <summary> /// 接收Ajax传递的文件流 /// </summary> /// <param name="files">表单文件信息</param> /// <returns></returns> public IActionResult UploadImage(IFormFile files) { //var files = Request.Form.Files[0];//获取请求发送过来的文件 if (files.Length <= 0) return Json(new { code = 1, msg = "请选择需要上传的文件~" }); var fileBytes = ReadFileBytes(files); var fileExtension = Path.GetExtension(files.FileName);//获取文件格式,拓展名 var result = HttpClientHelper._.HttpClientPost("https://localhost:44347/FileUpload/SingleFileUpload", fileBytes, fileExtension, files.FileName); var resultObj = JsonConvert.DeserializeObject<UploadReponse>(result); if (resultObj.IsSuccess) { return Json(new { code = 0, msg = resultObj }); } else { return Json(new { code = 1, msg = resultObj.ReturnMsg }); } } /// <summary> /// 文件流类型转化字节类型 /// </summary> /// <param name="fileData">表单文件信息</param> /// <returns></returns> private byte[] ReadFileBytes(IFormFile fileData) { byte[] data; using (Stream inputStream = fileData.OpenReadStream())//读取上传文件的请求流 { MemoryStream memoryStream = inputStream as MemoryStream; if (memoryStream == null) { memoryStream = new MemoryStream(); inputStream.CopyTo(memoryStream); } data = memoryStream.ToArray(); } return data; } } /// <summary> /// 上传响应模型 /// </summary> public class UploadReponse { /// <summary> /// 是否成功 /// </summary> public bool IsSuccess { get; set; } /// <summary> /// 结果 /// </summary> public string ReturnMsg { get; set; } /// <summary> /// 完整地址 /// </summary> public string CompleteFilePath { get; set; } }
向目标地址提交图片文件参数数据(HttpClient-上传multipart/form-data内容类型):
注意:
/// <summary> /// Http网络请求帮助类 /// </summary> public class HttpClientHelper { private static HttpClientHelper _httpClientHelper; public static HttpClientHelper _ { get => _httpClientHelper ?? (_httpClientHelper = new HttpClientHelper()); set => _httpClientHelper = value; } /// <summary> /// 向目标地址提交图片文件参数数据 /// </summary> /// <param name="requestUrl">请求地址</param> /// <param name="bmpBytes">图片字节流</param> /// <param name="imgType">上传图片类型</param> /// <param name="fileName">图片名称</param> /// <returns></returns> public string HttpClientPost(string requestUrl, byte[] bmpBytes, string imgType, string fileName) { using (var httpClient = new HttpClient()) { List<ByteArrayContent> byteArrayContents = new List<ByteArrayContent>(); var imgTypeContent = new ByteArrayContent(Encoding.UTF8.GetBytes(imgType)); imgTypeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "imgType" }; byteArrayContents.Add(imgTypeContent); var fileContent = new ByteArrayContent(bmpBytes);//填充图片文件二进制字节 fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "file", FileName = fileName }; byteArrayContents.Add(fileContent); var content = new MultipartFormDataContent(); //将ByteArrayContent集合加入到MultipartFormDataContent中 foreach (var byteArrayContent in byteArrayContents) { content.Add(byteArrayContent); } try { var result = httpClient.PostAsync(requestUrl, content).Result;//post请求 return result.Content.ReadAsStringAsync().Result; } catch (Exception ex) { return ex.Message; } } } }
模拟第三方上传文件接口,保存图片到服务端并返回文件预览完整地址:
关于.NET Core上传文件的后端服务接口可以参考我之前写过的文章:
/// <summary> /// 单文件上传(Ajax,Form表单都适用)模拟第三方服务端接口 /// </summary> /// <param name="file">表单文件信息</param> /// <returns></returns> public JsonResult SingleFileUpload(IFormFile file) { try { if (file != null) { var currentDate = DateTime.Now; var webRootPath = _hostingEnvironment.WebRootPath;//>>>相当于HttpContext.Current.Server.MapPath("") var filePath = $"/UploadFile/{currentDate:yyyyMMdd}/"; //创建每日存储文件夹 if (!Directory.Exists(webRootPath + filePath)) { Directory.CreateDirectory(webRootPath + filePath); } //文件后缀 var fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名 //判断文件大小 var fileSize = file.Length; if (fileSize > 1024 * 1024 * 10) //10M TODO:(1mb=1024X1024b) { return Json(new { isSuccess = false, resultMsg = "上传的文件不能大于10M" }); } //保存的文件名称(以名称和保存时间命名) var saveName = file.FileName.Substring(0, file.FileName.LastIndexOf('.')) + "_" + currentDate.ToString("HHmmss") + fileExtension; //文件保存 using (var fs = System.IO.File.Create(webRootPath + filePath + saveName)) { file.CopyTo(fs); fs.Flush(); } //完整的文件路径 var completeFilePath = Path.Combine(filePath, saveName); return Json(new { isSuccess = true, returnMsg = "上传成功", completeFilePath = completeFilePath }); } else { return Json(new { isSuccess = false, resultMsg = "上传失败,未检测上传的文件信息~" }); } } catch (Exception ex) { return Json(new { isSuccess = false, resultMsg = "文件保存失败,异常信息为:" + ex.Message }); } }