# Downloader
:rocket: Fast, cross-platform, and reliable multipart downloader in `.Net` :rocket:
> [!TIP]
> **Prefer a ready-to-use app over a library?** Meet **[Downloader Desktop](https://github.com/bezzad/Downloader.Desktop)** — a free, open-source GUI download manager for **Windows · Linux · macOS**, powered by this engine: multi-connection downloads, pause/resume, queues, scheduler, speed limits and more — with no dependencies to install.
**Downloader** is a modern, fluent, asynchronous, and portable library for .NET, built with testability in mind. It supports multipart downloads with real-time asynchronous progress events. The current `v5.x` line targets `.NET 8`, `.NET 9`, and `.NET 10`. (If you need `.NET Standard 2.1` or older runtimes such as `.NET Framework 4.6.1`, use the `v3.x` line — see the note below.)
Downloader works on Windows, Linux, and macOS.
> **Note**: Support for older versions of .NET was removed in Downloader `v3.2.0`. From this version onwards, only `.Net 8.0` and higher versions are supported.
> If you need compatibility with older .NET versions (e.g., `.NET Framework 4.6.1`), use Downloader `v3.1.*`.
> For a complete example, see the [Downloader.Sample](https://github.com/bezzad/Downloader/blob/master/src/Samples/Downloader.Sample/Program.cs) project in this repository.
## Table of Contents
- [Quick Start](#quick-start)
- [Sample Console Application](#sample-console-application)
- [Key Features](#key-features)
- [Installation via NuGet](#installation-via-nuget)
- [Installation via the .NET CLI](#installation-via-the-net-cli)
- [Usage](#usage)
- [How to get the file name and size without downloading](#how-to-get-the-file-name-and-size-without-downloading)
- [How to pause and resume downloads quickly](#how-to-pause-and-resume-downloads-quickly)
- [How to stop and resume downloads (manual approach)](#how-to-stop-and-resume-downloads-manual-approach)
- [How to automatically resume downloads (recommended)](#how-to-automatically-resume-downloads-recommended)
- [Fluent download builder usage](#fluent-download-builder-usage)
- [Using a Custom HttpClient or HttpMessageHandler](#using-a-custom-httpclient-or-httpmessagehandler)
- [When does the Downloader fail to download in multiple chunks?](#when-does-the-downloader-fail-to-download-in-multiple-chunks)
- [Redirects, cookies, and protected links](#redirects-cookies-and-protected-links)
- [How to serialize and deserialize the downloader package](#how-to-serialize-and-deserialize-the-downloader-package)
- [Building a Native AOT Version](#-building-a-native-aot-version)
- [Instructions for Contributing](#instructions-for-contributing)
- [Support the Project](#support-the-project)
- [License](#license)
- [Contributors](#contributors)
## Quick Start
```bash
dotnet add package Downloader
```
```csharp
await DownloadBuilder
.New()
.WithUrl(@"https://host.com/test-file.zip")
.WithDirectory(@"C:\temp")
.Build()
.StartAsync();
```
## Sample Console Application
---
## Key Features
- Simple interface for download requests.
- Asynchronous, non-blocking file downloads.
- Supports all file types (e.g., images, videos, PDFs, APKs).
- Cross-platform support for files of any size.
- Real-time progress updates for each download chunk.
- Downloads files in multiple parts (parallel download).
- Resilient to client-side and server-side errors.
- Configurable `ChunkCount` to control download segmentation.
- Supports both in-memory and on-disk multipart downloads.
- Parallel saving of chunks directly into the final file (no temporary files).
- Always downloads to a temporary file (configurable extension, default `.download`), then renames to the final name on completion.
- Always pre-allocates file size before download begins.
- Resume downloads manually by saving and restoring the `DownloadPackage` object.
- Automatic resume: when enabled, download metadata is embedded inside the `.download` file — no extra files or manual serialization needed.
- Provides real-time speed and progress data.
- Asynchronous pause and resume functionality.
- Download files with dynamic speed limits.
- Supports downloading to memory streams (without saving to disk).
- Supports large file downloads and live-streaming (e.g., music playback during download).
- Download a specific byte range from a large file.
- Resolve a remote file's name, size, and range support **without downloading it** (`RemoteFileResolver`).
- Lightweight, fast codebase with no external dependencies.
- Manage RAM usage during downloads.
- Supports custom `HttpClient` or `HttpMessageHandler` injection for advanced scenarios (e.g., `IHttpClientFactory`, HTTP caching, custom delegating handlers).
---
## Installation via [NuGet](https://www.nuget.org/packages/downloader)
```text
PM> Install-Package Downloader
```
## Installation via the .NET CLI
```bash
dotnet add package Downloader
```
---
## Usage
### **Step 1**: Create a Custom Configuration
#### Simple Configuration
```csharp
var downloadOpt = new DownloadConfiguration()
{
// Number of file parts, default is 1
ChunkCount = 8,
// Download parts in parallel (default is false)
ParallelDownload = true
};
```
#### Complex Configuration
> **Note**: Only include the options you need in your application.
```
…
```
### Recommended setup for fast **and** reliable downloads
A good general-purpose starting point — parallel for speed, with retries and auto-resume for safety:
```
…
```
> **Tip:** Higher `ChunkCount`/`ParallelCount` is not always faster — many servers rate-limit or
> cap connections per client. Values around `8` chunks and `4` parallel connections are a safe,
> fast default; tune for your target hosts.
### **Step 2**: Create the Download Service
```csharp
var downloader = new DownloadService(downloadOpt);
```
### **Step 3**: Handle Download Events
```
…
```
Example handler implementations:
```
…
```
> ### ⚠️ Important: how failures and cancellations are reported
>
> `DownloadFileTaskAsync(...)` / `StartAsync(...)` **do not throw** when a download fails or is
> cancelled. The awaited call completes normally, and the outcome is delivered through the
> **`DownloadFileCompleted`** event:
>
> - **Success** → `e.Cancelled == false` **and** `e.Error == null`
> - **Failure** → `e.Error` contains the exception (e.g. a network error, or an
> `IncompleteDownloadException` if the server ended the stream early)
> - **Stopped/paused by you** → `e.Cancelled == true`
>
> Always subscribe to `DownloadFileCompleted` and check `e.Error` before treating the file as
> complete — relying only on the `await` returning will silently miss failed downloads. You can
> also inspect `downloader.Package.Status` (`Completed` / `Stopped` / `Failed`).
### **Step 4**: Start the Download
```csharp
string file = @"Your_Path\fileName.zip";
string url = @"https://file-examples.com/fileName.zip";
await downloader.DownloadFileTaskAsync(url, file);
```
### **Step 4b**: Start the download without a file name
```csharp
DirectoryInfo path = new DirectoryInfo("Your_Path");
string url = @"https://file-examples.com/fileName.zip";
// download into "Your_Path\fileName.zip"
await downloader.DownloadFileTaskAsync(url, path);
```
### **Step 4c**: Download in MemoryStream
```csharp
// After the download completes, you get a MemoryStream
Stream destinationStream = await downloader.DownloadFileTaskAsync(url);
```
---
### How to get the **file name and size without downloading**
Sometimes you need a remote file's name and size **before** (or without ever) downloading it — for
example to populate a list/grid of queued downloads with their names and sizes while they wait for a
slot, instead of starting and immediately stopping a real download just to read the headers.
Use `RemoteFileResolver`. It performs a single lightweight header probe (a `Range: 0-0` GET that
follows redirects) and resolves the file name exactly the way the downloader does internally — from
the `Content-Disposition` header, falling back to the URL path, and finally to a generated GUID —
plus the size (`Content-Range` → `Content-Length`) and whether the server supports ranged
(resumable) downloads.
```csharp
// Just the file name
// (resilient: falls back to the URL-derived name
// on a network/server error)
string fileName = await RemoteFileResolver.GetFileNameAsync(url);
// Full metadata in one probe:
RemoteFileInfo info = await RemoteFileResolver.GetFileInfoAsync(url);
Console.WriteLine($"{info.FileName} — {info.FileSize} bytes, range: {info.SupportsRange}");
// info.Address is the final URL after any redirects.
```
Both methods accept an optional `DownloadConfiguration` (to reuse your headers, proxy, credentials,
cookies, redirect policy, …) and a `CancellationToken`. Each call owns and disposes its own client,
so when resolving many URLs at once add your own concurrency limiting / timeouts.
If you already hold a download service, the same lookup is available on it (using that service's
configuration, without disturbing any download in progress):
```csharp
RemoteFileInfo info = await downloader.GetFileInfoAsync(url);
```
> **Note:** `FileSize` is `-1` when the server does not advertise a length. `GetFileNameAsync` and
> the service/`GetFileInfoAsync` previews are best-effort and won't throw on a server that hides its
> size — you still get a usable name.
---
### How to **pause** and **resume** downloads quickly
When you want to resume a download quickly after pausing for a few seconds, call the `Pause` function of the downloader service. This way, the streams stay alive and are only suspended by a lock, to be released and resumed whenever you want.
```csharp
// Pause the download
downloader.Pause();
// Resume the download
downloader.Resume();
```
---
### How to **stop** and **resume** downloads (manual approach)
The `DownloadService` class has a property called `Package` that holds a live snapshot of the download state (chunk positions, URL, file path, etc.). While the download is in progress, this object is updated continuously.
To stop and later resume a download **manually**, you are responsible for keeping the `Package` object yourself — either in memory or serialized to disk. The Downloader does not store it for you in this approach.
```csharp
// 1. Keep a reference to the package before or after stopping:
DownloadPackage pack = downloader.Package;
```
**Stop or cancel the download:**
```csharp
// Fire-and-forget cancellation (returns void):
downloader.CancelAsync();
// Or await until the download has actually stopped before reading the package:
await downloader.CancelTaskAsync();
```
**Resume later — even after restarting the application:**
```csharp
// Pass the same (or deserialized) package to resume from the last position:
await downloader.DownloadFileTaskAsync(pack);
```
The `Package` object is lightweight — it contains only the URL, file path, and the position of each chunk (not the downloaded bytes). You can serialize it to JSON or binary (see [Serialization section](#how-to-serialize-and-deserialize-the-downloader-package)) and restore it at any time.
For more details see the [StopResumeDownloadTest](https://github.com/bezzad/Downloader/blob/master/src/Downloader.Test/IntegrationTests/DownloadIntegrationTest.cs#L210) method.
> **Note:** If the server does not support HTTP range requests, the download cannot be resumed and will restart from the beginning.
---
### How to **automatically resume** downloads (recommended)
If you don'