Exporter vers Backblaze B2 en .NET avec des outils open source
Exportez un fichier local vers un bucket B2 existant avec le SDK AWS open source pour .NET. Cet exemple complet d’application console utilise l’API compatible S3 de B2, accepte des fichiers jusqu’à 64 MiB et relit l’objet stocké avant de signaler la réussite. Les fichiers vides sont des entrées valides.
Présentation du stockage cloud Backblaze B2
B2 prend en charge une API native et une API compatible S3. Nous utilisons ici la seconde, avec les noms de buckets S3 et la signature des requêtes du SDK AWS. Aucun compte AWS n’est nécessaire.
Configurer un compte Backblaze B2
Utilisez un bucket privé existant et une clé d’application dédiée dont l’accès est limité à ce
bucket et, si nécessaire, à un préfixe d’exportation. Cet exemple nécessite
writeFiles pour PUT et readFiles pour sa vérification GET.
Notez le point de terminaison S3 du bucket et sa région. Utilisez l’ID de clé d’application
comme clé d’accès et la clé d’application comme secret ; un ID de compte ne remplace pas
l’ID de clé. Pour l’API compatible S3, utilisez une clé d’application standard et non la clé
principale. Backblaze explique ces restrictions dans son
guide des clés d’application.
Installer les bibliothèques .NET requises
Installez un SDK .NET 10 bénéficiant d’une maintenance. Les commandes ci-dessous utilisent Bash sous Linux et ont été testées avec le SDK 10.0.111. Appliquez les correctifs de votre SDK ; .NET 10 est une version LTS. Les versions des packages fixent la combinaison du SDK AWS v4 testée pour cet exemple.
Commencez dans un répertoire accessible en écriture, en dehors de l’arborescence d’un projet .NET
existant. Ce bloc refuse toute configuration de projet héritée ainsi qu’un répertoire
B2Export existant, et conserve le répertoire initial de votre shell même si
l’installation échoue :
(
command -v dotnet >/dev/null || { printf 'Install the .NET 10 SDK first.\n' >&2; exit 1; }
parent="$PWD"
while :; do
for config in "$parent"/global.json "$parent"/[Dd]irectory.[Bb]uild.* \
"$parent"/[Dd]irectory.[Pp]ackages.props "$parent"/[Nn]u[Gg]et.[Cc]onfig; do
if [ -e "$config" ]; then
printf 'Choose a directory outside an existing .NET project tree.\n' >&2
exit 1
fi
done
[ "$parent" = / ] && break
parent="${parent%/*}"
[ -n "$parent" ] || parent=/
done
mkdir B2Export && cd B2Export &&
dotnet new console --framework net10.0 --name B2Export --output . &&
dotnet add package AWSSDK.Core --version 4.0.102.11 &&
dotnet add package AWSSDK.S3 --version 4.0.104.4
)
S’authentifier auprès de l’API Backblaze B2 en .NET
Définissez ces variables dans l’environnement du processus du programme via votre gestionnaire de secrets ou votre configuration de lancement locale. Ne placez pas les clés d’application dans les fichiers source ni dans les arguments de commande.
| Variable | Valeur |
|---|---|
B2_ENDPOINT | Votre point de terminaison régional, par exemple https://s3.us-west-004.backblazeb2.com |
B2_REGION | La région correspondante, par exemple us-west-004 |
B2_BUCKET | Nom du bucket existant |
B2_KEY_ID | ID de clé d’application |
B2_APPLICATION_KEY | Clé d’application |
B2_OBJECT_KEY | Clé de destination pour la commande ci-dessous, par exemple reports/report #1.txt |
B2_TIMEOUT_SECONDS | Délai maximal facultatif pour toute l’opération, 1–300 secondes ; 300 par défaut |
Les informations d’authentification explicites ci-dessous empêchent le SDK de les rechercher dans un profil AWS ou dans les métadonnées d’instance. Backblaze documente la correspondance des clés et la configuration du SDK.
Téléverser des fichiers vers Backblaze B2 avec C#
Remplacez B2Export/Program.cs par ce programme complet. La clé de l’objet est fournie
indépendamment du nom du fichier local ; transmettez-la sans échappement pour que le SDK puisse
encoder les espaces, les caractères Unicode et les caractères réservés. Les chemins locaux doivent
être en UTF-8 valide et ne doivent pas contenir le caractère de remplacement
� ; cette protection empêche une conversion avec perte du nom de fichier
de sélectionner un autre fichier sous Linux.
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
public static class B2Exporter
{
public static async Task<int> Main(string[] args)
{
using var deadline = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; deadline.Cancel(); };
try
{
if (args.Length != 2) throw new ArgumentException("Pass a local file and an object key.");
if (args[0].Contains('\uFFFD')) throw new ArgumentException("Unsupported local filename.");
var seconds = Environment.GetEnvironmentVariable("B2_TIMEOUT_SECONDS") ?? "300";
if (!int.TryParse(seconds, out var timeout) || timeout < 1 || timeout > 300)
throw new ArgumentException("B2_TIMEOUT_SECONDS must be between 1 and 300.");
deadline.CancelAfter(TimeSpan.FromSeconds(timeout));
var endpoint = new Uri(Required("B2_ENDPOINT"));
if (endpoint.Scheme != "https" || endpoint.UserInfo.Length != 0 ||
endpoint.AbsolutePath != "/" || endpoint.Query.Length != 0 || endpoint.Fragment.Length != 0)
throw new ArgumentException("Use the HTTPS regional endpoint without a bucket or path.");
var config = new AmazonS3Config
{
ServiceURL = endpoint.AbsoluteUri,
AuthenticationRegion = Required("B2_REGION"),
ForcePathStyle = true,
RetryMode = RequestRetryMode.Standard,
MaxErrorRetry = 2,
RequestChecksumCalculation = RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation = ResponseChecksumValidation.WHEN_REQUIRED
};
using var client = new AmazonS3Client(
Required("B2_KEY_ID"), Required("B2_APPLICATION_KEY"), config);
var bucket = Required("B2_BUCKET");
var version = await UploadAsync(client, bucket, args[0], args[1], deadline.Token);
await VerifyAsync(client, bucket, args[0], args[1], version, deadline.Token);
Console.WriteLine("Uploaded and verified.");
return 0;
}
catch (OperationCanceledException) { Console.Error.WriteLine("Upload canceled or timed out."); }
catch (AmazonS3Exception error) { Console.Error.WriteLine($"Storage returned HTTP {(int)error.StatusCode}."); }
catch (Exception) { Console.Error.WriteLine("Upload failed. Check the file and storage configuration."); }
return 1;
}
private static string Required(string name) =>
Environment.GetEnvironmentVariable(name) is { Length: > 0 } value
? value : throw new ArgumentException($"Missing {name}.");
public static async Task<string?> UploadAsync(IAmazonS3 client, string bucket, string path,
string key, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("An object key is required.");
await using var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (input.Length > 64L * 1024 * 1024) throw new IOException("This example accepts at most 64 MiB.");
var uploaded = await client.PutObjectAsync(new PutObjectRequest
{
BucketName = bucket,
Key = key,
InputStream = input,
AutoCloseStream = false,
ContentType = "application/octet-stream",
UseChunkEncoding = false
}, cancellationToken);
return uploaded.VersionId;
}
private static async Task VerifyAsync(IAmazonS3 client, string bucket, string path,
string key, string? version, CancellationToken cancellationToken)
{
using var stored = await client.GetObjectAsync(new GetObjectRequest
{
BucketName = bucket,
Key = key,
VersionId = version
}, cancellationToken);
await using var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (stored.ContentLength != input.Length) throw new IOException("Stored length differs.");
var local = new byte[64 * 1024];
var remote = new byte[64 * 1024];
int count;
while ((count = await input.ReadAsync(local, cancellationToken)) != 0)
{
await stored.ResponseStream.ReadExactlyAsync(remote.AsMemory(0, count), cancellationToken);
if (!local.AsSpan(0, count).SequenceEqual(remote.AsSpan(0, count)))
throw new IOException("Stored bytes differ.");
}
if (await stored.ResponseStream.ReadAsync(remote.AsMemory(0, 1), cancellationToken) != 0)
throw new IOException("Stored object has extra bytes.");
}
}
Intégrer les téléversements vers Backblaze à votre application
Exécutez ces commandes depuis le répertoire dans lequel vous avez créé
B2Export. La première commande crée un petit fichier d’entrée et refuse
d’écraser un fichier report.txt existant :
(set -C; printf 'example export\n' > report.txt) &&
dotnet run --disable-build-servers --project B2Export -- ./report.txt "${B2_OBJECT_KEY:-}"
Un code de sortie zéro et Uploaded and verified. signifient que PUT s’est terminé et que les octets téléchargés correspondaient au fichier local. La requête GET utilise l’ID de version renvoyé lorsqu’il est disponible. La vérification lit les données par blocs de 64 KiB au lieu de conserver tout le fichier en mémoire ; cela n’impose aucune limite chiffrée aux propres tampons du SDK. Ne modifiez pas le fichier d’entrée avant la fin du processus.
UploadAsync accepte une instance de IAmazonS3 et un jeton
d’annulation ; une application peut donc réutiliser son client et propager l’annulation des
requêtes. Le client appartient à l’appelant ; la méthode ferme son flux de fichier en cas de
réussite comme d’échec. UploadAsync seul n’effectue pas la vérification ultérieure.
Dépannage et bonnes pratiques
La stratégie standard de nouvelle tentative configurée autorise jusqu’à deux nouvelles tentatives par requête du SDK. Le délai maximal couvre le téléversement, les nouvelles tentatives et la relecture dans leur ensemble ; le traitement d’un fichier valide peut donc échouer si la connexion est lente. Ctrl+C, l’expiration du délai, un refus du service de stockage ou une différence entre les octets entraîne un code de sortie un. Consultez la documentation du SDK sur les nouvelles tentatives et l’annulation.
En cas de refus HTTP, vérifiez le point de terminaison, la région correspondante, l’ID de clé, le secret de la clé, le bucket, le préfixe et les droits de lecture et d’écriture. Les diagnostics omettent les corps des réponses du fournisseur et les secrets. Une requête GET qui échoue peut laisser en place un objet dont le téléversement a réussi. Une requête PUT annulée peut également avoir un résultat incertain côté distant : inspectez le bucket avant de réessayer, sans supposer que rien n’a été stocké.
Cet exemple utilise des requêtes PUT en flux plutôt que des téléversements en plusieurs parties, y compris pour les fichiers vides. Pour les exportations plus volumineuses, utilisez les fonctions de téléversement en plusieurs parties du SDK et prévoyez le nettoyage de ces téléversements lorsqu’ils sont abandonnés. Les paramètres de somme de contrôle ne demandent que les sommes de contrôle que le SDK juge nécessaires ; la relecture explicite vérifie les octets indépendamment des en-têtes facultatifs de somme de contrôle. HTTPS et la signature des requêtes restent activés.
Un nouveau téléversement vers la même clé crée une autre version de fichier B2. Utilisez une nouvelle clé pour chaque exportation si vous ne souhaitez pas remplacer l’objet. Pour supprimer définitivement les données de test, supprimez toutes les versions et tous les marqueurs de masquage associés à leurs clés ; masquer uniquement le fichier actuel conserve son historique. Ce programme conserve l’objet exporté et ne gère pas ce nettoyage.
