Dateiexport nach Backblaze B2 in .NET mit Open-Source-Tools
Exportieren Sie eine lokale Datei mit dem quelloffenen AWS SDK für .NET in einen bestehenden B2-Bucket. Dieses vollständige Konsolenbeispiel nutzt die S3-kompatible API von B2, akzeptiert Dateien bis 64 MiB und liest das gespeicherte Objekt zurück, bevor es Erfolg meldet. Leere Dateien sind gültige Eingaben.
Einführung in den Cloud-Speicher Backblaze B2
B2 unterstützt eine native API und eine S3-kompatible API. Hier verwenden wir Letztere mit S3-Bucket-Namen und der Anfragesignierung des AWS SDK. Ein AWS-Konto ist nicht erforderlich.
Ein Backblaze-B2-Konto einrichten
Verwenden Sie einen bestehenden privaten Bucket und einen dedizierten Anwendungsschlüssel, der
auf diesen Bucket und gegebenenfalls ein Exportpräfix beschränkt ist. Dieses Beispiel benötigt
writeFiles für PUT und readFiles für die Überprüfung per GET.
Notieren Sie den S3-Endpunkt und die Region des Buckets. Verwenden Sie die Schlüssel-ID des
Anwendungsschlüssels als Zugriffsschlüssel und den Anwendungsschlüssel als geheimen Schlüssel;
eine Konto-ID ersetzt die Schlüssel-ID nicht. Verwenden Sie für die S3-kompatible API einen
Standard-Anwendungsschlüssel, keinen Hauptschlüssel. Backblaze erläutert diese Einschränkungen im
Leitfaden zu Anwendungsschlüsseln.
Erforderliche .NET-Bibliotheken installieren
Installieren Sie ein aktiv gepflegtes .NET 10 SDK. Die folgenden Befehle verwenden Bash unter Linux und wurden mit SDK 10.0.111 getestet. Halten Sie Ihr SDK mit Patches aktuell; .NET 10 ist eine LTS-Version. Die festgelegten Paketversionen entsprechen der getesteten Kombination des AWS SDK v4 in diesem Beispiel.
Beginnen Sie in einem beschreibbaren Verzeichnis außerhalb eines bestehenden .NET-Projektbaums.
Dieser Block verweigert die Ausführung bei geerbter Projektkonfiguration oder einem bereits
vorhandenen Verzeichnis B2Export. Ihre Shell bleibt auch dann im ursprünglichen
Verzeichnis, wenn die Installation fehlschlägt:
(
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
)
Bei der Backblaze-B2-API in .NET authentifizieren
Stellen Sie diese Variablen über Ihre Verwaltung für sensible Zugangsdaten oder die lokale Startkonfiguration in der Prozessumgebung des Programms bereit. Halten Sie Anwendungsschlüssel aus Quelldateien und Befehlsargumenten heraus.
| Variable | Wert |
|---|---|
B2_ENDPOINT | Ihr regionaler Endpunkt, etwa https://s3.us-west-004.backblazeb2.com |
B2_REGION | Die passende Region, etwa us-west-004 |
B2_BUCKET | Name des bestehenden Buckets |
B2_KEY_ID | ID des Anwendungsschlüssels |
B2_APPLICATION_KEY | Anwendungsschlüssel |
B2_OBJECT_KEY | Zielschlüssel für den folgenden Befehl, etwa reports/report #1.txt |
B2_TIMEOUT_SECONDS | Optionales Zeitlimit für den gesamten Vorgang, 1–300 Sekunden; Standardwert: 300 |
Die unten explizit angegebenen Zugangsdaten verhindern, dass das SDK ein AWS-Profil oder Instanzmetadaten durchsucht. Backblaze dokumentiert die Schlüsselzuordnung und SDK-Konfiguration.
Dateien mit C# zu Backblaze B2 hochladen
Ersetzen Sie B2Export/Program.cs durch dieses vollständige Programm. Der Objektschlüssel
wird unabhängig vom lokalen Dateinamen angegeben. Übergeben Sie ihn ohne Escape-Sequenzen, damit
das SDK Leerzeichen, Unicode und reservierte Zeichen URL-codieren kann. Lokale Pfade müssen
gültiges UTF-8 sein und dürfen das Ersatzzeichen � nicht enthalten.
Diese Absicherung verhindert, dass eine verlustbehaftete Dateinamenkonvertierung unter Linux eine
andere Datei auswählt.
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.");
}
}
Backblaze-Uploads in Ihre Anwendung integrieren
Führen Sie dies im selben Verzeichnis aus, in dem Sie B2Export erstellt haben.
Der erste Befehl erstellt eine kleine Eingabedatei und verweigert das Überschreiben einer bereits
vorhandenen Datei report.txt:
(set -C; printf 'example export\n' > report.txt) &&
dotnet run --disable-build-servers --project B2Export -- ./report.txt "${B2_OBJECT_KEY:-}"
Ein Exit-Status von null und Uploaded and verified. bedeuten, dass PUT abgeschlossen wurde und die heruntergeladenen Bytes mit der lokalen Datei übereinstimmten. GET verwendet die zurückgegebene Versions-ID, sofern verfügbar. Die Überprüfung liest in Blöcken von 64 KiB, statt die gesamte Datei im Arbeitsspeicher zu halten. Daraus ergibt sich keine numerische Obergrenze für die eigenen Puffer des SDK. Lassen Sie die Eingabedatei unverändert, bis der Prozess beendet ist.
UploadAsync akzeptiert eine Instanz von IAmazonS3 und ein
Abbruchtoken. So kann eine Anwendung ihren Client wiederverwenden und einen Anfrageabbruch
weitergeben. Der Client bleibt in der Verantwortung des aufrufenden Codes; die Methode schließt
ihren Dateistream sowohl bei Erfolg als auch bei Fehlern. UploadAsync allein
führt die anschließende Überprüfung nicht durch.
Fehlerbehebung und bewährte Verfahren
Die konfigurierte Standardrichtlinie für Wiederholungsversuche erlaubt bis zu zwei Wiederholungsversuche pro SDK-Anfrage. Das Zeitlimit gilt gemeinsam für Upload, Wiederholungsversuche und Rücklesen. Daher kann eine gültige Datei bei langsamer Verbindung trotzdem fehlschlagen. Strg+C, eine Zeitüberschreitung, eine Ablehnung durch den Speicherdienst oder abweichende Bytes führen zum Status eins. Siehe die SDK-Dokumentation zu Wiederholungsversuchen und Abbruch.
Prüfen Sie bei einer HTTP-Ablehnung den Endpunkt, die passende Region, die Schlüssel-ID, den geheimen Schlüssel, den Bucket, das Präfix und die Lese-/Schreibberechtigungen. Diagnosemeldungen lassen Antwortkörper des Anbieters und sensible Zugangsdaten aus. Ein fehlgeschlagener GET kann einen erfolgreichen Upload zurücklassen. Auch bei einem abgebrochenen PUT kann das Ergebnis auf der Gegenseite ungewiss sein: Prüfen Sie den Bucket vor einem erneuten Versuch, statt anzunehmen, dass nichts gespeichert wurde.
Dieses Beispiel verwendet gestreamte PUT-Anfragen statt Multipart-Uploads, auch für leere Dateien. Nutzen Sie für größere Exporte die Multipart-Funktionen des SDK und planen Sie die Bereinigung aufgegebener Multipart-Uploads ein. Die Prüfsummeneinstellungen fordern nur Prüfsummen an, die das SDK als erforderlich einstuft. Das explizite Rücklesen prüft die Bytes unabhängig von optionalen Prüfsummen-Headern. HTTPS und Anfragesignierung bleiben aktiviert.
Ein erneuter Upload unter demselben Schlüssel erstellt eine weitere B2-Dateiversion. Verwenden Sie für jeden Export einen neuen Schlüssel, wenn ein Ersetzen nicht beabsichtigt ist. Um Testdaten dauerhaft zu entfernen, löschen Sie für die zugehörigen Schlüssel jede Version und jeden Ausblendungsmarker. Das bloße Ausblenden der aktuellen Datei lässt ihren Verlauf bestehen. Dieses Programm behält das exportierte Objekt bei und übernimmt diese Bereinigung nicht.
