WebAPI批量上传文件(WPF/AngularJS/MUI)
2017-03-22 22:26
351 查看
ASP.NET Web API遵循了通用的HTTP协议,也就是说常见的HTTP请求都可以被接受,而不必考虑请求是从WPF应用、WinForm应用或是Web页面发出的。本文归纳了本人最近采用Web API实现文件上传的成果,并将包含以下内容:
WebAPI服务端
WPF客户端
AngularJS客户端
MUI手机APP
创建WebAPI应用,添加web API控制器FileController,代码如下:
//判断请求是否包含multipart/form-data
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
multipart/form-data为上传文件时使用的http请求头,如果服务端接收到的请求头中不包含multipart/form-data,服务端会认为这是一个不合要求的请求并返回HttpStatusCode.UnsupportedMediaType(不支持的媒体格式)。
//上传文件路径
string basePath = HttpContext.Current.Server.MapPath(baseUri);
if (!Directory.Exists(basePath))
{
Directory.CreateDirectory(basePath);
}
文件上传到服务器上需要有位置进行存储,HttpContext.Current.Server.MapPath(baseUri)是指相对于当前API服务根目录的baseUri路径,baseUri在web.config中定义。若不存在存储目录则由API服务自动创建。在这里可根据实际需要改成绝对路径。
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(basePath);
MultipartFormDataStreamProvider 是流提供程序,它将查看Content-Disposition 标头字段,并根据 filename 参数是否存在来确定输出 System.IO.Stream。如果Content-Disposition 标头字段中存在 filename 参数,则正文部分将写入 System.IO.FileStream中;否则,正文部分将写入 System.IO.MemoryStream 中。这将更加便于处理作为窗体数据和文件内容的组合的 MIME 多部分 HTML 窗体数据。
//读取字节流
await Request.Content.ReadAsMultipartAsync(provider);
读取 MIME 多部分消息中的所有正文部分,并通过使用 streamProvider 实例确定每个正文部分内容的写入位置,来生成一组 System.Net.Http.HttpContent实例作为结果。在这一行代码执行完毕后文件已经保存在上面定义的目录下,但其存在形式却是类似Body_xxxxx的无后缀文件,不能直接使用。
//读取文件
foreach (MultipartFileData file in provider.FileData)
{
//filePaths.Add(Path.GetFileName(file.LocalFileName));
string fileName = file.Headers.ContentDisposition.FileName;
FileInfo fileInfo = new FileInfo(file.LocalFileName);
fileInfo.CopyTo(Path.Combine(basePath, fileName), true);
fileInfo.Delete();
filePaths.Add(Path.Combine(basePath, fileName));
}
多个文件批量上传时,单个文件均是以MultipartFileData 形式作为httpContent的一部分存在的。从请求头中分别拿到这些文件的原有文件名及后缀,将临时文件另存为期望的文件名。
//读取附加参数
foreach (var key in provider.FormData.AllKeys)
{
param.Add(key, provider.FormData[key]);
}
多数情况下,文件上传并不是孤立存在的功能,往往需要在上传文件的同时传递附加参数,以便进行相应的业务处理。附加参数的传递是以Key-Value键值对的形式实现的,包含在整个Http请求的表单数据中。
return Request.CreateResponse(HttpStatusCode.OK, filePaths)
最后将文件上传后保存的路径作为结果返回。
在上述方法中,将文件流和参数均包含在同一个HttpContent中发送给API服务。方法的调用方式如下:
封装的方法TryUpload接受三个参数,参数action表示要访问的API路由地址,参数filePaths表示要上传的文件集合(包含文件实际路径及文件上传后要保存的文件名,保证上传到服务器的文件不会重名),参数param表示要传递的参数,以键值对形式存在。
其中ConnectionSettings为在app.js中定义的全局参数,用来获取API服务的基地址(如http://192.168.1.100:8000)。封装的方法同样包含三个参数,分别代表访问的API接口路由地址、上传文件集合和传输参数集合。fileService的使用方法如下:
使用HBuilder可以创建MUI的H5应用示例,在示例中包含了拍照上传和相册图片上传示例,上述代码就是在其基础上修改得到,有兴趣的可以自己试一试。
除了上述几种批量上传的形式外,使用ajax、WinForm也能实现批量文件上传并附带传递参数。只需一个web API就能实现不同平台的对接,方便之处令人欢喜。更多细节问题将在进一步使用过程中继续研究。
WebAPI服务端
WPF客户端
AngularJS客户端
MUI手机APP
Web API服务端
环境 | 版本 |
---|---|
操作系统 | Windows 10 prefessional |
编译器 | Visual Studio 2015 update3 |
public class FileController { private string baseUri = ConfigurationManager.AppSettings["UploadUri"];//文件保存路径 [AcceptVerbs("POST")] public async Task<HttpResponseMessage> UploadFileWithData() { //判断请求是否包含multipart/form-data if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } //上传文件路径 string basePath = HttpContext.Current.Server.MapPath(baseUri); if (!Directory.Exists(basePath)) { Directory.CreateDirectory(basePath); } MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(basePath); List<string> filePaths = new List<string>(); Dictionary<string, string> param = new Dictionary<string, string>(); try { //读取字节流 await Request.Content.ReadAsMultipartAsync(provider); //读取文件 foreach (MultipartFileData file in provider.FileData) { //filePaths.Add(Path.GetFileName(file.LocalFileName)); string fileName = file.Headers.ContentDisposition.FileName; FileInfo fileInfo = new FileInfo(file.LocalFileName); fileInfo.CopyTo(Path.Combine(basePath, fileName), true); fileInfo.Delete(); filePaths.Add(Path.Combine(basePath, fileName)); } //读取附加参数 foreach (var key in provider.FormData.AllKeys) { param.Add(key, provider.FormData[key]); } //利用参数操作数据库 string strSql = "insert into T_Record (RecordId,UserName,OperateDate) values ("; strSql += string.Format("'{0}','{1}','{2}');", provider.FormData["RecordId"], provider.FormData["UserName"], provider.FormData["OperateDate"]); int num = DB.ExecuteCount(strSql); return Request.CreateResponse(HttpStatusCode.OK, filePaths); } catch (Exception ex) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex); } } }
//判断请求是否包含multipart/form-data
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
multipart/form-data为上传文件时使用的http请求头,如果服务端接收到的请求头中不包含multipart/form-data,服务端会认为这是一个不合要求的请求并返回HttpStatusCode.UnsupportedMediaType(不支持的媒体格式)。
//上传文件路径
string basePath = HttpContext.Current.Server.MapPath(baseUri);
if (!Directory.Exists(basePath))
{
Directory.CreateDirectory(basePath);
}
文件上传到服务器上需要有位置进行存储,HttpContext.Current.Server.MapPath(baseUri)是指相对于当前API服务根目录的baseUri路径,baseUri在web.config中定义。若不存在存储目录则由API服务自动创建。在这里可根据实际需要改成绝对路径。
MultipartFormDataStreamProvider provider = new MultipartFormDataStreamProvider(basePath);
MultipartFormDataStreamProvider 是流提供程序,它将查看Content-Disposition 标头字段,并根据 filename 参数是否存在来确定输出 System.IO.Stream。如果Content-Disposition 标头字段中存在 filename 参数,则正文部分将写入 System.IO.FileStream中;否则,正文部分将写入 System.IO.MemoryStream 中。这将更加便于处理作为窗体数据和文件内容的组合的 MIME 多部分 HTML 窗体数据。
//读取字节流
await Request.Content.ReadAsMultipartAsync(provider);
读取 MIME 多部分消息中的所有正文部分,并通过使用 streamProvider 实例确定每个正文部分内容的写入位置,来生成一组 System.Net.Http.HttpContent实例作为结果。在这一行代码执行完毕后文件已经保存在上面定义的目录下,但其存在形式却是类似Body_xxxxx的无后缀文件,不能直接使用。
//读取文件
foreach (MultipartFileData file in provider.FileData)
{
//filePaths.Add(Path.GetFileName(file.LocalFileName));
string fileName = file.Headers.ContentDisposition.FileName;
FileInfo fileInfo = new FileInfo(file.LocalFileName);
fileInfo.CopyTo(Path.Combine(basePath, fileName), true);
fileInfo.Delete();
filePaths.Add(Path.Combine(basePath, fileName));
}
多个文件批量上传时,单个文件均是以MultipartFileData 形式作为httpContent的一部分存在的。从请求头中分别拿到这些文件的原有文件名及后缀,将临时文件另存为期望的文件名。
//读取附加参数
foreach (var key in provider.FormData.AllKeys)
{
param.Add(key, provider.FormData[key]);
}
多数情况下,文件上传并不是孤立存在的功能,往往需要在上传文件的同时传递附加参数,以便进行相应的业务处理。附加参数的传递是以Key-Value键值对的形式实现的,包含在整个Http请求的表单数据中。
return Request.CreateResponse(HttpStatusCode.OK, filePaths)
最后将文件上传后保存的路径作为结果返回。
WPF客户端
创建WPF项目,同时在项目中引用System.Net.Http,以便使用HttpClient访问API服务。同时为了使用方便,封装了上传通用方法,代码如下:private static readonly string BaseUri = ConfigurationManager.AppSettings["ApiUri"]; /// <summary> /// 批量文件上传--含参数--同步方法 /// </summary> /// <param name="action">方法名</param> /// <param name="filePaths">文件</param> /// <param name="param">参数</param> /// <returns></returns> public static string TryUpload(string action, Dictionary<string, string> filePaths, Dictionary<string, string> param) { using (HttpClient client = new HttpClient()) { using (var content = new MultipartFormDataContent()) { //基地址/域名 client.BaseAddress = new Uri(BaseUri); //文件 foreach (var filePath in filePaths) { var fileContent = new ByteArrayContent(System.IO.File.ReadAllBytes(filePath.Key)); fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { FileName = filePath.Value }; content.Add(fileContent); } //参数 foreach (var pair in param) { var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(pair.Value)); dataContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") { Name = pair.Key }; content.Add(dataContent); } //上传提交 HttpResponseMessage response = client.PostAsync(action, content).Result; if (response.IsSuccessStatusCode) { return response.Content.ReadAsStringAsync().Result; } else { return ""; } } } }
在上述方法中,将文件流和参数均包含在同一个HttpContent中发送给API服务。方法的调用方式如下:
private void btnUpload_Click(object sender, RoutedEventArgs e) { try { //文件 Dictionary<string, string> filePaths = new Dictionary<string, string>(); filePaths.Add("路径\当前文件名1.jpg", "新文件名1.jpg"); filePaths.Add("路径\当前文件名2.jpg", "新文件名2.jpg"); Dictionary<string, string> param = new Dictionary<string, string>(); param.Add("UserName", "admin"); param.Add("OperateDate", DateTime.Now); param.Add("RecordID", Guid.NewGuid()); this.Dispatcher.Invoke(() => { //上传文件 string result = TryUpload("api/File/UploadFileWithData", filePaths, param); }); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
封装的方法TryUpload接受三个参数,参数action表示要访问的API路由地址,参数filePaths表示要上传的文件集合(包含文件实际路径及文件上传后要保存的文件名,保证上传到服务器的文件不会重名),参数param表示要传递的参数,以键值对形式存在。
AngularJS客户端
创建AngularJS应用,使用ng-file-upload控件,需要引用文件ng-file-upload-shim.min.js和ng-file-upload.min.js。同样为了便于使用将上传方法封装到Service中,代码如下:var app= angular.module('AngularDemo', []) app.factory('fileService', ['$q', 'ConnectionSettings','Upload', function ( $q, ConnectionSettings,Upload) { var apiBaseUri = ConnectionSettings.apiUri; var service = {}; //文件上传 service.UploadFile=function(action,files,param){ var defered = $q.defer(); Upload.upload({ url:apiBaseUri+action, data:param, file:files }).success(function(response){ defered.resolve(response); }).error(function (err, status) { defered.reject(err); }); return defered.promise; }; return service; }]);
其中ConnectionSettings为在app.js中定义的全局参数,用来获取API服务的基地址(如http://192.168.1.100:8000)。封装的方法同样包含三个参数,分别代表访问的API接口路由地址、上传文件集合和传输参数集合。fileService的使用方法如下:
app.controller('fileController', ['$scope', '$location', 'fileService',function($scope, $location, fileService, ) { //上传文件 $scope.UploadedFiles=[]; $scope.files=[]; $scope.data={UserName:'admin',OperateDate:'2017/03/22',RecordeId:'1'}; $scope.startUploading=function(files,data){ fileService.UploadFile('api/File/UploadFileWithData',files,data).then(function(response){ $.each(response, function(index,content) { uploadFilesName+=content.NewName+","; $scope.UploadedFiles.push(content); }); if(uploadFilesName.length>0) { uploadFilesName=uploadFilesName.substring(0,uploadFilesName.length-1); } }); }; }])
MUI手机APP
MUI是国内一套不错的HTML5 UI框架,可以方便的开发接近原生APP的移动应用,与现有的WebAPI结合十分方便。MUI已经封装了文件上传的相关方法,可以直接使用,十分方便,代码如下:<script src="../js/mui.min.js"></script> <script type="text/javascript" src="../js/common.js" ></script> <script type="text/javascript" src="../js/immersed.js" ></script> <script type="text/javascript"> var url="http://192.168.1.100:8000/api/File/UploadFileWithData"; var files=['路径\文件名1.jpg','路径\文件名2.jpg']; var data={UserName:'admin',OperateDate:'2017/03/22',RecordId:'1'}; mui.plusReady(function(){ //上传按钮事件 document.getElementById("saveBtn").addEventListener('tap', function() { upload(files); }); }); // 上传文件 function upload(files){ if(files.length<=0){ plus.nativeUI.alert("没有添加上传文件!"); return; } var wt=plus.nativeUI.showWaiting(); var task=plus.uploader.createUpload(url,{method:"POST"},function(t,status){ //上传完成 if(status==200){ mui.toast("同步完成!"); wt.close(); }else{ mui.toast("同步失败:"+status); wt.close(); } } ); //参数 task.addData("UserName",data.UserName); task.addData("OperateDate",data.OperateDate); task.addData("RecordId",data.RecordId); //文件 for(var i=0;i<files.length;i++){ var f=files[i]; var arry=f.split('/'); task.addFile(f,{name:arry[arry.length-1],key:arry[arry.length-1]}); } task.start();//启动上传 } </script>
使用HBuilder可以创建MUI的H5应用示例,在示例中包含了拍照上传和相册图片上传示例,上述代码就是在其基础上修改得到,有兴趣的可以自己试一试。
除了上述几种批量上传的形式外,使用ajax、WinForm也能实现批量文件上传并附带传递参数。只需一个web API就能实现不同平台的对接,方便之处令人欢喜。更多细节问题将在进一步使用过程中继续研究。
相关文章推荐
- angularjs+文件上传 http头content-type字段变化
- Angular.JS和Spring MVC之文件上传的兼容
- SpringMvc+Angularjs 多文件批量上传
- AngularJS下$http上传文件(AngularJS file upload/post file)
- SSM/angularjs _ 文件的上传下载
- AngularJS $http上传文件(AngularJS file upload/post file)
- angularjsFileUpload+Springmvc上传文件
- angularjsFileUpload+Springmvc上传文件
- spring boot+angularjs实现文件上传
- webAPI+angularJS文件上传和下载
- SpringMvc+Angularjs 实现多文件批量上传
- AngularJS+Bootstrap实现多文件上传与管理
- 使用js来实现模拟无刷新文件上传。
- IE+JS: 上传之前检测图片文件大小
- JS控制上传文件大小
- JS 控制RadioButtonList 获得上传文件类型后选中
- js实现文件批量上传,支持ie firefox
- 上传文件限制文件格式js 及浏览框左面的text框只读
- JS验证上传文件类型
- js判断input file上传文件路径是否正确