Downloading files
Resourcefull gives you a file's content as a stream to read as you go, or as bytes in memory, or just its description without the content. You can also download part of a file, which is how video players seek and download managers resume.
| Method | Returns | Use it for |
|---|---|---|
DownloadStreamingAsync |
a ResourceDownloadResult: the ResourceInfo and a Stream |
anything large, or anything you pass on (to a response, a file) |
DownloadContentAsync |
a ResourceContent: the ResourceInfo and the bytes as BinaryData |
small resources you need in memory |
GetResourceInfoAsync |
the ResourceInfo only |
checking a version's length, type, hash or metadata without reading it |
All three return null when the resource (or the version you asked for) doesn't exist, and read the current version
unless you pass a versionId.
The stream from DownloadStreamingAsync reads from storage as you read it, so memory use stays flat whatever the
size. It's read-only and forward-only: Length is the content's length and Position counts the bytes read, but it
can't seek. Dispose it when you're done, which releases what the provider keeps for it (with SQL Server, a database
context). From 4.0 4.0, disposing the download disposes its stream, so
await using ResourceDownloadResult? download = await container.DownloadStreamingAsync(...) does it too.
ResourceDownloadResult? download = await container.DownloadStreamingAsync("report.pdf");
if (download is not null)
{
await using Stream content = download.Stream;
await using FileStream file = File.Create("report.pdf");
await content.CopyToAsync(file, cancellationToken);
}
The stream and the ResourceInfo describe the same version, even if another upload makes a newer version current
while you read. With SQL Server, if the version itself is deleted while you're reading it, the stream throws
IOException rather than ending early. That includes an upload that replaces it: without versioning, each replacing
upload deletes the version before it, so a download of a file that's replaced often can fail partway and needs a retry. With
expiry turned on 4.0, a replaced version stays five minutes more, so a download that takes less
than that finishes.
Downloading part of a resource
Pass a ResourceRange to download only some of the bytes, such as to resume an interrupted download or to seek in a
video. It's an offset and a length, or an offset alone to read to the end:
ResourceDownloadResult? part = await container.DownloadStreamingAsync("talk.mp4", versionId: null,
new ResourceRange(Offset: 1_000_000, Length: 65_536), cancellationToken);
- The stream holds just the range: its
Lengthis the range's length.part.Rangesays which bytes it holds, andpart.TotalLengthis the whole content's length, measured from what's stored. - A range that runs past the end is cut at the end, and
part.Rangeshows the cut. A range that starts past the end throwsResourceRangeNotSatisfiableException, whoseContentLengthis the content's length, and which the exception handler answers with 416. - Only the range is read from storage. With SQL Server, reading the last MiB of a large file costs about what reading a 1 MiB file does.
- To download a resource in parts, pass the version ID from the first part's
ResourceInfoto the rest, so a newer upload can't mix two versions in one file.
Every download's result has TotalLength; Range is null when it holds the whole content. To serve ranges over
HTTP, see Range requests.