Asp Net Core Api文件流上传

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、文件流

那么接下来,我们来看看怎么用文件流来上传大文件,避免一次性将所有上传的文件都加载到服务器内存中。用文件流来上传比较麻烦的地方在于你无法使用ASP.NET Core MVC的模型绑定器来将上传文件反序列化为C#对象(如同前面介绍的IFormFile接口那样)。首先我们需要定义类MultipartRequestHelper,用于识别Http请求中的各个section类型(是表单键值对section,还是上传文件section)

using System;
using System.IO;
using Microsoft.Net.Http.Headers;

namespace AspNetCore.MultipartRequest
{
    public static class MultipartRequestHelper
    {
        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
        // The spec says 70 characters is a reasonable limit.
        public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
        {
            //var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary);// .NET Core <2.0
            var boundary = Microsoft.Net.Http.Headers.HeaderUtilities.RemoveQuotes(contentType.Boundary).Value; //.NET Core 2.0
            if (string.IsNullOrWhiteSpace(boundary))
            {
                throw new InvalidDataException("Missing content-type boundary.");
            }

            //注意这里的boundary.Length指的是boundary=---------------------------99614912995中等号后面---------------------------99614912995字符串的长度,也就是section分隔符的长度,上面也说了这个长度一般不会超过70个字符是比较合理的
            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;
        }

        //如果section是表单键值对section,那么本方法返回true
        public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
        {
            // Content-Disposition: form-data; name="key";
            return contentDisposition != null
                    && contentDisposition.DispositionType.Equals("form-data")
                    && string.IsNullOrEmpty(contentDisposition.FileName.Value) // For .NET Core <2.0 remove ".Value"
                    && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value); // For .NET Core <2.0 remove ".Value"
        }

        //如果section是上传文件section,那么本方法返回true
        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) // For .NET Core <2.0 remove ".Value"
                        || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value)); // For .NET Core <2.0 remove ".Value"
        }

        // 如果一个section的Header是: Content-Disposition: form-data; name="files"; filename="F:\Misc 002.jpg"
        // 那么本方法返回: files
        public static string GetFileContentInputName(ContentDispositionHeaderValue contentDisposition)
        {
            return contentDisposition.Name.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);
        }
    }
}

然后我们需要定义一个扩展类叫FileStreamingHelper,其中的StreamFiles扩展方法用于读取上传文件的文件流数据,并且将数据写入到服务器的硬盘上,其接受一个参数targetDirectory,用于声明将上传文件存储到服务器的哪个文件夹下。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace AspNetCore.MultipartRequest
{
    public static class FileStreamingHelper
    {
        private static readonly FormOptions _defaultFormOptions = new FormOptions();

        public static async Task<FileReturnType> StreamFiles(this HttpRequest request, string targetDirectory = null)
        {
            if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
            {
                throw new Exception($"Expected a multipart request, but got {request.ContentType}");
            }

            // Used to accumulate all the form url encoded key value pairs in the 
            // request.
            var formAccumulator = new KeyValueAccumulator();
			var fileEntity = new List<FileEntity>();
			
            var boundary = MultipartRequestHelper.GetBoundary(
                MediaTypeHeaderValue.Parse(request.ContentType),
                _defaultFormOptions.MultipartBoundaryLengthLimit);
            var reader = new MultipartReader(boundary, request.Body);

            var section = await reader.ReadNextSectionAsync();//用于读取Http请求中的第一个section数据
            while (section != null)
            {
                ContentDispositionHeaderValue contentDisposition;
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);

                if (hasContentDispositionHeader)
                {
                    /*
                    用于处理上传文件类型的的section
                    -----------------------------99614912995
                    Content - Disposition: form - data; name = "files"; filename = "Misc 002.jpg"

                    ASAADSDSDJXCKDSDSDSHAUSAUASAASSDSDFDSFJHSIHFSDUIASUI+/==
                    -----------------------------99614912995
                    */
                    if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                    {
                       var fileName = MultipartRequestHelper.GetFileName(contentDisposition);
                       var streamdFileContent = await FileHelpers.ProcessStreamedFile(section, fileName,targetDirectory)
                       fileEntity.Add( new FileEntity
                       {
                       		Name = fileName;
                       		AttachmentType = Path.GetExtension(fileName).ToLower(),
                       		SavePath = streamdFileContent.Item2,
                       		FileSize = FileHelper.GetLength(streamdFileContent.Item.Length); 
                       		FileFormat= Path.GetExtension(fileName).ToLower(),
                       }); 

                    }
                    /*
                    用于处理表单键值数据的section
                    -----------------------------99614912995
                    Content - Disposition: form - data; name = "SOMENAME"

                    Formulaire de Quota
                    -----------------------------99614912995
                    */
                    else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
                    {
                        // Content-Disposition: form-data; name="key"
                        //
                        // value

                        // Do not limit the key name length here because the 
                        // multipart headers length limit is already in effect.
                        var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                        var encoding = GetEncoding(section);
                        using (var streamReader = new StreamReader(
                            section.Body,
                            encoding,
                            detectEncodingFromByteOrderMarks: true,
                            bufferSize: 1024,
                            leaveOpen: true))
                        {
                            // The value length limit is enforced by MultipartBodyLengthLimit
                            var value = await streamReader.ReadToEndAsync();
                            if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                            {
                                value = String.Empty;
                            }
                            formAccumulator.Append(key.Value, value); // For .NET Core <2.0 remove ".Value" from key

                            if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                            {
                                throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
                            }
                        }
                    }
                }

                // Drains any remaining section body that has not been consumed and
                // reads the headers for the next section.
                section = await reader.ReadNextSectionAsync();//用于读取Http请求中的下一个section数据
            }

            // Bind form data to a model
            var fileReturnType = new FileReturnType();
            fileReturnType.formValueProvider = new FormValueProvider(
                BindingSource.Form,
                new FormCollection(formAccumulator.GetResults()),
                CultureInfo.CurrentCulture);
			fileReturnType.fileEntity = fileEntity;
            return fileReturnType;
        }

        private static Encoding GetEncoding(MultipartSection section)
        {
            MediaTypeHeaderValue mediaType;
            var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
            // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
            // most cases.
            if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
            {
                return Encoding.UTF8;
            }
            return mediaType.Encoding;
        }
    }
}

现在我们还需要创建一个ASP.NET Core MVC的自定义拦截器DisableFormValueModelBindingAttribute,该拦截器实现接口IResourceFilter,用来禁用ASP.NET Core MVC的模型绑定器,这样当一个Http请求到达服务器后,ASP.NET Core MVC就不会在将请求的所有上传文件数据都加载到服务器内存后,才执行Controller的Action方法,而是当Http请求到达服务器时,就立刻执行Controller的Action方法。

ASP.NET Core 3.X使用下面的代码:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System;
using System.Linq;

namespace AspNetCore.MultipartRequest
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var factories = context.ValueProviderFactories;
            factories.RemoveType<FormValueProviderFactory>();
            factories.RemoveType<FormFileValueProviderFactory>();
            factories.RemoveType<JQueryFormValueProviderFactory>();
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }
}

最后我们在Controller中定义一个叫Index的Action方法,并注册我们定义的DisableFormValueModelBindingAttribute拦截器,来禁用Action的模型绑定。Index方法会调用我们前面定义的FileStreamingHelper类中的StreamFiles方法,其参数为用来存储上传文件的文件夹路径。StreamFiles方法会返回一个FormValueProvider,用来存储Http请求中的表单键值数据,之后我们会将其绑定到MVC的视图模型viewModel上,然后将viewModel传回给客户端浏览器,来告述客户端浏览器文件上传成功。

[HttpPost]
[DisableFormValueModelBinding]
[DisableRequestSizeLimit]
public async Task<IActionResult> Index()
{
    FormValueProvider formModel;
   // formModel = await Request.StreamFiles(@"F:\UploadingFiles");
      formModel = await Request.StreamFiles();
      
    var viewModel = new MyViewModel();

    var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
        valueProvider: formModel);

	

    if (!bindingSuccessful)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
    }
//bindingSuccessful 接受到上传值的属性,自己保存到数据库

    return Ok(viewModel);
}
public static class FileHelpers
{	
	private static string _filePath = _filePath ?? AppContext.BaseDirectory;//保存视频文件路径
	private static string _dbFilePath; //保存数据库中的文件夹路径

	///<summary>
	/// 文件返回类型
	///</summary>
	///<param name="section">文件流</param>
	///<param name="fileName">文件名</param>	
	///<param name="targetDirectory">存储位置</param>
	///<returns></returns>
	public static async Task<Tuple<byte[],string>> ProcessStreamedFile(MultipartSection section,string fileName,
		string targetDirectory = null)
	{
		try
		{
			if(string.IsNullOrEmpty(fileName))
			{
				throw new Exception("文件名不能为空");
			}
			
			using(var memoryStream = new MemoryStream())
			{
				//这个是每一次从Http请求的section中读出文件数据的大小,单位是Byte即字节,这里设置为1024的意思是,每次从Http请求的section数据流中读取出1024字节的数据到服务器内存中,然后写入下面targetFileStream的文件流中,可以根据服务器的内存大小调整这个值。这样就避免了一次加载所有上传文件的数据到服务器内存中,导致服务器崩溃。
				var loadBufferBytes = 1024;
				//section.Body是System.IO.Stream类型,表示的是Http请求中一个section的数据流,从该数据流中可以读出每一个section的全部数据,所以我们下面也可以不用section.Body.CopyToAsync方法,而是在一个循环中用section.Body.Read方法自己读出数据(如果section.Body.Read方法返回0,表示数据流已经到末尾,数据已经全部都读取完了),再将数据写入到targetFileStream
				await section.Body.CopyToAsync(memoryStream ,loadBufferBytes);
				var ext = Path.GetExtension(fileName).ToLower();
				if(memoryStream.Length == 0 )
				{
					throw new Exception("文件是空文件");
				}
				else if(memoryStream.Length > 2147483648 )
				{
					throw new Exception("文件大小超过2GB");
				}
				else if(memoryStream.Length > 2147483648 )
				{
					throw new Exception("文件大小超过2GB");
				}
				else if(ext.Contains(".mp4") || ext.Contains(".avi") || ext.Contains(".wmv")
				 || ext.Contains(".3gp") || ext.Contains(".dv"))
				{
					SaveFile(memoryStream, fileName, targetDirectory);
					Tuple<byte[],string> tuple = new Tuple<byte[],string>(memoryStream.ToArray(), _dbFilePath);
					return tuple;
				}
			}
		}
		 catch (Exception ex)
    	 {
        	  throw new Exception(ex.InnerException?.Message ?? ex.Message);
    	 }
		
	}

  private static void SaveFile(MultipartSection memoryStream, string fileName,
        string targetDirectory = null)
        {
            string currentDate = DateTime.Now.ToString("yyyy");
            string subfolder = "Video";

            //F:\Practice\DOTNET CORE\StudentManagement\StudentManagement\bin\Debug\net5.0\20210901\Video
            var folder = Path.Combine(currentDate, subfolder);
            var uploadPath = Path.Combine(_filePath,folder);

            //自定义存储路径
            if (!string.IsNullOrEmpty(targetDirectory))
            {
                if (!Directory.Exists(uploadPath))
                {
                    Directory.CreateDirectory(uploadPath);
                }
                uploadPath = targetDirectory;
            }
            //规定路径
            else if (!string.IsNullOrEmpty(uploadPath))
            {
                Directory.CreateDirectory(uploadPath);
            }

            var ext = Path.GetExtension(fileName).ToLower();
            var newName = GenerateId.GenerateOrderNumber() + ext;
            using (var fs = new FileStream(Path.Combine(uploadPath, newName), FileMode.Create))
            {
                fs.Write(memoryStream.ToArray(), 0, memoryStream.ToArray().Length);
                fs.Close();
                _dbFilePath = Path.Combine(folder,newName);
            }

        }

//GenerateId类
		 /// <summary>
        ///  获取文件大小显示为字符串
        /// </summary>
        /// <param name="lengthOfDocument">文件大小 单位: Byte 类型:long</param>
        /// <returns></returns>
        public static string GetLength(long lengthOfDocument)
        {
            //如果文件大小0-1024B以内   显示以B为单位
            //如果文件大小1KB-1024KB以内   显示以KB为单位
            //如果文件大小1M-1024M以内   显示以M为单位
            //如果文件大小1024 MB以内   显示以GB为单位
            if (lengthOfDocument <1024)
                return string.Format(lengthOfDocument.ToString() + 'B');
            else if(lengthOfDocument >1024 && lengthOfDocument <= Math.Pow(1024, 2))
                return string.Format((lengthOfDocument / 1024.0).ToString("F2") + "KB");
            else if (lengthOfDocument > Math.Pow(1024, 2) && lengthOfDocument <= Math.Pow(1024, 3))
                return string.Format((lengthOfDocument / 1024.0 / 1024.0).ToString("F2") + "M");
            else 
                return string.Format((lengthOfDocument / 1024.0 / 1024.0 / 1024.0).ToString("F2") + "GB");

        }

	///<summary>
	/// 文件返回类型
	///</summary>
	public class FileReturnType
	{
		public FileReturnType()
		{
			fileEntity = new List<>(FileEntity);
		}
		public FormValueProvider formValueProvider  {get;set;}
		public List<FileEntity> fileEntity {get;set;}
	}

}	

二、类

public class FileEntity
{
	public string Name {get;set;}
	//附件类型
	public string AttachmentType{get;set;}
	public string SavePath {get;set;}
	//文件格式
	public string FileFormat{get;set;}
	public string FileSize{get;set;}
}

总结

为自己提供方便,有错误请指出(写的有点乱) 谢谢。

参考文献:
https://www.cnblogs.com/OpenCoder/p/9785031.html 文件流上传
https://hub.fastgit.org/dotnet/AspNetCore.Docs/blob/main/aspnetcore/mvc/models/file-uploads/samples/3.x/SampleApp/Controllers/StreamingController.cs 官方上传文件
https://blog.csdn.net/qq_22098679/article/details/81327074 文件创建权限问题

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要在ASP.NET Core上载文件,您可以按照以下步骤操作: 1. 在您的控制器方法中使用`[HttpPost]`属性标记该方法。 2. 添加`[Request.Form.Files]`属性来接收提交的文件。 3. 使用`IFormFile`对象处理文件。您可以使用`CopyToAsync()`方法将文件复制到服务器文件系统中。 以下是示例代码: ```csharp [HttpPost] public async Task<IActionResult> UploadFile([FromForm] List<IFormFile> files) { long size = files.Sum(f => f.Length); // 遍历上传文件 foreach (var formFile in files) { if (formFile.Length > 0) { var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot\\uploads", formFile.FileName); using (var stream = new FileStream(filePath, FileMode.Create)) { await formFile.CopyToAsync(stream); } } } return Ok(new { count = files.Count, size }); } ``` 在此示例中,我们从`[HttpPost]`属性开始,该属性表示此方法应该响应HTTP POST请求。我们还使用`[FromForm]`属性将数据传递给`files`参数。 接下来,我们使用`Sum()`方法计算上传文件的大小。然后,我们使用`foreach`循环遍历上传文件。如果文件大小大于零,则将文件保存到服务器文件系统中。 最后,我们使用`Ok()`方法返回HTTP 200 OK响应,并将上传文件的数量和大小作为JSON对象返回。 ### 回答2: ASP.NET Core 文件上传是指在开发ASP.NET Core应用程序中,实现将文件从客户端上传到服务器的功能。ASP.NET Core 提供了丰富的工具和API来处理文件上传。 要实现文件上传功能,首先需要在前端添加一个文件选择的input元素,并将其设置为文件上传的类型。然后,当用户选择文件后,可以使用JavaScript将文件发送到后端。 在后端,可以使用ASP.NET Core的控制器来处理文件上传。可以使用HttpPost特性将方法定义为POST请求,并使用FromForm特性获取从前端发送的文件。 下一步是处理接收到的文件。可以使用IFormFile接口来操作上传文件。可以通过Name属性获取文件的名称,并使用OpenReadStream方法获取文件的二进制。 在处理文件之前,可以对文件进行一些验证和验证。例如,可以检查文件的大小、文件类型等。如果有验证错误,可以将错误信息返回给前端。 接下来,可以将接收到的文件保存到服务器的指定位置。可以使用File类的CopyToAsync方法将文件复制到指定的路径。在保存文件之前,可以为文件生成一个唯一的文件名,以避免文件名冲突。 完成文件上传后,可以向前端发送响应,确认文件上传成功。可以返回一个包含文件信息的JSON对象,或者返回一个简单的成功消息。 综上所述,ASP.NET Core 文件上传可以通过前端的文件选择和JavaScript来实现文件上传功能,并使用ASP.NET Core的控制器来接收和处理上传文件。可以对上传文件进行验证和处理,并将其保存到指定的位置。完成文件上传后,可以向前端发送响应以确认上传成功。 ### 回答3: ASP.NET Core 是一个开源、跨平台的框架,可以用来构建 Web 应用程序和服务。文件上传是 Web 开发中常见的功能,ASP.NET Core 提供了方便的方式来处理文件上传。 要在 ASP.NET Core 中实现文件上传,可以使用 Mvc 中的 Controller 和 View。下面是一个简单的示例: 1. 在 Controller 中,可以使用 [HttpPost] 特性来标记一个接受 Post 请求的方法。 2. 在这个方法中,需要使用 [FromForm] 特性来接受从前端传来的文件。例如,可以在方法的参数中定义一个 IFormFile 类型的参数来接收上传文件。 3. 为了接受文件上传的表单,需要在 View 文件中使用 form 标签,并设置 enctype="multipart/form-data" 属性来指示表单是用于文件上传。 4. 在表单中,可以使用 input 标签的 type="file" 来创建一个文件选择框。用户选择文件后,就可以点击提交按钮将文件上传到服务器。 5. 在 Controller 中的方法中,可以处理接收到的文件。例如,可以使用 IFormFile 类型的参数的 OpenReadStream() 方法来获取文件,然后将文件保存到服务器。 需要注意的是,文件上传涉及到服务器端的文件处理和安全问题。在实际应用中,我们需要对文件的类型、大小、保存路径等进行限制,以及对上传文件进行合法性校验和防止文件上传漏洞攻击的处理。 总结来说,ASP.NET Core 提供了方便的方式来实现文件上传功能。通过使用 Controller 和 View,可以很容易地将文件从前端上传到服务器,并对文件进行处理和保存。同时,为了确保安全和合法性,我们需要对文件进行校验和防御措施。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值