Errors
Resourcefull reports an expected absence with a return value and everything else with an exception.
| Situation | Result |
|---|---|
| A single resource, version or container that doesn't exist | null |
| A listing with nothing in it | an empty list, never null |
An existence check, or a delete of something that may not exist (…IfExistsAsync) |
bool |
A null name, or a missing argument |
ArgumentNullException, before storage is contacted |
| A name that breaks the name rules | ResourceNameException, an ArgumentException, before storage is contacted |
| Uploading a name that exists, when the upload is create-only | ResourceConflictException |
| Creating a container that exists | ResourceConflictException |
| Uploading into a container that doesn't exist | ResourceContainerNotFoundException |
An upload or delete whose ResourceConditions the current version doesn't meet, including a delete with conditions of a name that doesn't exist |
ResourcePreconditionFailedException (nothing changes) |
| Moving a resource onto a name that exists, or importing into a container that exists 4.0 | ResourceConflictException |
| A download range that starts past the end of the content 4.0 | ResourceRangeNotSatisfiableException, with the content's length |
| A download range with a negative offset or length 4.0 | ArgumentOutOfRangeException |
| A version deleted while it's being downloaded, with SQL Server | IOException from the download stream, rather than a stream that ends early |
| Importing a file that isn't an export, describes something that can't be imported, or whose content was changed 4.0 | InvalidDataException (nothing is left behind) |
| Importing from a stream that can't seek 4.0 | ArgumentException |
| An import that fails, and then fails to delete what it imported 4.0 | AggregateException, holding both failures |
| A feature the storage provider doesn't support, such as a provider written for an earlier release | NotSupportedException |
| An upload whose declared length differs from its content | ResourceContentLengthMismatchException (nothing is stored) |
| A write that breaks its container's policy 4.0 | ResourcePolicyViolationException (nothing changes) |
| An upload or imported version your upload inspector refuses 4.0 | ResourceContentRefusedException, with the inspector's reason (nothing is stored) |
| An upload inspector that throws 4.0 | the inspector's exception (nothing is stored) |
UseVersioning with fewer than one version |
ArgumentOutOfRangeException |
| Configuring no provider, or two | InvalidOperationException from AddResourcefull |
| A canceled operation | OperationCanceledException |
| A storage failure | the provider's exception, such as SqlException; with SQL Server, a transient failure that outlasts the retries throws EF Core's RetryLimitExceededException, with the last failure as its inner exception |
Reading from a container that doesn't exist
Writing into a container that doesn't exist throws ResourceContainerNotFoundException, but reading from it and
deleting from it treat it as an empty container: listings are empty, GetUsageAsync 4.0 reports
zero, downloads and GetResourceInfoAsync return null, and ExistsAsync and …IfExistsAsync deletes return
false. So a misspelled container name looks like an empty container rather than an error. Where that
matters, such as a container name that comes from a request, check the service client's ContainerExistsAsync
first and answer 404 if it's false:
if (!await resources.ContainerExistsAsync(name, cancellationToken)) return Results.NotFound();
IResourceContainerClient container = resources.GetContainerClient(name);
Resourcefull's exceptions
All of them derive from ResourcefullException, so one catch handles any of them:
| Exception | Meaning | HTTP status it usually maps to |
|---|---|---|
ResourceConflictException |
The name or container already exists. | 409 Conflict |
ResourceContainerNotFoundException |
The container was never created, or was deleted before the write. | 404 Not Found |
ResourceContentLengthMismatchException |
The upload's declared length didn't match its content. | 400 Bad Request |
ResourcePreconditionFailedException |
The name's current version didn't meet the upload's or delete's conditions. | 412 Precondition Failed |
ResourcePolicyViolationException 4.0 |
The write broke its container's policy; Rule says which rule, and Limit its limit. |
413 for the size limits, 409 for MaxResourceCount, 403 for ReadOnly, 415 for AllowedContentTypes |
ResourceRangeNotSatisfiableException 4.0 |
The download range starts past the end of the content; ContentLength says how long it is. |
416 Range Not Satisfiable |
ResourceContentRefusedException 4.0 |
The upload inspector refused the content; Reason says why. |
422 Unprocessable Content |
ResourceUploadOffsetException 4.0 |
An append to an upload session didn't start where its stored content ends; CurrentOffset says where to resume. |
409 Conflict |
ResourceRetainedException 4.0 |
A delete would remove a version that's retained or under a legal hold; VersionId, RetainUntil and LegalHold say which and why. Nothing was deleted, unless the version was protected while a batched delete was under way. |
409 Conflict |
ResourceUploadInterruptedException 4.0 |
An upload's version was deleted before the upload completed, by a container delete running at the same time or by the sweep that removes uploads that have stored nothing for a day. Nothing was stored; try again, though if the container was deleted, the retry throws ResourceContainerNotFoundException. |
409 Conflict |
ResourceUploadLimitException 4.0 |
An append would take an upload session past what its provider can store in one session, or an upload is larger than its provider can store in one object. | 413 Content Too Large |
ResourceNameException derives from ArgumentException rather than ResourcefullException, so existing
catch (ArgumentException) code still catches it; it maps to 400 Bad Request. Its Name 4.0 is the name that
broke the rules.
What an exception is about
ContainerName and ResourceName 4.0 say what a ResourcefullException is about, so you don't have to read its
message:
- A resource: both are set. For a copy or move whose destination is the problem, such as a destination name that exists, they name the destination.
- A container: only
ContainerNameis set, as for creating a container that exists, or for aResourcePolicyViolationException, which names the container whose policy refused the write.
They're set whatever the storage provider: when a provider doesn't set them, the client fills them in from the call that failed. They aren't sent in HTTP responses or recorded in telemetry, so a resource name in them stays within your application.
try
{
await container.UploadAsync(name, content);
}
catch (ResourceConflictException)
{
// someone else stored this name first
}
catch (ResourceContainerNotFoundException)
{
// create the container, or report it missing
}
The ASP.NET Core package maps them all, the name exception included, to responses in one place.