• IT轮子系列(五)——MVC API 文件上传,总有一款是你需要的


    前言

    在对外提供的接口时,也常常需要提供上传文件的。在这篇文章中会描述三种上传方式。

    1、第一款,通过Base64字符上传——PostFromBase64Str

    首先,定义上传数据模型。对于模型的定义,一般都会给定名称、文件类型、数据字段。如下代码所示:

     1   /// <summary>
     2     /// 文件模型
     3     /// </summary>
     4     public class FileModel
     5     {
     6         /// <summary>
     7         /// 文件名称
     8         /// </summary>
     9         public string FileName { get; set; }
    10         /// <summary>
    11         /// 文件类型,如jpg,doc
    12         /// </summary>
    13         [Required(ErrorMessage = "{0}不能为空")]
    14         public string FileType { get; set; }
    15         /// <summary>
    16         /// base64字符串
    17         /// </summary>
    18         [Required(ErrorMessage="{0}不能为空")]
    19         public string FileStr { get; set; }
    20 
    21     }
    View Code

    ps:这篇文章也是在介绍swagger使用中使用的解决方案。

    其次,在API控制器中定义接收文件的方法

     1         /// <summary>
     2         /// 使用base64字符串上传文件(图片)
     3         /// </summary>
     4         /// <param name="model">文件模型</param>
     5         /// <returns>ResultInfo</returns>
     6         [HttpPost]
     7         [Swashbuckle.Swagger.Annotations.SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResultInfo<string>))]
     8         public HttpResponseMessage PostFromBase64Str([FromBody]FileModel model)
     9         {
    10             ResultInfo<string> result = new ResultInfo<string>();
    11             try
    12             {
    13                 string folderPath = System.Web.HttpContext.Current.Server.MapPath(@"/Upload/ApiFile/");
    14                 //判断项目路径是否存在
    15                 if (!System.IO.Directory.Exists(folderPath))
    16                 {                    
    17                     System.IO.Directory.CreateDirectory(folderPath);
    18                 }
    19                 //生成GUID文件名
    20                 string fileName = System.Guid.NewGuid().ToString().Replace("-", "") + "." + model.FileType;
    21 
    22                 //读取字符串转为字节数组
    23                 byte[] strs = Convert.FromBase64String(model.FileStr);
    24                 //文件保存路径
    25                 string filePath = folderPath + fileName;
    26                 using (System.IO.FileStream fs = new System.IO.FileStream(filePath,FileMode.OpenOrCreate,FileAccess.Write))
    27                 {
    28                     //保存文件
    29                     fs.WriteAsync(strs, 0, strs.Length);
    30                 }
    31             }
    32             catch (Exception ex)
    33             {
    34                 result.Status = "FAIL";
    35                 result.Msg = ex.Message;
    36             }
    37             return toJson<string>(result);
    38         }
    View Code

    这里要注意的是,有些工具将文件转为BASE64是带格式说明的,如下图:

    在实际项目中,这个数据格式要跟前端或者调用方协商好,数据是只传逗号后面部分还是传所有的;如果是所有的,在接收的时候就需要后台处理,把数据截取出来。否则会保存不成功。

    好了,第一款,上传文件就这么简单。

    第二款,从Form表单上传文件—— PostFromFormData

    我们都知道,在form表单中,可以使用input type=file 控件上传文件。在API 中也可以使用这个知识点。

     使用这种方法上传,需要了解一个类:System.Web.HttpContext .Current.Request 。今天,在敲代码的时候,发现深入理解并用好这个Request就可以解决大部分请求的问题。建议,设置个断点进去看看它都有那些内容。

    PostFromFormData 方法代码如下:

     1         /// <summary>
     2         /// 使用form表单上传文件
     3         /// </summary>
     4         /// <returns>ResultInfo</returns>
     5         [HttpPost]
     6         [Swashbuckle.Swagger.Annotations.SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResultInfo<string>))]
     7         public HttpResponseMessage PostFromFormData()
     8         {
     9             ResultInfo<string> result = new ResultInfo<string>();
    10             try
    11             {
    12                 var rq = System.Web.HttpContext.Current.Request;
    13                 //这里跟前端调用或调用方 协商好,请求的content 类型必须为multipart/form-data
    14                 if (!rq.ContentType.Contains("multipart"))
    15                 {
    16                     result.Status = "FAIL";
    17                     return toJson<string>(result);
    18                 }
    19                 //读取表单数据,若是有其他参数,可以约定好统一放到form中,如Token,timestamp
    20                 var form = rq.Form;
    21                 //获取上传的文件集合,可能上传了多个文件
    22                 var files = rq.Files;
    23                 var count = files.Count;//获取文件的数量
    24                 var fileExtention = "";//文件的扩展名
    25                 if (count > 0)
    26                 {
    27                     // 在遍历文件前,需验证form上传的参数
    28                     //在实际项目中,常常需要做一些基础参数的验证
    29                     //if (!form.AllKeys.Contains("Token")) //若form没有添加Token参数,直接返回
    30                     //{
    31                     //    result.Status = "FAIL";
    32                     //    return toJson<string>(result);
    33                     //}
    34                     //遍历所有的文件
    35                     for (int i = 0; i < count; i++)
    36                     {
    37                         var file = files[i];
    38                         var fileName = file.FileName;
    39                         var fileType = file.ContentType;//文件类型
    40                         //
    41                         fileExtention = fileName.Split('.')[1];
    42                         //读取文件的二进制流
    43                         var itemStream = file.InputStream;
    44                         var filePath = "";//存储返回路径  在实际项目中,数据库存储的是相对路径,方便更换服务器。
    45                         bool back = SaveFileFromStream(itemStream, fileExtention, out filePath);
    46                         if (!back)
    47                         {
    48                             result.Status = "FAIL";
    49                             result.Msg = "UploadFail";
    50                         }
    51                     }
    52                 }
    53                 else
    54                 {
    55                     //使用二进制上传文件                    
    56                     var stream = rq.InputStream;
    57                 }
    58 
    59             }
    60             catch (Exception ex)
    61             {
    62                 result.Status = "FAIL";
    63                 result.Msg = ex.Message;
    64             }
    65             return toJson<string>(result);
    66         }
    View Code

    其中,保存文件的私有方法如下:

    这样,只要完成以上两个方法就可以通过form表单上传文件拉。

    PS:因为要测试文件的上传,暂时不懂如何配置swagger,让它支持上传文件。在这里,推荐使用Postman(网上自行下载安装) 进行测试。如下图所示:

    1、设定请求地址以及请求内容

    2、设定请求的数据,如下图:

    这样,当我们点击发送之后,就能看到返回OK的结果。同时,在我们网站的目录,会看到上传成功的文件,如下图:

     第三款,使用二进制Binary上传文件

    从上面的方法PostFromFormData中就可以看到,count>0的分支,就是使用二进制上传的文件。其中的原理跟form表单是一样的,都是通过Inputstream。

    这里面要注意的是,因为使用binary上传的时候,没法定义form表单数据。因此,需要在请求headers中添加参数。如协商好的文件类型等。

    代码如下:

     运行项目,启动postman,进行测试:

    1、配置请求头

    2、修改body 为Binay

     点击发送,同样可以看到OK返回。在文件夹目录下也可看到新上传的文件:

    总结

    以上,就是通过API上传文件的三款方式,希望可以帮助到大家。在测试的时候,只是上传了图片。其实,在form表单上传中并不只限于图片,比如也可上传doc,txt等。因为都是通过inputstream保存文件,同时,其上传大文件类型也是可以知道的,因此,form表单这种方式是通用的。

    PS:上传文件,应该还涉及到大文件的断点续传,不过在实际项目还没有碰到过断点需求这样的需求,这里就不作描述了。

    大家,GOOD NIGTH!

    补记

    最近,ios、android 同事在调用这个接口的时候,发现,只有一个文件的时候,调用是成功。多个文件,就只接收到一个文件。后来发现,是他们上传的格式有问题。因此,大家在提供这个接口给第三方调用的是,务必按以下的

    文件格式上传:

    .POST /api/FriendDiary/PostFile HTTP/1.1
    Content-Type: multipart/form-data; boundary=--------------------------262463402950365865406301
    Host: 120.24.246.194:8888
    content-length: 239808
    
    
    ----------------------------262463402950365865406301
    Content-Disposition: form-data; name="UserId"
    
    1
    ----------------------------262463402950365865406301
    Content-Disposition: form-data; name="Token"
    
    123456
    ----------------------------262463402950365865406301
    Content-Disposition: form-data; name="Timestamp"
    
    wwww
    ----------------------------262463402950365865406301
    Content-Disposition: form-data; name="RecordId"
    
    1
    ----------------------------262463402950365865406301
    Content-Disposition: form-data; name=""; filename="............_20170915173117.png"
    Content-Type: image/png
    
    文件流
    ----------------------------262463402950365865406301--
    
                                                  .

    各个参数间,都是由边界262463402950365865406301相隔的。同时,每个参数行后面都是一个回车。APP部门同事上传多个文件之所以没有成功,就是在拼接第二个文件流的时候,少了回车。

  • 相关阅读:
    Maven报错,没有有效的生命周期
    6张图解释IO流
    传统Java JDBC
    快速杀死占用8080端口进程的批处理(kill-8080.bat)
    ubuntu环境配置终极解答
    Linux系统下Java开发环境的配置(未完...)
    Linux常用命令及操作(第二弹)
    Linux下安装Mysql
    Linux常用命令及操作
    int转LPCTSTR
  • 原文地址:https://www.cnblogs.com/liangxiarong/p/7768824.html
Copyright © 2020-2023  润新知