前言

前两天我采用技巧式方案基本实现大文件分片上传,这里只是重点在于个人思路和亲身实践,若在实际生产环境要求比较高的话肯定不行,仍存在一些问题需要深入处理,本文继续在之前基础上给出基于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. Burp Suite Scanner Module - 扫描模块

    Burp Suite Professional 和Enterprise Version的Scaner功能较丰富. 以Professional版本为例,包含Issue activity, Scan qu ...

  2. Python Ethical Hacking - VULNERABILITY SCANNER(2)

    VULNERABILITY_SCANNER How to discover a vulnerability in a web application? 1. Go into every possibl ...

  3. Linux 后台启动 Redis

    1. 修改 redis.conf 首先,这里有一个坑 ! 不同的 redis版本,在安装的时候,redis.conf 的路径稍微有些不同 redis.conf 可能出现的三个位置: /etc/redi ...

  4. 详解TCP一:三次握手、四次挥手

    TCP协议同样是运输层的协议,掌握TCP重点要关注这几个问题:顺序问题.丢包问题.连接维护.流量控制.拥塞控制.先解析下TCP报文段结构,相比于UDP要复杂很多. 首先还是两个端口号,对应着具体的应用 ...

  5. 高效C++:设计与声明

    C++软件开发可以理解为设计一系列的类,让这些类相互使用,最终实现我们所需要的功能.类与类之间的相互关系可以很复杂,也可以很简单,如何简单高效的描述类与类之间的关系是设计的难点之一.遵循本文所提供的方 ...

  6. 关于maven的一份小笔记

    简介 项目里一直用的 maven,几乎天天和这个"熟知"的工具打交道,但是,最近我发觉自己对 maven 了解的还不够,例如,什么是 goal?什么是 phase?等等.趁着最近有 ...

  7. 全栈的自我修养: 0005 Java 包扫描实现和应用(Jar篇)

    全栈的自我修养: 0005 Java 包扫描实现和应用(Jar篇) It's not the altitude, it's the attitude. 决定一切的不是高度而是态度. Table of ...

  8. MacOS下VUEJS简单入门

    微信搜索"艺术行者",关注并回复关键词"vue"获取课程资料 上传的在线学习视频(黑马和传智双元,感谢) VueJs概述与快速入门 学习之前假设你已了解关于 H ...

  9. metaclass 拦截类的创建,并返回

    def upper_attr(future_class_name, future_class_parents, future_class_attr): #遍历属性字典,把不是__开头的属性名字变为大写 ...

  10. 面试官你好,我已经掌握了MySQL主从配置和读写分离,你看我还有机会吗?

    我是风筝,公众号「古时的风筝」,一个简单的程序员鼓励师. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 面试官:我看你简历上写的你们公司数据库是 ...