Asp.Net Core 轻松学-一行代码搞定文件上传

Asp.Net Core 轻松学-一行代码搞定文件上传

 

前言

    在 Web 应用程序开发过程中,总是无法避免涉及到文件上传,这次我们来聊一聊怎么去实现一个简单方便可复用文件上传功能;通过创建自定义绑定模型来实现文件上传。

1. 实现自定义绑定模型
  • 1.1 在 Asp.Net Core MVC 中,内置了很多种绑定模型,让我们可以很方便的去使用,比如下面常用的几种绑定模型
 FromBodyAttribute
 FromFromAttribute
 FromQueryAttribute
 FromHeaderAttribute
 FromServicesAttribute
 FromRouteAttribute
  • 常见用法比如
    [HttpPost]
     public async Task<IActionResult> PostInfo([FromBody]UserInfo user,[FromQuery] string city)
    {
        ...
    }
    1. 查看以上绑定模型,唯独缺少一个 FromFileAttribute ,下面就来实现一个自己的 FromFileAttribute
    public class FromFileAttribute : Attribute, IBindingSourceMetadata
    {
        public BindingSource BindingSource => BindingSource.FormFile;
    }
  • 非常简单,就三行代码,完全照抄系统内置的绑定模型,唯一不同的就是指定 BindingSource 为 BindingSource.FormFile。
2. 实现一个上传文件实体类,专门用于接收客户端参数
  • 2.1 创建 UserFile
public class UserFile
    {
        public string FileName { get; set; }
        public long Length { get; set; }
        public string Extension { get; set; }
        public string FileType { get; set; }
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span>[] Filters = { <span class="hljs-string">".jpg"</span>, <span class="hljs-string">".png"</span>, <span class="hljs-string">".bmp"</span> };
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsValid =&gt; !<span class="hljs-keyword">string</span>.IsNullOrEmpty(<span class="hljs-keyword">this</span>.Extension) &amp;&amp; Filters.Contains(<span class="hljs-keyword">this</span>.Extension);

    <span class="hljs-keyword">private</span> IFormFile file;
    <span class="hljs-keyword">public</span> IFormFile File
    {
        <span class="hljs-keyword">get</span> { <span class="hljs-keyword">return</span> file; }
        <span class="hljs-keyword">set</span>
        {
            <span class="hljs-keyword">if</span> (<span class="hljs-keyword">value</span> != <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">this</span>.file = <span class="hljs-keyword">value</span>;

                <span class="hljs-keyword">this</span>.FileType = <span class="hljs-keyword">this</span>.file.ContentType;
                <span class="hljs-keyword">this</span>.Length = <span class="hljs-keyword">this</span>.file.Length;
                <span class="hljs-keyword">this</span>.Extension = <span class="hljs-keyword">this</span>.file.FileName.Substring(file.FileName.LastIndexOf(<span class="hljs-string">'.'</span>));
                <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(<span class="hljs-keyword">this</span>.FileName))
                    <span class="hljs-keyword">this</span>.FileName = <span class="hljs-keyword">this</span>.file.FileName;
            }
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">SaveAs</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> destinationDir = <span class="hljs-literal">null</span></span>)
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.file == <span class="hljs-literal">null</span>)
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-string">"没有需要保存的文件"</span>);

        <span class="hljs-keyword">if</span> (destinationDir != <span class="hljs-literal">null</span>)
            Directory.CreateDirectory(destinationDir);

        <span class="hljs-keyword">var</span> newName = DateTime.Now.Ticks;
        <span class="hljs-keyword">var</span> newFile = Path.Combine(destinationDir ?? <span class="hljs-string">""</span>, <span class="hljs-string">$"<span class="hljs-subst">{newName}</span><span class="hljs-subst">{<span class="hljs-keyword">this</span>.Extension}</span>"</span>);
        <span class="hljs-keyword">using</span> (FileStream fs = <span class="hljs-keyword">new</span> FileStream(newFile, FileMode.CreateNew))
        {
            <span class="hljs-keyword">await</span> <span class="hljs-keyword">this</span>.file.CopyToAsync(fs);
            fs.Flush();
        }

        <span class="hljs-keyword">return</span> newFile;
    }
}</code></pre>
  • UserFile 是一个带保持文件行为的实体类,该类的公共属性用于从表单域中接收和属性名称相同的表单值,其中公共属性 File 用于接收文件,并在设置值的时候去做一些其它属性初始化的工作,比如文件长度和扩展名、文件类型
  • 其中还实现了一个简单的文件过滤器,判断客户端上传的文件是否属于服务端允许上传的文件扩展名
  • 最后 SaveAs(string destinationDir = null) 通过传入指定目录,将文件保存,并返回保存后的文件绝对路径
3. 上传文件
  • 3.1 下面就定义一个简单的 API 接口,用于测试上传文件
        [HttpPost]
        public async Task<IActionResult> Post([FromFile]UserFile file)
        {
            if (file == null || !file.IsValid)
                return new JsonResult(new { code = 500, message = "不允许上传的文件类型" });
        <span class="hljs-keyword">string</span> newFile = <span class="hljs-keyword">string</span>.Empty;
        <span class="hljs-keyword">if</span> (file != <span class="hljs-literal">null</span>)
            newFile = <span class="hljs-keyword">await</span> file.SaveAs(<span class="hljs-string">"/data/files/images"</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResult(<span class="hljs-keyword">new</span> { code = <span class="hljs-number">0</span>, message = <span class="hljs-string">"成功"</span>, url = newFile });
    }</code></pre>
  • 3.2 首先是在 Post([FromFile]UserFile file) 中使用上面创建的 FromFileAttribute 对模型 UserFile 进行绑定,然后验证文件是否正确,接下来通过 file.SaveAs("/data/files/images"); 保存文件

  • 3.3 上传代码非常简单,几乎到了无法精简的程度,最终发挥作用的就是 file.SaveAs 操作

4. 上传测试
  • 4.1 现在通过控制台启动服务

  • 4.2 使用 Postman 模拟表单上传文件

  • 4.3 上传成功,现在来查看目录下是否有文件

结语
  • 在上传表单中,我们定义了附件的名称为 file 对应绑定模型的公共属性 File,这样模型就可以自动获得该文件
  • 表单中还传递了另外一个字段 filename,对应绑定模型的公共属性 FileName,实现自定义文件友好显示名称
  • 通过自定义模型绑定,实现了快速上传文件功能,该功能只能用于上传小文件,对于大文件,还是需要实现分片上传,或者使用 CDN 等服务商的接口
示例代码下载

(经 海阔天空XM. 提醒,由于上传了编译后的 dll 引起部分朋友杀毒软件误报,现已重新上传,感谢!)

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.UploadFile

微信公众号:DotNet程序园
欢迎关注收取阅读最新文章
  • 您随手点赞是我不断书写的动力,如有错误,欢迎指正
  • 出处:http://www.cnblogs.com/viter/
  • 推荐一个快速开发脚手架,基于 .netcore+pgsql,GitHub地址: https://github.com/lianggx/mystaging
  • 本文版权归作者和博客园共有,欢迎个人转载,必须保留此段声明;商业转载请联系授权,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 欢迎大家关注我的微信公众号,一起学习一起进步
标签: .netcore
27
0
« 上一篇:.NET Core 2.2 新增部分功能使用尝鲜
» 下一篇:Asp.Net Core 轻松学-基于微服务的后台任务调度管理器
	</div>
	<div class="postDesc">posted @ <span id="post-date">2018-12-06 08:19</span> <a href="https://www.cnblogs.com/viter/">Ron.Liang</a> 阅读(<span id="post_view_count">2871</span>) 评论(<span id="post_comment_count">20</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=10074766" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(10074766);return false;">收藏</a></div>
</div>
<script src="//common.cnblogs.com/highlight/9.12.0/highlight.min.js"></script><script>markdown_highlight();</script><script type="text/javascript">var allowComments=true,cb_blogId=30433,cb_entryId=10074766,cb_blogApp=currentBlogApp,cb_blogUserGuid='e9823d0b-63cf-dd11-9e4d-001cf0cd104b',cb_entryCreatedDate='2018/12/6 8:19:00';loadViewCount(cb_entryId);var cb_postType=1;var isMarkdown=true;</script>
posted @ 2019-07-19 16:04  奋斗的中年人哈哈哈  阅读(311)  评论(0编辑  收藏  举报