SolidTUS 0.0.10

There is a newer version of this package available.
See the version list below for details.
dotnet add package SolidTUS --version 0.0.10
NuGet\Install-Package SolidTUS -Version 0.0.10
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SolidTUS" Version="0.0.10" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SolidTUS --version 0.0.10
#r "nuget: SolidTUS, 0.0.10"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install SolidTUS as a Cake Addin
#addin nuget:?package=SolidTUS&version=0.0.10

// Install SolidTUS as a Cake Tool
#tool nuget:?package=SolidTUS&version=0.0.10

What is it?

A C# dotnet 6 implementation of the TUS-protocol for resumable file uploads for an ASP.NET Core application.

Why create another TUS library?
This library's purpose is to be simple and give the consumer more options on how to authorize and authenticate requests.
Which I felt that other libraries did not provide.
SolidTUS is a more Controller/Action oriented and integrates well with a Web API or an MVC project.

If you have any suggestions or improvements please do not hesitate to contribute.

Other TUS libraries for C#

Install

Run dotnet add package solidTUS

Configuration

The required configuration is to register the services in the startup process:

// Register TUS services
builder.Services.AddTUS();

Usage

Then in the Controller you mark the 2 actions that will be the TUS file creation endpoint with a TusCreation-attribute and the upload action with a TusUpload-attribute:

Each Action will be injected with a context class: TusCreationContext and TusUploadContext.

[TusCreation]
public async Task<ActionResult> CreateUpload(TusCreationContext context)
{
  // ... Construct a file ID and an URL route to the Upload endpoint
  var fileID = "myFileID";
  var url = "/url/path/to/upload/action/myFileID";
  
  // When ready to create resource metadata
  await context.StartCreationAsync(fileID, url);
  
  return NoContent();
}

For the upload action:

[TusUpload]
[RequestSizeLimit(5_000_000_000)] // <-- example: Set upload size for the action to 5 Gib
[Route("url/path/to/upload/action/{fileId}")]
public async Task<ActionResult> Upload(string fileId, TusUploadContext context)
{
  // ... stuff
  await context.StartAppendDataAsync(fileId);
  
  // Important must return 204 on success (TUS-protocol)
  return NoContent();
}

TUS-features

  • Core-protocol v.1.0.0 (stop and resume uploads)
  • Creation (create upload)
  • Creation-with-upload (upload and create in a single step)
  • Checksum (validate partial uploads with a given algorithm and checksum)
  • Max-size (define a hard limit for upload size)
  • Tus-metadata validation
  • Options (server feature announcements)

Eventual future features (not implemented):

  • Expiration (deletion of unfinished uploads)
  • Concatenation (upload chunks and then combine the files)

Note that the checksum feature does not implement the trailing header feature, i.e. A checksum value must be provided upon sending the http request.

Extra options

Configurations

TUS configurations

// Custom metadata provider or set maximum TUS protocol file size
builder.Services
  .AddTUS()
  .Configuration(options =>
  {
    // A Func<string, bool> that validates the given TUS-metadata upon resource creation
    options.MetadataValidator = (metadata) => metadata.ContainsKey("filename");
    
    // This max size is different than the ASP.NET specified max size. To change the request size limit do it per Action with an attribute (recommended).
    options.MaxSize = 5_000_000_000;
  });

Note: to change request size limits see: Microsoft documentation

If you don't want to use the default FileUploadStorageHandler you can provide your own, maybe you want to save the files to a database?

// Add custom storage handler
builder.Services
  .AddTUS()
  .AddStorageHandler<MyStorageHandler>(); // <-- must implement IUploadStorageHandler interface

If you want to support more checksum validators. The default checksum validators are: SHA1 and MD5:

// Add custom checksum validator
builder.Services
  .AddTUS()
  .AddChecksumValidator<MyChecksumValidator>(); // <-- must implement IChecksumValidator interface

If you use the default FileUploadStorageHandler you can configure the directory where to store files:

// Add custom checksum validator
builder.Services
  .AddTUS()
  .FileStorageConfiguration(options =>
  {
    options.DirectoryPath = "path/to/where/save/upload/files";
  });

Configuration from appSettings.json or environment variables

You can configure the Tus-Max-Size parameter and the default file storage upload folder from the appSettings.json configuration:

{
  "SolidTUS": {
    "DirectoryPath": "/path/to/my/uploads",
    "MaxSize": "3000000"
  }
}

Environment variables are named as SolidTUS__DirectoryPath with a double underscore (so they also can be read from a linux environment). See Microsofts documentation for naming

Contexts

The injected context classes are excluded from ModelBinding but do show up in Swagger SwashBuckle. Excluding the contexts from the Swagger document, will be left as an exercise for the reader.

TusUploadContext

Is responsible for starting or terminating the upload. A termination is a premature ending and signals to the client that the upload has been terminated.
The class contains the following members:

  • UploadFileInfo - Upload file metadata (file size, offset, TUS-metadata and id)
  • OnUploadFinished - A method that takes an awaitable callback. When the whole file has been completely uploaded the callback is invoked.
  • StartAppendDataAsync - Starts accepting the upload stream from the client
  • TerminateUpload - Returns an error http response (default: 400 BadRequest)

MUST call either StartAppendDataAsync or TerminateUpload method. Cannot call both in a single request (you can't accept and not accept an upload).

The TusUploadContext is injected from the TusUpload-attribute.

TusUploadAttribute

Is responsible for marking the TUS-protocol upload endpoint. And needs information about 2 things:

  1. The file ID parameter name of type string
  2. The context parameter name of type TusUploadContext

These parameters can be tuned:

[TusUpload(FileIdParameterName = "Id", ContextParameterName = "tus")]
public async Task<ActionResult> UploadEndPoint(string Id, TusUploadContext tus)
{
  /* Logic omitted ... */
}

TusCreationContext

Is responsible for creating the resource metadata UploadFileInfo. Defining the file ID and eventual any TUS-metadata.
SolidTUS implements the TUS-protocol extension creation-with-upload thus a resource creation can contain some upload data.
Before reaching the actual Action method the metadata validator defined in the Configuration will run; If metadata is invalid an automatic response of 400 Bad Request will be returned, as specified in the TUS-protocol.
The class contains the following members:

  • StartCreationAsync - Starts resource creation
  • OnResourceCreated - A method that takes a callback function, which is invoked when the resource has been successfully created
  • OnUploadFinished - A method that takes a callback function, which is invoked when the partial file or whole file has finished uploading. It could be the client has sent a partial upload or the whole file.
  • Metadata - A Dictionary<string, string> property of the parsed TUS-metadata

The TusCreationContext is injected from the TusCreation-attribute.

TusCreationAttribute

Is responsible for marking the TUS-creation endpoint and needs information about the context parameter name.

[TusCreation(ContextParameterName = "creationContext")]
public async Task<ActionResult> CreationEndPoint(TusUploadContext creationContext)
{
  /* Logic omitted ... */
}

The TUS protocol with SolidTUS simplified

In essence the client sends a request to an endpoint (as marked by the TusCreation attribute:

POST /files HTTP/1.1
Host: tus.example.org
Content-Length: 0
Upload-Length: 100
Tus-Resumable: 1.0.0

The server then knows to expect an upload file with a total size of 100 bytes and at which point SolidTUS creates an UploadFileInfo which contains this metadata.
The server responds on success where the file can be uploaded:

HTTP/1.1 201 Created
Location: https://tus.example.org/files/24e533e02ec3bc40c387f1a0e460e216
Tus-Resumable: 1.0.0

In this example the file has an ID of 24e533e02ec3bc40c387f1a0e460e216.
The client then uploads the data to that location as marked by the TusUpload attribute:

PATCH /files/24e533e02ec3bc40c387f1a0e460e216 HTTP/1.1
Host: tus.example.org
Content-Type: application/offset+octet-stream
Content-Length: 30
Upload-Offset: 70
Tus-Resumable: 1.0.0

[remaining 30 bytes]

The upload starts from byte 70 and has a total size of 100, the missing 30 bytes are in the PATCH body. This data is directed to the OnPartialUploadAsync method in the IUploadStorageHandler.

On success the server responds:

HTTP/1.1 204 No Content
Tus-Resumable: 1.0.0
Upload-Offset: 100

Test methodology

Using unit tests and manually making TUS-request with the official javascript client in the examples folder.

Dependencies

This package uses the C# functional extensions package, which can be a very opinionated library.
In the future I would like to not depend on this library so the consumer wouldn't have to deal with function pollutions.

References

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.34 95 2/2/2024
0.0.33 83 2/1/2024
0.0.32 109 1/12/2024
0.0.31 112 1/4/2024
0.0.30 104 1/4/2024
0.0.29 147 1/3/2024
0.0.28 99 1/2/2024
0.0.27 96 12/21/2023
0.0.26 94 12/18/2023
0.0.25 82 12/11/2023
0.0.24 73 12/11/2023
0.0.23 65 12/11/2023
0.0.22 87 12/7/2023
0.0.21 99 12/5/2023
0.0.20 110 10/9/2023
0.0.19 111 9/25/2023
0.0.18 118 9/23/2023
0.0.17 142 4/27/2023
0.0.16 387 8/8/2022
0.0.15 365 8/6/2022
0.0.14 380 8/5/2022
0.0.13 379 7/21/2022
0.0.12 368 7/21/2022
0.0.11 368 7/21/2022
0.0.10 396 7/13/2022
0.0.9 409 7/13/2022
0.0.8 397 7/13/2022
0.0.7 398 7/12/2022