前言

前两天我采用技巧式方案基本实现大文件分片上传,这里只是重点在于个人思路和亲身实践,若在实际生产环境要求比较高的话肯定不行,仍存在一些问题需要深入处理,本文继续在之前基础上给出基于tus协议的轮子方案,本打算再次尝试利用.NET Core实现此协议,但在github上一搜索早在2016年就已有此协议对应的.NET和.NET Core方案,并且一直更新到最近的.NET Core 3.x版本,完全满足各位所需,本文是我写出的一点demo。

基于tus协议实现断点续传演示

基于tus协议tusdotnet方案基本demo

关于此协议实现原理这里不做阐述,请参照上述github地址自行了解,本文只是给出.NET Core方案下的基本demo,我们上传一个大文件然后通过进度显示上传进度以及对上传可暂停可继续,专业点讲就是断点续传,首先肯定是引入tus脚本和需要用到的bootstrap样式,我们将进度条默认隐藏,当上传时才显示,所以我们给出如下HTML。

<div class="form-horizontal" style="margin-top:80px;">
<div class="form-group" id="progress-group" style="display:none;">
<div id="size"></div>
<div class="progress">
<div id="progress" class="progress-bar progress-bar-success progress-bar-animated progress-bar-striped" role="progressbar"
aria-valuemin="0" aria-valuemax="100">
<span id="percentage"></span>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input name="file" id="file" type="file" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" id="submit" value="上传" class="btn btn-success" />
<input type="button" id="pause" value="暂停" class="btn btn-danger" />
<input type="button" id="continue" value="继续" class="btn btn-info" />
</div>
</div>
</div>

接下来就是使用引入的tus脚本,也没什么太多要讲解的,直接上代码,这里稍微注意的是在如下元数据(metadata)属性对象定义给出实际文件名,便于在后台最终将上传的文件转换为目标文件,至少得知道文件扩展名,对吧。

<script type="text/javascript">
$(function () {
var upload; //上传
$('#submit').click(function () { $('#progress-group').show(); var file = $('#file')[0].files[0]; // 创建tus上传对象
upload = new tus.Upload(file, {
// 文件服务器上传终结点地址设置
endpoint: "files/",
// 重试延迟设置
retryDelays: [0, 3000, 5000, 10000, 20000],
// 附件服务器所需的元数据
metadata: {
name: file.name,
contentType: file.type || 'application/octet-stream',
emptyMetaKey: ''
},
// 回调无法通过重试解决的错误
onError: function (error) {
console.log("Failed because: " + error)
},
// 上传进度回调
onProgress: onProgress,
// 上传完成后回调
onSuccess: function () {
console.log("Download %s from %s", upload.file.name, upload.url)
}
}) upload.start()
}); //暂停
$('#pause').click(function () {
upload.abort()
}); //继续
$('#continue').click(function () {
upload.start()
}); //上传进度展示
function onProgress(bytesUploaded, bytesTotal) {
var percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
$('#progress').attr('aria-valuenow', percentage);
$('#progress').css('width', percentage + '%'); $('#percentage').html(percentage + '%'); var uploadBytes = byteToSize(bytesUploaded);
var totalBytes = byteToSize(bytesTotal); $('#size').html(uploadBytes + '/' + totalBytes);
} //将字节转换为Byte、KB、MB等
function byteToSize(bytes, separator = '', postFix = '') {
if (bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString(), 10), sizes.length - 1);
return `${(bytes / (1024 ** i)).toFixed(i ? 1 : 0)}${separator}${sizes[i]}${postFix}`;
}
return 'n/a';
}
}); </script>

接下来进入后台,首先安装对应tus协议实现包,如下:

接下来则是添加tus中间件,说白了就是对tus的配置,各种配置都可满足你所需,这里我只实现了文件上传完成后将上传文件转换为目标文件的处理,紧接着将如下实现tus配置以单例形式注入即可

private DefaultTusConfiguration CreateTusConfiguration(IServiceProvider serviceProvider)
{
var env = (IWebHostEnvironment)serviceProvider.GetRequiredService(typeof(IWebHostEnvironment)); //文件上传路径
var tusFiles = Path.Combine(env.WebRootPath, "tusfiles"); return new DefaultTusConfiguration
{
UrlPath = "/files",
//文件存储路径
Store = new TusDiskStore(tusFiles),
//元数据是否允许空值
MetadataParsingStrategy = MetadataParsingStrategy.AllowEmptyValues,
//文件过期后不再更新
Expiration = new AbsoluteExpiration(TimeSpan.FromMinutes()),
//事件处理(各种事件,满足你所需)
Events = new Events
{
//上传完成事件回调
OnFileCompleteAsync = async ctx =>
{
//获取上传文件
var file = await ctx.GetFileAsync(); //获取上传文件元数据
var metadatas = await file.GetMetadataAsync(ctx.CancellationToken); //获取上述文件元数据中的目标文件名称
var fileNameMetadata = metadatas["name"]; //目标文件名以base64编码,所以这里需要解码
var fileName = fileNameMetadata.GetString(Encoding.UTF8); var extensionName = Path.GetExtension(fileName); //将上传文件转换为实际目标文件
File.Move(Path.Combine(tusFiles, ctx.FileId), Path.Combine(tusFiles, $"{ctx.FileId}{extensionName}"));
}
}
};
}

然后获取并使用上述添加的tus配置服务

app.UseTus(httpContext => Task.FromResult(httpContext.RequestServices.GetService<DefaultTusConfiguration>()));

在脚本中我们看到有个endpoint属性,此属性表示上传到服务器的上传结点地址,因为在上到服务器时我们可能需对此请求进行额外处理,比如元数据中的文件名是否已提供等等,所以我们在使用结点映射时,添加对上述结点名称的映射,如下:

endpoints.MapGet("/files/{fileId}", DownloadFileEndpoint.HandleRoute);

该映射第二个参数为RequestDelegate,这个参数用过.NET Core的童鞋都知道,这里我是直接拷贝该包的路由实现,如下:

public static class DownloadFileEndpoint
{
public static async Task HandleRoute(HttpContext context)
{
var config = context.RequestServices.GetRequiredService<DefaultTusConfiguration>(); if (!(config.Store is ITusReadableStore store))
{
return;
} var fileId = (string)context.Request.RouteValues["fileId"];
var file = await store.GetFileAsync(fileId, context.RequestAborted); if (file == null)
{
context.Response.StatusCode = ;
await context.Response.WriteAsync($"File with id {fileId} was not found.", context.RequestAborted);
return;
} var fileStream = await file.GetContentAsync(context.RequestAborted);
var metadata = await file.GetMetadataAsync(context.RequestAborted); context.Response.ContentType = GetContentTypeOrDefault(metadata);
context.Response.ContentLength = fileStream.Length; if (metadata.TryGetValue("name", out var nameMeta))
{
context.Response.Headers.Add("Content-Disposition",
new[] { $"attachment; filename=\"{nameMeta.GetString(Encoding.UTF8)}\"" });
} using (fileStream)
{
await fileStream.CopyToAsync(context.Response.Body, , context.RequestAborted);
}
} private static string GetContentTypeOrDefault(Dictionary<string, Metadata> metadata)
{
if (metadata.TryGetValue("contentType", out var contentType))
{
return contentType.GetString(Encoding.UTF8);
} return "application/octet-stream";
}
}

文件上传大小限制说明

我们知道无论是.NET还是.NET Core对于文件上传大小都有默认限制大小,这里对.NET Core中文件大小各种环境配置做一个统一说明,如果你将.NET Core寄宿在IIS上运行,那么请修改web.config配置文件大小限制

<system.webServer>
<security>
<requestFiltering>
//若不配置,默认是28.6兆
<requestLimits maxAllowedContentLength="" />
</requestFiltering>
</security>
</system.webServer>

如果在开发环境默认使用IIS运行应用程序,请通过如下根据实际情况配置文件上传大小

services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = int.MaxValue;
});

如果程序运行在Kestrel服务器,那么请通过如下根据实际情况配置文件上传大小

services.Configure<KestrelServerOptions>(options =>
{
//若不配置,默认是30兆(没记错的话)
options.Limits.MaxRequestBodySize = int.MaxValue;
});

如果是通过表单上传文件,那么请通过如下根据实际情况配置文件上传大小

services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
//如果不配置,默认是128兆(没记错的话)
x.MultipartBodyLengthLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
}); 

总结

为了更好体验可以再加上当前网络宽带情况或剩余多少分钟,更详细内容请参考:https://github.com/tusdotnet/tusdotnethttps://github.com/tus/tus-js-client,关于大文件上传处理到此结束,希望对那些苦苦寻找最终解决方案而无助的童鞋们提供最佳轮子,谢谢。

.NET或.NET Core Web APi基于tus协议实现断点续传的更多相关文章

  1. ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...

  2. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线

    重温.NET下Assembly的加载过程   最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...

  3. ASP.NET Core Web API + Angular 仿B站(三)后台配置 JWT 的基于 token 的验证

    前言: 本系列文章主要为对所学 Angular 框架的一次微小的实践,对 b站页面作简单的模仿. 本系列文章主要参考资料: 微软文档: https://docs.microsoft.com/zh-cn ...

  4. 在ASP.NET Core Web API上使用Swagger提供API文档

    我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的API文档功能.当设置IISExpress的默认启动路由到Swagger的API文档页 ...

  5. 在docker中运行ASP.NET Core Web API应用程序

    本文是一篇指导快速演练的文章,将介绍在docker中运行一个ASP.NET Core Web API应用程序的基本步骤,在介绍的过程中,也会对docker的使用进行一些简单的描述.对于.NET Cor ...

  6. ASP.NET Core Web API Cassandra CRUD 操作

    在本文中,我们将创建一个简单的 Web API 来实现对一个 “todo” 列表的 CRUD 操作,使用 Apache Cassandra 来存储数据,在这里不会创建 UI ,Web API 的测试将 ...

  7. 在Mac下创建ASP.NET Core Web API

    在Mac下创建ASP.NET Core Web API 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: 官方文档涉 ...

  8. ASP.NET Core Web API 开发-RESTful API实现

    ASP.NET Core Web API 开发-RESTful API实现 REST 介绍: 符合REST设计风格的Web API称为RESTful API. 具象状态传输(英文:Representa ...

  9. Core Web API上使用Swagger提供API文档

    在ASP.NET Core Web API上使用Swagger提供API文档   我在开发自己的博客系统(http://daxnet.me)时,给自己的RESTful服务增加了基于Swagger的AP ...

随机推荐

  1. [CISCN2019 华东南赛区]Double Secret

    0x01 进入页面如下 提示我们寻找secret,再加上题目的提示,猜测这里有secret页面,我们尝试访问,结果如下 根据它这个话的意思,是让我们传参,然后它会给你加密,我们试一下 发现输入的1变成 ...

  2. git push到远程新分支

    获取远程代码并在本地切换到一个新分支修改后,想要 push 到远端与原来不同的新分支,可以使用下面的命令实现: git push origin 本地分支:远端希望创建的分支 上面的本地分支 是基于拉取 ...

  3. EF批量插入太慢?那是你的姿势不对

    大概所有的程序员应该都接触过批量插入的场景,我也相信任何的程序员都能写出可正常运行的批量插入的代码.但怎样实现一个高效.快速插入的批量插入功能呢? 由于每个人的工作履历,工作年限的不同,在实现这样的一 ...

  4. HTML学习汇总

    HTML学习大汇总 (1)HTML概述 Html(超文本标记语言): 用文字来描述的标签语言,用文字来描述网页的一种语言. HTML是 HyperText Mark-up Language 的首字母简 ...

  5. ken桑带你读源码 之scrapy scrapy\core\scheduler.py

    从英文来看是调度程序  我们看看是怎么调度 首先爬虫队列有两个 一个是保存在内存中  没有历史记录   重新开始  42行  self.mqs = self.pqclass(self._newmq) ...

  6. CORS跨域操作cookie

    CORS 跨域 在服务端设置响应头 ACAO( Access-Control-Allow-Origin )即可 前端代码,运行在 8080 端口上 $.ajax({ url:'http://local ...

  7. springboot + ehcache

    一.使用 springboot + ehcache本地堆缓存实现相应功能 1.引入ehcache的jar包 2.创建ehcache的xml配置文件 <?xml version="1.0 ...

  8. Python os.read() 方法

    概述 os.read() 方法用于从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd对应文件已达到结尾, 返回一个空字符串.高佣联盟 www.cgewang.com ...

  9. JavaScript Object的复制

    var obj = { a: 1, b: 2, c: { d: 3, e: 4, f: function () { console.log("对象复制"); } } } 1. fo ...

  10. C/C++编程笔记:C语言预处理命令是什么?不要以为你直接写#就行!

    很多小伙伴在自己写代码的时候,已经多次使用过#include命令.使用库函数之前,应该用#include引入对应的头文件.其实这种以#号开头的命令称为预处理命令. C语言源文件要经过编译.链接才能生成 ...