ASP.NET Core MVC 从入门到精通之文件上传

随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用于初学者,在校毕业生,或其他想从事ASP.NET Core MVC 系统开发的人员。经过前几篇文章的讲解,初步了解ASP.NET Core MVC项目创建,启动运行,以及命名约定,创建控制器,视图,模型,接收参数,传递数据ViewData,ViewBag,路由,页面布局,wwwroot和客户端库,Razor语法,EnityFrameworkCore与数据库,HttpContext,Request,Response,Session,序列化等内容,今天继续讲解ASP.NET Core MVC 中文件上传等相关内容,仅供学习分享使用。

d96f7175cefc229270d56cbd54711bc8.png

概述


在实际应用开发中,文件上传是非常常见的功能,文件上传主要分为单文件上传,多文件上传,文件与其他内容混合上传,大文件上传几种情况,本文会分别讲解。

IFormFile


在ASP.NET Core MVC项目中,IFormFile表示使用 HttpRequest 发送的文件,可用于文件流的接收。 是用于处理或保存文件的文件的 C# 表示形式。

文件上传使用的磁盘和内存取决于并发文件上传的数量和大小。如果应用尝试缓冲过多上传,站点就会在内存或磁盘空间不足时崩溃。如果文件上传的大小或频率会消耗应用资源,请使用流式传输。

IFormFile的属性和方法如下:

acd2be9517d6562fee9042655c695bf8.png

 对于小文件的上传,一般采用IFormFile;大文件上传,采用流式上传,以实现可靠稳定传输。

单个文件上传


单文件上传功能主要分为两部分:文件上传视图和后台处理方法。

1. 文件上传视图

首先创建视图,用于单个文件上传。关于视图有两点说明,如下所示:

  1. 文件上传通过form表单,采用post方式,加密类型为multipart/form-data

  2. 文件上传采用input控件,类型为file。

视图代码如下所示:

<form method="post" enctype="multipart/form-data" action="/File/OneFileUpload">
    <h1>单文件上传</h1>
    <div>
        <span>文件:</span>
        <input type="file" name="file" />
    </div>
    <input type="submit" value="上传" />
</form>

2. 后台处理方法

form提交后台处理方法OneFileUpload,关于处理方法有几点说明,如下所示:

  1. 方法中的参数IFormFile  file用于接收客户端上传的文件,其他file和视图中上传控件的name一一对应。如果错误,则无法上传。

  2. _webHostEnvironment 为控制器通过接口注入的IWebHostEnvironment类型的获取站点信息接口,主要用于获取站点根目录。

  3. 调用IFormFile的CopyTo方法进行保存,此方法接收Stream类型的参数。

上传处理代码,如下所示:

/// <summary>
/// 单文件上传
/// </summary>
/// <returns></returns>
public IActionResult OneFileUpload(IFormFile file)
{
    var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
    using (FileStream fs = new FileStream(path, FileMode.Create))
    {
        file.CopyTo(fs);
    }
    return Ok("上传成功");
}

多文件上传


多文件上传表示一次可以上传多个文件。上传功能主要分为两部分:文件上传视图和后台处理方法。

1. 多文件上传视图

input控件在类型为file时表示文件上传,默认是单个文件上传,通过设置multiple属性,可实现多文件上传。视图代码如下所示:

<form method="post" enctype="multipart/form-data" action="/File/MoreFileUpload">
    <h1>多文件上传</h1>
    <div>
        <span>文件:</span>
        <input type="file" name="files" multiple />
    </div>
    <input type="submit" value="上传" />
</form>

2. 多文件后台处理方法

多个文件上传,参数为IFormFile数组类型,可以接收上传文件列表,然后循环获取并进行保存即可。如下所示:

/// <summary>
/// 多文件上传
/// </summary>
/// <returns></returns>
public IActionResult MoreFileUpload(IFormFile[] files)
{
    foreach (var file in files)
    {
        var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
        using (FileStream fs = new FileStream(path, FileMode.Create))
        {
            file.CopyTo(fs);
        }
    }


    return Ok("上传成功");
}

文件文本混合上传


在实际应用中,文件上传只是一部分,还需要搭配其他的文本说明,如录入产品信息,并上传附件等。

1. 创建模型

在Product中,包含两个属性,一个字符串类型的Name,用于绑定名称,一个IFormFile类型的File,用于上传文件。如下所示:

namespace DemoCoreMVC.Models
{
    public class Product
    {
        public string Name { get; set; }


        public IFormFile File { get; set; }
    }
}

2. 视图绑定模型

在视图最顶部,为视图指定模型,如下所示:

@model DemoCoreMVC.Models.Product

3. 混合文本文件上传视图

在form表单中,除了文件上传控件,还有一个文件框,用于输入名称。其中控件name和模型相对应。如下所示:

<form method="post" enctype="multipart/form-data" action="/File/FileWithContentUpload">
    <h1>文件,文本混合上传</h1>
    <div>
        <span>名称:</span>
        <input type="text" name="Name"  />
    </div>
    <div>
        <span>文件:</span>
        <input type="file" name="File" />
    </div>
    <input type="submit" value="上传" />
</form>

4. 后台处理方法

文件文本混合上传,参数为模型Product,通过属性匹配接收参数,然后获取属性File对应的内存流进行保存即可。如下所示:

/// <summary>
/// 文件内容混合上传
/// </summary>
/// <returns></returns>
public IActionResult FileWithContentUpload(Product product)
{
    var file = product.File;
    var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads", string.Format("{0}_{1}", DateTime.Now.Ticks, file.FileName));
    using (FileStream fs = new FileStream(path, FileMode.Create))
    {
        file.CopyTo(fs);
    }
    return Ok($"{product.Name} 上传成功");
}

大文件上传


首先如何界定大文件/小文件,并没有统一的标准。根据官网相关参数说明:

  1. 默认情况下, HttpRequest.Form 不会缓冲整个请求正文 (BufferBody) ,但会缓冲包含的任何多部分表单文件。

  2. MultipartBodyLengthLimit 是缓冲表单文件的最大大小,默认值为 128MB。

  3. MemoryBufferThreshold 指示在转换为磁盘上的缓冲区文件之前,内存中的文件缓冲量,默认为 64KB。

  4. MemoryBufferThreshold 充当小型和大型文件之间的边界,这些文件根据应用资源和方案而引发或降低。

大文件上传采用流式传输,可降低上传文件时对内存或磁盘空间的需求。

1. 创建视图

大文件和小文件上传在视图上并无差别,只是后台处理方法不同,如下所示:

<form method="post" enctype="multipart/form-data" action="/File/BigFileUpload">
    <h1>大文件上传</h1>
    <div>
        <span>文件:</span>
        <input type="file" name="file" />
    </div>
    <input type="submit" value="上传" />
</form>

2. 后台处理方法

首先创建大文件上传帮助类MultipartRequestHelper,如下所示:

using System;
using System.IO;
using Microsoft.Net.Http.Headers;
namespace DemoCoreMVC
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;


            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }


            if (boundary.Length > lengthLimit)
            {
                throw new InvalidDataException(
                    $"Multipart boundary length limit {lengthLimit} exceeded.");
            }


            return boundary;
        }


        public static bool IsMultipartContentType(string contentType)
        {
            return !string.IsNullOrEmpty(contentType)
                   && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
        }


        public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
            return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
        }


        // 如果一个section的Header是: Content-Disposition: form-data; name="myfile1"; filename="F:\Misc 002.jpg"
        // 那么本方法返回: Misc 002.jpg
        public static string GetFileName(ContentDispositionHeaderValue contentDisposition)
        {
            return Path.GetFileName(contentDisposition.FileName.Value);
        }


    }
}

处理方法BigFileUploadAsync,关于Action说明,如下所示:

Action中要进行DisableRequestSizeLimit特性说明,否则会有大小限制【InvalidDataException: Multipart body length limit 16384 exceeded】。

在该操作中,使用 MultipartReader 读取窗体的内容,它会读取每个单独的 MultipartSection,从而根据需要处理文件或存储内容。读取多部分节后,该操作会执行自己的模型绑定。

/// <summary>
/// 大文件上传
/// </summary>
/// <returns></returns>
[DisableRequestSizeLimit]
public async Task<IActionResult> BigFileUploadAsync()
{
    var contentType = Request.ContentType;
    if (!MultipartRequestHelper.IsMultipartContentType(contentType))
    {
        ModelState.AddModelError("File",
            $"上传文件类型不对.");
        return BadRequest(ModelState);
    }
    var path = Path.Combine(_webHostEnvironment.ContentRootPath, "uploads");


    var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);


    var reader = new MultipartReader(boundary, HttpContext.Request.Body);


    var section = await reader.ReadNextSectionAsync();


    while (section != null)
    {
        var hasContentDispositionHeader =
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);


        if (hasContentDispositionHeader)
        {
            if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
            {
                ModelState.AddModelError("File",
                    $"The request couldn't be processed (Error 2).");


                return BadRequest(ModelState);
            }
            else
            {
                var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
                var loadBufferBytes = 1024;//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。


                using (var targetFileStream = new FileStream(path + "\\" + string.Format("{0}_{1}", DateTime.Now.Ticks, fileName), FileMode.Create, FileAccess.ReadWrite))
                {
                    using (section.Body)
                    {
                        //section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据(如果section.Body.Read方法返回0,表示数据流已经到末尾,数据已经全部都读取完了),再将数据写入到targetFileStream
                        await section.Body.CopyToAsync(targetFileStream, loadBufferBytes);
                    }
                }
            }
        }
        section = await reader.ReadNextSectionAsync();
    }
    return Ok("上传成功");
}

注意:在文件上传功能中,上传后的文件一般都要进行重命名的,否则如果客户端上传相同名称的文件,则可能会被覆盖。在本例中,在原文件前面加上了时间戳,以减少重复的概率。

文件上传校验


在实际开发中,为了避免客户端上传不满足条件的文件,一般都会进行校验。

  1. 文件扩展名验证:应在允许的扩展名列表中查找上传的文件的扩展名。

  2. 文件签名验证:文件的签名由文件开头部分中的前几个字节确定。可以使用这些字节指示扩展名是否与文件内容匹配。示例应用检查一些常见文件类型的文件签名。

  3. 文件名安全:切勿使用客户端提供的文件名来将文件保存到物理存储。

  4. 文件大小验证:限制上传的文件的大小。

文件上传安全


为避免文件上传功能造成攻击可能性,常规安全措施如下:

  1. 将文件上传到专用文件上传区域,最好是非系统驱动器。使用专用位置便于对上传的文件实施安全限制。禁用对文件上传位置的执行权限。

  2. 请勿将上传的文件保存在与应用相同的目录树中。

  3. 使用应用确定的安全的文件名。请勿使用用户提供的文件名或上传的文件的不受信任的文件名。† 当显示不受信任的文件名时 HTML 会对它进行编码。例如,记录文件名或在 UI 中显示(Razor 自动对输出进行 HTML 编码)。

  4. 按照应用的设计规范,仅允许已批准的文件扩展名。

  5. 验证是否对服务器执行客户端检查。† 客户端检查易于规避。

  6. 检查已上传文件的大小。设置一个大小上限以防止上传大型文件。

  7. 文件不应该被具有相同名称的上传文件覆盖时,先在数据库或物理存储上检查文件名,然后再上传文件。

  8. 先对上传的内容运行病毒/恶意软件扫描程序,然后再存储文件。

参考文章


本篇文章主要参考内容如下:

1. 官方文档:https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0

-

技术群:添加小编微信并备注进群

小编微信:mm1552923   

公众号:dotNet编程大全    

以上就是ASP.NET Core MVC从入门到精通之文件上传的全部内容

,关于ASP.NET Core MVC 从入门到精通其他文章,可通过以下链接查看:

ASP.NET Core MVC 从入门到精通之初窥门径

ASP.NET Core MVC 从入门到精通之接化发(一)

ASP.NET Core MVC 从入门到精通之接化发(二)

ASP.NET Core MVC 从入门到精通之路由

ASP.NET Core MVC 从入门到精通之布局

ASP.NET Core MVC 从入门到精通之wwwroot和客户端库

ASP.NET Core MVC 从入门到精通之Razor语法

ASP.NET Core MVC 从入门到精通之数据库

ASP.NET Core MVC 从入门到精通之HttpContext

ASP.NET Core MVC 从入门到精通之序列化


学习编程,从关注【老码识途】开始!!!

7878ed4d5c14ec48933ea40db604b58c.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值