From a4e931b82f2e296758980e5e190a401aa9a38d80 Mon Sep 17 00:00:00 2001 From: axtox Date: Thu, 5 Jun 2025 03:11:05 +0300 Subject: [PATCH 01/20] New Dotnet project added to the samples --- dotnet/.gitignore | 57 +++++++++++++++++++++++++++++++++++++++++++++++ dotnet/README.md | 0 2 files changed, 57 insertions(+) create mode 100644 dotnet/.gitignore create mode 100644 dotnet/README.md diff --git a/dotnet/.gitignore b/dotnet/.gitignore new file mode 100644 index 0000000..08e0b17 --- /dev/null +++ b/dotnet/.gitignore @@ -0,0 +1,57 @@ +## A streamlined .gitignore for modern .NET projects +## including temporary files, build results, and +## files generated by popular .NET tools. If you are +## developing with Visual Studio, the VS .gitignore +## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore +## has more thorough IDE-specific entries. +## +## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore + +# Environment files +.env + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg + +# Others +~$* +*~ +CodeCoverage/ + +# MSBuild Binary and Structured Log +*.binlog + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml \ No newline at end of file diff --git a/dotnet/README.md b/dotnet/README.md new file mode 100644 index 0000000..e69de29 From ccc73476096f46f23bbdef76ad9b4415258b8110 Mon Sep 17 00:00:00 2001 From: axtox Date: Thu, 5 Jun 2025 05:30:20 +0300 Subject: [PATCH 02/20] Add .NET sample task and S3 connection example This includes only basic S3 setup --- .vscode/tasks.json | 14 +++++++++ dotnet/src/.env.example | 8 +++++ dotnet/src/sample.cs | 69 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 .vscode/tasks.json create mode 100644 dotnet/src/.env.example create mode 100644 dotnet/src/sample.cs diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7768db1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,14 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run .NET Sample", + "command": "dotnet", + "type": "shell", + "args": ["run", "sample.cs"], + "options": { + "cwd": "${workspaceFolder}/dotnet/src" + }, + } + ] +} \ No newline at end of file diff --git a/dotnet/src/.env.example b/dotnet/src/.env.example new file mode 100644 index 0000000..65b6a59 --- /dev/null +++ b/dotnet/src/.env.example @@ -0,0 +1,8 @@ +# Данные для подключения к S3 хранилищу Timeweb Cloud +# можно найти во вкладке `Дашборд` в разделе `Хранилище S3` +# в личном кабинете Timeweb Cloud. +S3__SERVICEURL="https://s3.twcstorage.ru" +S3__REGION="ru-1" +S3__BUCKETNAME="" +S3__ACCESSKEY="" +S3__SECRETKEY="" \ No newline at end of file diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs new file mode 100644 index 0000000..9ae2c6f --- /dev/null +++ b/dotnet/src/sample.cs @@ -0,0 +1,69 @@ +#:package AWSSDK.S3@3.5.10.2 +#:package Microsoft.Extensions.Configuration.EnvironmentVariables@9.0.5 +#:package Microsoft.Extensions.Configuration.Binder@9.0.5 +#:package DotNetEnv@3.1.1 + +using Amazon.Runtime; +using Amazon.S3; +using DotNetEnv; +using Microsoft.Extensions.Configuration; +using System; + +Env.Load(); +var builder = new ConfigurationBuilder() + .AddEnvironmentVariables(); +var configuration = builder.Build(); +var settings = configuration.GetSection("S3").Get(); + +var s3Client = CreateS3Client(settings); +Console.WriteLine("S3 Клиент успешно создан и готов к работе."); + +Console.WriteLine($"Создание бакета {settings.BucketName}..."); + +/// +/// Создает экземпляр клиента Amazon S3 с заданными настройками. +/// +/// Настройки для подключения к S3. +/// Экземпляр клиента Amazon S3. +/// Если s3Settings равен null. +/// Если какие-либо обязательные поля в s3Settings пустые. +AmazonS3Client CreateS3Client(S3Settings? s3Settings) +{ + if (s3Settings == null) + throw new ArgumentNullException(nameof(s3Settings), "Настройки S3 не могут быть пустыми."); + if (string.IsNullOrEmpty(s3Settings.AccessKey) || string.IsNullOrEmpty(s3Settings.SecretKey)) + throw new ArgumentException("Все поля в настройках S3 должны быть заполнены, включая AccessKey, SecretKey, BucketName и ServiceUrl."); + + var config = new AmazonS3Config + { + ServiceURL = s3Settings.ServiceUrl, + ForcePathStyle = true + }; + var credentials = new BasicAWSCredentials(s3Settings.AccessKey, s3Settings.SecretKey); + var client = new AmazonS3Client(credentials, config); + return client; +} + +/// +/// Класс для хранения настроек подключения к Amazon S3. +/// Используется для конфигурации клиента S3. +/// +/// Данные для подключения к S3 хранилищу Timeweb Cloud +/// можно найти во вкладке `Дашборд` в разделе `Хранилище S3` +/// в личном кабинете Timeweb Cloud. +/// +class S3Settings +{ + public required string AccessKey { get; set; } + public required string SecretKey { get; set; } + public required string ServiceUrl { get; set; } + public required string BucketName { get; set; } + public required string Region { get; set; } + + public bool IsValid() + => !string.IsNullOrEmpty(AccessKey) && + !string.IsNullOrEmpty(SecretKey) && + !string.IsNullOrEmpty(ServiceUrl) && + !string.IsNullOrEmpty(BucketName) && + !string.IsNullOrEmpty(Region); +} \ No newline at end of file From 25af41a2041f801bf808d4e82b7f4a023f76a68a Mon Sep 17 00:00:00 2001 From: axtox Date: Thu, 5 Jun 2025 06:51:40 +0300 Subject: [PATCH 03/20] Enhance S3 sample: add bucket listing and error handling in TryExecute method --- dotnet/src/.env.example | 1 + dotnet/src/sample.cs | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/dotnet/src/.env.example b/dotnet/src/.env.example index 65b6a59..24a515d 100644 --- a/dotnet/src/.env.example +++ b/dotnet/src/.env.example @@ -3,6 +3,7 @@ # в личном кабинете Timeweb Cloud. S3__SERVICEURL="https://s3.twcstorage.ru" S3__REGION="ru-1" +# Имя нового или существующего бакета, лучше всего работает UUID S3__BUCKETNAME="" S3__ACCESSKEY="" S3__SECRETKEY="" \ No newline at end of file diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 9ae2c6f..253c5a2 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -5,20 +5,32 @@ using Amazon.Runtime; using Amazon.S3; +using Amazon.S3.Model; using DotNetEnv; using Microsoft.Extensions.Configuration; using System; +using System.Text.Json; Env.Load(); var builder = new ConfigurationBuilder() .AddEnvironmentVariables(); var configuration = builder.Build(); -var settings = configuration.GetSection("S3").Get(); +var settings = configuration.GetSection("S3").Get()!; var s3Client = CreateS3Client(settings); Console.WriteLine("S3 Клиент успешно создан и готов к работе."); -Console.WriteLine($"Создание бакета {settings.BucketName}..."); +Console.WriteLine($"Создание бакета {settings.BucketName}."); +await TryExecute( + s3Client.PutBucketAsync(new PutBucketRequest { BucketName = settings.BucketName })); + +Console.WriteLine($"Получение информации о регионе бакета {settings.BucketName}."); +await TryExecute( + s3Client.GetBucketLocationAsync(new GetBucketLocationRequest { BucketName = settings.BucketName })); + +Console.WriteLine($"Получение списка бакетов."); +await TryExecute( + s3Client.ListBucketsAsync()); /// /// Создает экземпляр клиента Amazon S3 с заданными настройками. @@ -44,6 +56,20 @@ AmazonS3Client CreateS3Client(S3Settings? s3Settings) return client; } +async Task TryExecute(Task action) where T : AmazonWebServiceResponse +{ + try + { + var response = await action; + Console.WriteLine($"Операция выполнена успешно. Результат: \n" + + $"{ JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true })}\n"); + } + catch (Exception ex) + { + Console.WriteLine($"Ошибка: {ex.Message}"); + } +} + /// /// Класс для хранения настроек подключения к Amazon S3. /// Используется для конфигурации клиента S3. From 4b731211b3c40866240ead19c0888dee49aae4d0 Mon Sep 17 00:00:00 2001 From: axtox Date: Thu, 5 Jun 2025 07:08:34 +0300 Subject: [PATCH 04/20] Add README.md for .NET S3 sample with usage instructions and requirements --- .vscode/tasks.json | 5 ++++- dotnet/README.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7768db1..8dafa04 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,7 +8,10 @@ "args": ["run", "sample.cs"], "options": { "cwd": "${workspaceFolder}/dotnet/src" - }, + }, + "group": { + "kind": "build" + } } ] } \ No newline at end of file diff --git a/dotnet/README.md b/dotnet/README.md index e69de29..5067829 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -0,0 +1,37 @@ +# Использование dotnet-sdk Для Работы с S3 Хранилищем Timeweb Cloud +## Описание +> [!WARNING] +> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку в Timeweb не поддерживает самые последние версии клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), `nullable` поля и др. + +Приложение показывает использование S3 хранилища Timeweb Cloud в `dotnet`, при запуске будут выполнены следующие команды: +1. Создание бакета (если уже существует - вернет детали существующего бакета) +2. Регион бакета +3. Список бакетов + +> [!CAUTION] +> Во время выполнения этой программы будет выполнено удаление бакета и всех файлов в нем! Будьте аккуратны когда указываете имя существующего бакета в `.env` +При успешном завершении операции будет выведен ответ от S3 API в формате JSON, при неудаче - детали ошибки. + +В качестве клиента к S3 хранилищем Timeweb Cloud используется официальный SDK от Amazon (`AWS.SDK`) версии `3.5.10.2`, ендпоинт Timeweb Cloud S3 API `https://s3.twcstorage.ru` +В [sample.cs](./src/sample.cs) описаны основные ендпоинты для работы с API, для запуска потребуется переименовать [.env.example](./src/.env.example) в `.env` и вписать настоящие данные для подключения к S3 хранилищу Timeweb Cloud, которые можно найти во вкладке `Дашборд` в разделе `Хранилище S3` в личном кабинете Timeweb Cloud. + +Больше информации об объектном S3 хранилище Timeweb Cloud в [документации](https://timeweb.cloud/docs/s3-storage). + +## Требования +- Linux/Windows/macOS +- dotnet 10/Docker +### Запуск через CLI +Для локального запуска требуется установка [dotnet SDK версии >10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) + +После установки достаточно выполнить эту команду в любой консоли (убедитесь что выполняете команду из папки ./dotnet/src): +```shell +dotnet run sample.cs +``` +### Запуск через Visual Studio Code +> [!NOTE] +> Так как приложение писалось когда .NET 10 был еще в Preview 4, запуск через F5 с дебаггером на тот момент не был реализован для direct file приложений, поэтому опция с дебагом отсутствует так же как и файл launch.json +Все ёще требуется установка [dotnet SDK версии >10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) + +Для удобства в проекте создан файл `tasks.json` благодаря которому программу можно запустить через сочетание клавиш `CTRL + SHIFT + B` для Windows и `CMD + SHIFT + B` для MacOS -> `Run .NET Sample` + +### Запуск через Docker \ No newline at end of file From 7a6598fcbe85d09f6b745f83f5d4a3a73313a5e5 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 6 Jun 2025 23:14:45 +0300 Subject: [PATCH 05/20] Fix formatting in README.md: add line breaks for better readability --- dotnet/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dotnet/README.md b/dotnet/README.md index 5067829..68a0b01 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -10,6 +10,7 @@ > [!CAUTION] > Во время выполнения этой программы будет выполнено удаление бакета и всех файлов в нем! Будьте аккуратны когда указываете имя существующего бакета в `.env` + При успешном завершении операции будет выведен ответ от S3 API в формате JSON, при неудаче - детали ошибки. В качестве клиента к S3 хранилищем Timeweb Cloud используется официальный SDK от Amazon (`AWS.SDK`) версии `3.5.10.2`, ендпоинт Timeweb Cloud S3 API `https://s3.twcstorage.ru` @@ -30,6 +31,7 @@ dotnet run sample.cs ### Запуск через Visual Studio Code > [!NOTE] > Так как приложение писалось когда .NET 10 был еще в Preview 4, запуск через F5 с дебаггером на тот момент не был реализован для direct file приложений, поэтому опция с дебагом отсутствует так же как и файл launch.json + Все ёще требуется установка [dotnet SDK версии >10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) Для удобства в проекте создан файл `tasks.json` благодаря которому программу можно запустить через сочетание клавиш `CTRL + SHIFT + B` для Windows и `CMD + SHIFT + B` для MacOS -> `Run .NET Sample` From 44e2b660df906d3ebe030444806842faf1ecf593 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 6 Jun 2025 23:15:37 +0300 Subject: [PATCH 06/20] Refactor S3 sample: improve error handling and enhance JSON output formatting --- dotnet/src/sample.cs | 50 ++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 253c5a2..0175af8 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -2,12 +2,16 @@ #:package Microsoft.Extensions.Configuration.EnvironmentVariables@9.0.5 #:package Microsoft.Extensions.Configuration.Binder@9.0.5 #:package DotNetEnv@3.1.1 +#:package Spectre.Console.Cli@0.50.0 +#:package Spectre.Console.Json@0.50.0 using Amazon.Runtime; using Amazon.S3; using Amazon.S3.Model; using DotNetEnv; using Microsoft.Extensions.Configuration; +using Spectre.Console; +using Spectre.Console.Json; using System; using System.Text.Json; @@ -32,14 +36,8 @@ await TryExecute( await TryExecute( s3Client.ListBucketsAsync()); -/// -/// Создает экземпляр клиента Amazon S3 с заданными настройками. -/// -/// Настройки для подключения к S3. -/// Экземпляр клиента Amazon S3. -/// Если s3Settings равен null. -/// Если какие-либо обязательные поля в s3Settings пустые. -AmazonS3Client CreateS3Client(S3Settings? s3Settings) + +static AmazonS3Client CreateS3Client(S3Settings? s3Settings) { if (s3Settings == null) throw new ArgumentNullException(nameof(s3Settings), "Настройки S3 не могут быть пустыми."); @@ -56,13 +54,36 @@ AmazonS3Client CreateS3Client(S3Settings? s3Settings) return client; } -async Task TryExecute(Task action) where T : AmazonWebServiceResponse +#region +static async Task TryExecute(Task action) where T : AmazonWebServiceResponse { try { var response = await action; - Console.WriteLine($"Операция выполнена успешно. Результат: \n" + - $"{ JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true })}\n"); + var json = JsonSerializer.Serialize(response, new JsonSerializerOptions + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + Console.WriteLine($"Операция выполнена успешно. Результат: \n"); + // JSON в формате Visual Studio Code + AnsiConsole.Write( + new Panel( + new JsonText(json) + .BracesColor(Color.Grey84) + .BracketColor(Color.Grey84) + .ColonColor(Color.Grey84) + .CommaColor(Color.Grey84) + .StringColor(Color.LightSalmon3_1) + .NumberColor(Color.DarkSeaGreen3_1) + .BooleanColor(Color.SkyBlue3) + .NullColor(Color.SkyBlue3) + .MemberColor(Color.SteelBlue1_1) + ) + .Header("JSON") + .Collapse() + .RoundedBorder() + .BorderColor(Color.Yellow)); } catch (Exception ex) { @@ -74,15 +95,17 @@ async Task TryExecute(Task action) where T : AmazonWebServiceResponse /// Класс для хранения настроек подключения к Amazon S3. /// Используется для конфигурации клиента S3. /// +/// Заполняется автоматически из переменных окружения (файл .env) +/// /// Данные для подключения к S3 хранилищу Timeweb Cloud /// можно найти во вкладке `Дашборд` в разделе `Хранилище S3` /// в личном кабинете Timeweb Cloud. /// class S3Settings { + public required string ServiceUrl { get; set; } public required string AccessKey { get; set; } public required string SecretKey { get; set; } - public required string ServiceUrl { get; set; } public required string BucketName { get; set; } public required string Region { get; set; } @@ -92,4 +115,5 @@ public bool IsValid() !string.IsNullOrEmpty(ServiceUrl) && !string.IsNullOrEmpty(BucketName) && !string.IsNullOrEmpty(Region); -} \ No newline at end of file +} +#endregion \ No newline at end of file From c5fb1f5fed6e9f07620b9036dac2cda111f83b10 Mon Sep 17 00:00:00 2001 From: axtox Date: Sat, 7 Jun 2025 04:40:25 +0300 Subject: [PATCH 07/20] Enhance S3 sample: add file upload, object retrieval, and bucket deletion functionalities --- dotnet/src/sample.cs | 116 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 0175af8..acf24ba 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -2,7 +2,6 @@ #:package Microsoft.Extensions.Configuration.EnvironmentVariables@9.0.5 #:package Microsoft.Extensions.Configuration.Binder@9.0.5 #:package DotNetEnv@3.1.1 -#:package Spectre.Console.Cli@0.50.0 #:package Spectre.Console.Json@0.50.0 using Amazon.Runtime; @@ -13,7 +12,9 @@ using Spectre.Console; using Spectre.Console.Json; using System; +using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; Env.Load(); var builder = new ConfigurationBuilder() @@ -36,6 +37,72 @@ await TryExecute( await TryExecute( s3Client.ListBucketsAsync()); +Console.WriteLine($"Отправка нового текстового файла через передачу текста в ContentBody."); +await TryExecute( + s3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = settings.BucketName, + Key = "example-from-text.txt", + ContentBody = "Привет, это файл созданный из текста!" + })); + +Console.WriteLine($"Отправка нового текстового файла."); +// Имитация чтения файла через MemoryStream вместо File.OpenRead +await using var file = new MemoryStream(Encoding.UTF8.GetBytes("Привет, это файл переданный напрямую!")); +await TryExecute( + s3Client.PutObjectAsync(new PutObjectRequest + { + BucketName = settings.BucketName, + Key = "example-from-file.txt", + InputStream = file + })); + +Console.WriteLine($"Получение списка объектов в бакете {settings.BucketName}."); +await TryExecute( + s3Client.ListObjectsV2Async(new ListObjectsV2Request { BucketName = settings.BucketName })); + +Console.WriteLine($"Получение объекта example-from-text.txt."); +await TryRetrieve( + s3Client.GetObjectAsync(new GetObjectRequest + { + BucketName = settings.BucketName, + Key = "example-from-text.txt" + })); + +Console.WriteLine($"Получение метаданных объекта example-from-text.txt."); +await TryExecute( + s3Client.GetObjectMetadataAsync(new GetObjectMetadataRequest + { + BucketName = settings.BucketName, + Key = "example-from-text.txt" + })); + +Console.WriteLine($"Копирование объекта example-from-text.txt."); +await TryExecute( + s3Client.CopyObjectAsync(new CopyObjectRequest + { + SourceBucket = settings.BucketName, + SourceKey = "example-from-text.txt", + DestinationBucket = settings.BucketName, + DestinationKey = "example-from-text-copy.txt" + })); + +Console.WriteLine($"Удаление всех объектов."); +await TryExecute( + s3Client.DeleteObjectsAsync(new DeleteObjectsRequest + { + BucketName = settings.BucketName, + Objects = new List + { + new KeyVersion { Key = "example-from-text.txt" }, + new KeyVersion { Key = "example-from-file.txt" }, + new KeyVersion { Key = "example-from-text-copy.txt" } + } + })); + +Console.WriteLine($"Удаление бакета {settings.BucketName}."); +await TryExecute( + s3Client.DeleteBucketAsync(new DeleteBucketRequest { BucketName = settings.BucketName })); static AmazonS3Client CreateS3Client(S3Settings? s3Settings) { @@ -63,25 +130,48 @@ static async Task TryExecute(Task action) where T : AmazonWebServiceRespon var json = JsonSerializer.Serialize(response, new JsonSerializerOptions { WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault }); Console.WriteLine($"Операция выполнена успешно. Результат: \n"); - // JSON в формате Visual Studio Code + // JSON стайлинг в формате Visual Studio Code AnsiConsole.Write( new Panel( new JsonText(json) - .BracesColor(Color.Grey84) - .BracketColor(Color.Grey84) - .ColonColor(Color.Grey84) - .CommaColor(Color.Grey84) - .StringColor(Color.LightSalmon3_1) - .NumberColor(Color.DarkSeaGreen3_1) - .BooleanColor(Color.SkyBlue3) - .NullColor(Color.SkyBlue3) - .MemberColor(Color.SteelBlue1_1) + .BracesColor(Color.Grey84) + .BracketColor(Color.Grey84) + .ColonColor(Color.Grey84) + .CommaColor(Color.Grey84) + .StringColor(Color.LightSalmon3_1) + .NumberColor(Color.DarkSeaGreen3_1) + .BooleanColor(Color.SkyBlue3) + .NullColor(Color.SkyBlue3) + .MemberColor(Color.SteelBlue1_1) ) .Header("JSON") - .Collapse() + .Expand() + .Padding(2, 1) + .RoundedBorder() + .BorderColor(Color.Yellow)); + } + catch (Exception ex) + { + Console.WriteLine($"Ошибка: {ex.Message}"); + } +} +static async Task TryRetrieve(Task action) where T : StreamResponse +{ + try + { + var response = await action; + using var reader = new StreamReader(response.ResponseStream); + // Исключительно для текстовых файлов + var text = await reader.ReadToEndAsync(); + Console.WriteLine("Загрузка выполнена успешно. Содержимое объекта: \n"); + AnsiConsole.Write( + new Panel(new Padder(new Text(text)).PadBottom(1).PadTop(1)) + .Header("Объект S3") + .Expand() .RoundedBorder() .BorderColor(Color.Yellow)); } From d6f507c215b8fbd91fe6d5e52df586a07959ebc2 Mon Sep 17 00:00:00 2001 From: axtox Date: Sat, 7 Jun 2025 05:31:59 +0300 Subject: [PATCH 08/20] Refactor S3 client creation: validate S3 settings using IsValid method --- dotnet/src/sample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index acf24ba..d775a37 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -108,7 +108,7 @@ static AmazonS3Client CreateS3Client(S3Settings? s3Settings) { if (s3Settings == null) throw new ArgumentNullException(nameof(s3Settings), "Настройки S3 не могут быть пустыми."); - if (string.IsNullOrEmpty(s3Settings.AccessKey) || string.IsNullOrEmpty(s3Settings.SecretKey)) + if (!s3Settings.IsValid()) throw new ArgumentException("Все поля в настройках S3 должны быть заполнены, включая AccessKey, SecretKey, BucketName и ServiceUrl."); var config = new AmazonS3Config From 8c4c401c5896f2bae597d8e79a91806432540346 Mon Sep 17 00:00:00 2001 From: axtox Date: Sat, 7 Jun 2025 07:02:13 +0300 Subject: [PATCH 09/20] Add Docker support for .NET sample: create tasks for building and running Docker images, and add .dockerignore --- .vscode/tasks.json | 35 ++++++++++++++++++++++++++++++-- dotnet/.dockerignore | 1 + dotnet/Dockerfile | 18 +++++++++++++++++ dotnet/README.md | 44 +++++++++++++++++++++++++++++++++-------- dotnet/src/.env.example | 12 ++++++----- 5 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 dotnet/.dockerignore create mode 100644 dotnet/Dockerfile diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8dafa04..4215c5d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,9 +2,10 @@ "version": "2.0.0", "tasks": [ { - "label": "Run .NET Sample", - "command": "dotnet", + "label": ".NET Sample. Local: Run .NET Sample Locally", + "detail": "Run a .NET sample application locally. Requires .NET SDK 10 to be installed.", "type": "shell", + "command": "dotnet", "args": ["run", "sample.cs"], "options": { "cwd": "${workspaceFolder}/dotnet/src" @@ -12,6 +13,36 @@ "group": { "kind": "build" } + }, + { + "label": ".NET Sample. Docker: Build Docker Image", + "detail": "Build a Docker image for the .NET sample application. Requires Docker to be installed.", + "type": "docker-build", + "dockerBuild": { + "context": "${workspaceFolder}/dotnet/src", + "dockerfile": "${workspaceFolder}/dotnet/Dockerfile", + "tag": "dotnet-s3-example:latest" + } + }, + { + "label": ".NET Sample. Docker: Build an Image and Run Container", + "detail": "Run a .NET sample application in a Docker container. Requires Docker to be installed.", + "type": "process", + "command": "docker", + "args": [ + "run", + "--rm", + "-it", + "--name", + "dotnet-s3-example", + "--env-file", + "${workspaceFolder}/dotnet/src/.env", + "dotnet-s3-example:latest" + ], + "dependsOn": ".NET Sample. Docker: Build Docker Image", + "group": { + "kind": "build" + } } ] } \ No newline at end of file diff --git a/dotnet/.dockerignore b/dotnet/.dockerignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/dotnet/.dockerignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/dotnet/Dockerfile b/dotnet/Dockerfile new file mode 100644 index 0000000..56ecd0b --- /dev/null +++ b/dotnet/Dockerfile @@ -0,0 +1,18 @@ +# Stage 1: Convert to regular project and build the app +FROM mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine AS build +WORKDIR /src + +# convert the sample to a regular project to reduce image size +COPY sample.cs . +RUN dotnet project convert sample.cs + +RUN dotnet publish sample/ \ + -c Release + +# Stage 2: Use ~<100mb image and run the app +FROM mcr.microsoft.com/dotnet/runtime:10.0-preview-alpine AS runtime +WORKDIR /app + +COPY --from=build /src/sample/bin/Release/net10.0/publish . + +ENTRYPOINT ["./sample"] \ No newline at end of file diff --git a/dotnet/README.md b/dotnet/README.md index 68a0b01..e6ab52c 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,17 +1,25 @@ # Использование dotnet-sdk Для Работы с S3 Хранилищем Timeweb Cloud ## Описание > [!WARNING] -> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку в Timeweb не поддерживает самые последние версии клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), `nullable` поля и др. +> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку в Timeweb не поддерживает самые последние версии .NET клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), `nullable` поля и др. Приложение показывает использование S3 хранилища Timeweb Cloud в `dotnet`, при запуске будут выполнены следующие команды: 1. Создание бакета (если уже существует - вернет детали существующего бакета) -2. Регион бакета -3. Список бакетов +1. Регион бакета +1. Список бакетов +1. Отправка текста в файл +1. Отправка файла +1. Получение списка объектов +1. Скачивание объекта +1. Получение метаданных объекта +1. Копирование файла +1. Удаление всех объектов +1. Удаление бакета > [!CAUTION] > Во время выполнения этой программы будет выполнено удаление бакета и всех файлов в нем! Будьте аккуратны когда указываете имя существующего бакета в `.env` -При успешном завершении операции будет выведен ответ от S3 API в формате JSON, при неудаче - детали ошибки. +При успешном завершении операции будет выведен ответ от S3 API в формате JSON, при неудаче - детали ошибки. Если в качестве ответа приходит файл, будет выведено его содержимое (поддерживаются только текстовые файлы). В качестве клиента к S3 хранилищем Timeweb Cloud используется официальный SDK от Amazon (`AWS.SDK`) версии `3.5.10.2`, ендпоинт Timeweb Cloud S3 API `https://s3.twcstorage.ru` В [sample.cs](./src/sample.cs) описаны основные ендпоинты для работы с API, для запуска потребуется переименовать [.env.example](./src/.env.example) в `.env` и вписать настоящие данные для подключения к S3 хранилищу Timeweb Cloud, которые можно найти во вкладке `Дашборд` в разделе `Хранилище S3` в личном кабинете Timeweb Cloud. @@ -22,18 +30,38 @@ - Linux/Windows/macOS - dotnet 10/Docker ### Запуск через CLI -Для локального запуска требуется установка [dotnet SDK версии >10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +Для локального запуска требуется установка [dotnet SDK версии >=10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) и заполненный `.env` файл. После установки достаточно выполнить эту команду в любой консоли (убедитесь что выполняете команду из папки ./dotnet/src): ```shell dotnet run sample.cs ``` + +### Запуск через Docker +Для запуска и сборки через Docker требуется установка [Docker](https://www.docker.com/) и заполненный `.env` файл. + +В проекте присутствует [Dockerfile](./Dockerfile) с двумя стейджами для уменьшения размера docker image: +**Stage 1**: Использует dotnet 10.0 SDK и конвертирует файл [sample.cs](./src/sample.cs) в классический формат проекта dotnet чтобы иметь возможность собрать исполняемый файл (пока что для direct file запуска требуется SDK, он довольно тяжеловесный). Из-за требований S3 библиотеки к функциям рефлексии, ради экономии времени было решено не идти по пути AOT (размер докер образа можно было бы уменьшить до ~<10mb) +**Stage 2**: Копирует компилированные файлы в исполняемый образ и создает `ENTRYPOINT` + +Для сборки проекта достаточно выполнить следующую CLI команду, находясь в папке `./dotnet`: +```shell +docker build -t dotnet-s3-example:latest ./src +``` + +Для запуска приложения после сборки: +```shell +docker run --rm -it --name dotnet-s3-example --env-file ./src/.env dotnet-s3-example:latest +``` + ### Запуск через Visual Studio Code > [!NOTE] > Так как приложение писалось когда .NET 10 был еще в Preview 4, запуск через F5 с дебаггером на тот момент не был реализован для direct file приложений, поэтому опция с дебагом отсутствует так же как и файл launch.json -Все ёще требуется установка [dotnet SDK версии >10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) +Для Visual Studio Code поддерживаются 2 варианта запуска: +1. [Локальный запуск](#запуск-через-cli) +2. [Запуск через Docker](#запуск-через-docker) -Для удобства в проекте создан файл `tasks.json` благодаря которому программу можно запустить через сочетание клавиш `CTRL + SHIFT + B` для Windows и `CMD + SHIFT + B` для MacOS -> `Run .NET Sample` +Так как это просто шорткаты для двух описанных выше вариантов запуска, при выборе определенного варианта все требования необходимо удовлетворить. -### Запуск через Docker \ No newline at end of file +Для удобства в проекте создан файл `tasks.json` благодаря которому программу можно запустить через сочетание клавиш `CTRL + SHIFT + B` для Windows и `CMD + SHIFT + B` для MacOS -> `Run .NET Sample` \ No newline at end of file diff --git a/dotnet/src/.env.example b/dotnet/src/.env.example index 24a515d..a59fbde 100644 --- a/dotnet/src/.env.example +++ b/dotnet/src/.env.example @@ -1,9 +1,11 @@ # Данные для подключения к S3 хранилищу Timeweb Cloud # можно найти во вкладке `Дашборд` в разделе `Хранилище S3` # в личном кабинете Timeweb Cloud. -S3__SERVICEURL="https://s3.twcstorage.ru" -S3__REGION="ru-1" + +# Ковычки в значениях использовать не следует, так как они плохо распознаются командой docker run. +S3__SERVICEURL=https://s3.twcstorage.ru +S3__REGION=ru-1 # Имя нового или существующего бакета, лучше всего работает UUID -S3__BUCKETNAME="" -S3__ACCESSKEY="" -S3__SECRETKEY="" \ No newline at end of file +S3__BUCKETNAME= +S3__ACCESSKEY= +S3__SECRETKEY= \ No newline at end of file From 87a1db3f389776973081c750c0fdd20bc62eac5f Mon Sep 17 00:00:00 2001 From: axtox Date: Sun, 8 Jun 2025 04:29:06 +0300 Subject: [PATCH 10/20] Update README.md: change shell to bash for code blocks and improve Docker section formatting --- dotnet/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index e6ab52c..efc4c17 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -33,24 +33,25 @@ Для локального запуска требуется установка [dotnet SDK версии >=10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) и заполненный `.env` файл. После установки достаточно выполнить эту команду в любой консоли (убедитесь что выполняете команду из папки ./dotnet/src): -```shell +```bash dotnet run sample.cs ``` ### Запуск через Docker Для запуска и сборки через Docker требуется установка [Docker](https://www.docker.com/) и заполненный `.env` файл. -В проекте присутствует [Dockerfile](./Dockerfile) с двумя стейджами для уменьшения размера docker image: -**Stage 1**: Использует dotnet 10.0 SDK и конвертирует файл [sample.cs](./src/sample.cs) в классический формат проекта dotnet чтобы иметь возможность собрать исполняемый файл (пока что для direct file запуска требуется SDK, он довольно тяжеловесный). Из-за требований S3 библиотеки к функциям рефлексии, ради экономии времени было решено не идти по пути AOT (размер докер образа можно было бы уменьшить до ~<10mb) +В проекте присутствует [Dockerfile](./Dockerfile) с двумя стейджами для уменьшения размера docker image: + +**Stage 1**: Использует dotnet 10.0 SDK и конвертирует файл [sample.cs](./src/sample.cs) в классический формат проекта dotnet чтобы иметь возможность собрать исполняемый файл (пока что для direct file запуска требуется SDK, он довольно тяжеловесный). Из-за требований S3 библиотеки к функциям рефлексии, ради экономии времени было решено не идти по пути AOT (размер докер образа можно было бы уменьшить до ~<10mb) **Stage 2**: Копирует компилированные файлы в исполняемый образ и создает `ENTRYPOINT` Для сборки проекта достаточно выполнить следующую CLI команду, находясь в папке `./dotnet`: -```shell +```bash docker build -t dotnet-s3-example:latest ./src ``` Для запуска приложения после сборки: -```shell +```bash docker run --rm -it --name dotnet-s3-example --env-file ./src/.env dotnet-s3-example:latest ``` From 4d12f274f07425bb1f1863ea6c189c4bc9986e0b Mon Sep 17 00:00:00 2001 From: axtox Date: Sun, 8 Jun 2025 04:31:37 +0300 Subject: [PATCH 11/20] Update README.md: change code block syntax from bash to console for clarity --- dotnet/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index efc4c17..fb0b557 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -33,7 +33,7 @@ Для локального запуска требуется установка [dotnet SDK версии >=10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) и заполненный `.env` файл. После установки достаточно выполнить эту команду в любой консоли (убедитесь что выполняете команду из папки ./dotnet/src): -```bash +```console dotnet run sample.cs ``` @@ -46,12 +46,12 @@ dotnet run sample.cs **Stage 2**: Копирует компилированные файлы в исполняемый образ и создает `ENTRYPOINT` Для сборки проекта достаточно выполнить следующую CLI команду, находясь в папке `./dotnet`: -```bash +```console docker build -t dotnet-s3-example:latest ./src ``` Для запуска приложения после сборки: -```bash +```console docker run --rm -it --name dotnet-s3-example --env-file ./src/.env dotnet-s3-example:latest ``` From e8e27927d46ae0ff9ff4f9bdc43cd62ba105a235 Mon Sep 17 00:00:00 2001 From: axtox Date: Sun, 8 Jun 2025 15:03:52 +0300 Subject: [PATCH 12/20] Add .NET example link to main README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cd06801..7e6502c 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,4 @@ - [Node.js](https://github.com/timeweb-cloud/s3-examples/tree/master/nodejs) - [Python](https://github.com/timeweb-cloud/s3-examples/tree/master/python3) - [PHP](https://github.com/timeweb-cloud/s3-examples/tree/master/php) +- [.NET](./dotnet/README.md) From bbe474cc980ce870ecdef1ca9c9d44244ce3379c Mon Sep 17 00:00:00 2001 From: axtox Date: Sun, 8 Jun 2025 15:04:13 +0300 Subject: [PATCH 13/20] Refactor S3 settings loading: encapsulate logic in LoadS3Settings method and improve validation in CreateS3Client --- dotnet/src/sample.cs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index d775a37..bbc6afc 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -16,12 +16,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -Env.Load(); -var builder = new ConfigurationBuilder() - .AddEnvironmentVariables(); -var configuration = builder.Build(); -var settings = configuration.GetSection("S3").Get()!; - +var settings = LoadS3Settings(); var s3Client = CreateS3Client(settings); Console.WriteLine("S3 Клиент успешно создан и готов к работе."); @@ -104,13 +99,8 @@ await TryExecute( await TryExecute( s3Client.DeleteBucketAsync(new DeleteBucketRequest { BucketName = settings.BucketName })); -static AmazonS3Client CreateS3Client(S3Settings? s3Settings) +static AmazonS3Client CreateS3Client(S3Settings s3Settings) { - if (s3Settings == null) - throw new ArgumentNullException(nameof(s3Settings), "Настройки S3 не могут быть пустыми."); - if (!s3Settings.IsValid()) - throw new ArgumentException("Все поля в настройках S3 должны быть заполнены, включая AccessKey, SecretKey, BucketName и ServiceUrl."); - var config = new AmazonS3Config { ServiceURL = s3Settings.ServiceUrl, @@ -121,6 +111,24 @@ static AmazonS3Client CreateS3Client(S3Settings? s3Settings) return client; } +static S3Settings LoadS3Settings() +{ + Env.Load(); + + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + var settings = configuration.GetSection("S3").Get(); + + if (settings == null) + throw new ArgumentNullException(nameof(settings), "Настройки S3 не могут быть пустыми."); + if (!settings.IsValid()) + throw new ArgumentException("Все поля в настройках S3 должны быть заполнены, включая AccessKey, SecretKey, BucketName и ServiceUrl."); + + return settings; +} + #region static async Task TryExecute(Task action) where T : AmazonWebServiceResponse { From 24c3406f5731c4af3919963c969ab37c7e2326c9 Mon Sep 17 00:00:00 2001 From: axtox Date: Sun, 8 Jun 2025 15:08:20 +0300 Subject: [PATCH 14/20] Update .NET link in README.md to point to the directory instead of README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e6502c..16d24c8 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,4 @@ - [Node.js](https://github.com/timeweb-cloud/s3-examples/tree/master/nodejs) - [Python](https://github.com/timeweb-cloud/s3-examples/tree/master/python3) - [PHP](https://github.com/timeweb-cloud/s3-examples/tree/master/php) -- [.NET](./dotnet/README.md) +- [.NET](./dotnet) From 201ca63d103640e9666d0c279c9476e15c155705 Mon Sep 17 00:00:00 2001 From: axtox Date: Wed, 18 Jun 2025 15:29:37 +0300 Subject: [PATCH 15/20] Errors will now show stacktrace and inner exceptions --- dotnet/src/sample.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index bbc6afc..0369b1d 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -164,7 +164,7 @@ static async Task TryExecute(Task action) where T : AmazonWebServiceRespon } catch (Exception ex) { - Console.WriteLine($"Ошибка: {ex.Message}"); + Console.WriteLine($"Ошибка: {ex.Message}\n{ex.StackTrace}\nВложенная ошибка: {ex.InnerException?.Message}\n{ex.InnerException?.StackTrace}"); } } static async Task TryRetrieve(Task action) where T : StreamResponse @@ -185,7 +185,7 @@ static async Task TryRetrieve(Task action) where T : StreamResponse } catch (Exception ex) { - Console.WriteLine($"Ошибка: {ex.Message}"); + Console.WriteLine($"Ошибка: {ex.Message}\n{ex.StackTrace}\nВложенная ошибка: {ex.InnerException?.Message}\n{ex.InnerException?.StackTrace}"); } } From 582adef2b4acc18b6052e4933f1c356f6dbb14c3 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 20 Jun 2025 04:47:32 +0300 Subject: [PATCH 16/20] Region was missing in the s3 config --- dotnet/src/sample.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 0369b1d..620e3e7 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -104,7 +104,8 @@ static AmazonS3Client CreateS3Client(S3Settings s3Settings) var config = new AmazonS3Config { ServiceURL = s3Settings.ServiceUrl, - ForcePathStyle = true + ForcePathStyle = true, + AuthenticationRegion = s3Settings.Region }; var credentials = new BasicAWSCredentials(s3Settings.AccessKey, s3Settings.SecretKey); var client = new AmazonS3Client(credentials, config); From 147a91d28b1b9447e82f54118f945216519f9d11 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 20 Jun 2025 06:25:59 +0300 Subject: [PATCH 17/20] Added Timeweb S3 compatibility issue in Readme --- dotnet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/README.md b/dotnet/README.md index fb0b557..fdec4e0 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,7 +1,7 @@ # Использование dotnet-sdk Для Работы с S3 Хранилищем Timeweb Cloud ## Описание > [!WARNING] -> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку в Timeweb не поддерживает самые последние версии .NET клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), `nullable` поля и др. +> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку Timeweb S3 не поддерживает самые последние версии .NET клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), валидацией checksum (DefaultChecksumValidation), `nullable` поля и др. Приложение показывает использование S3 хранилища Timeweb Cloud в `dotnet`, при запуске будут выполнены следующие команды: 1. Создание бакета (если уже существует - вернет детали существующего бакета) From e154c3e534120441009f39ec42b488b872759ce6 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 27 Jun 2025 05:04:06 +0300 Subject: [PATCH 18/20] AWS SDK updated to 4.0.2 with remarks --- dotnet/README.md | 4 ++-- dotnet/src/sample.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index fdec4e0..23f8b94 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -1,7 +1,7 @@ # Использование dotnet-sdk Для Работы с S3 Хранилищем Timeweb Cloud ## Описание > [!WARNING] -> Версия NuGet пакета `AWS.SDK` должна быть `<=3.5.10.2`, поскольку Timeweb S3 не поддерживает самые последние версии .NET клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), валидацией checksum (DefaultChecksumValidation), `nullable` поля и др. +> Версия NuGet пакета `AWS.SDK` в примере `<=4.0.2`, однако, на Июнь 2025 года она не работает полноценно с методами `PutObject` (для работы `PutObject` необходимо либо установить флаг `DisableDefaultChecksumValidation` в `true` во время создания запроса `PutObjectRequest`, либо использовать версию SDK `<=3.5.10.2`), поскольку Timeweb S3 пока что не поддерживает самые последние версии .NET клиентов. Предположительно, это связано с обновлением не самого S3 API, который так и остался в версии `2006-03-01`, но с новыми требованиями к сети (HTTP/2), валидацией checksum (DefaultChecksumValidation), `nullable` поля и др. Приложение показывает использование S3 хранилища Timeweb Cloud в `dotnet`, при запуске будут выполнены следующие команды: 1. Создание бакета (если уже существует - вернет детали существующего бакета) @@ -21,7 +21,7 @@ При успешном завершении операции будет выведен ответ от S3 API в формате JSON, при неудаче - детали ошибки. Если в качестве ответа приходит файл, будет выведено его содержимое (поддерживаются только текстовые файлы). -В качестве клиента к S3 хранилищем Timeweb Cloud используется официальный SDK от Amazon (`AWS.SDK`) версии `3.5.10.2`, ендпоинт Timeweb Cloud S3 API `https://s3.twcstorage.ru` +В качестве клиента к S3 хранилищем Timeweb Cloud используется официальный SDK от Amazon (`AWS.SDK`) версии `4.0.2`, ендпоинт Timeweb Cloud S3 API `https://s3.twcstorage.ru` В [sample.cs](./src/sample.cs) описаны основные ендпоинты для работы с API, для запуска потребуется переименовать [.env.example](./src/.env.example) в `.env` и вписать настоящие данные для подключения к S3 хранилищу Timeweb Cloud, которые можно найти во вкладке `Дашборд` в разделе `Хранилище S3` в личном кабинете Timeweb Cloud. Больше информации об объектном S3 хранилище Timeweb Cloud в [документации](https://timeweb.cloud/docs/s3-storage). diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 620e3e7..8115522 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -1,4 +1,4 @@ -#:package AWSSDK.S3@3.5.10.2 +#:package AWSSDK.S3@4.0.2 #:package Microsoft.Extensions.Configuration.EnvironmentVariables@9.0.5 #:package Microsoft.Extensions.Configuration.Binder@9.0.5 #:package DotNetEnv@3.1.1 @@ -38,7 +38,9 @@ await TryExecute( { BucketName = settings.BucketName, Key = "example-from-text.txt", - ContentBody = "Привет, это файл созданный из текста!" + ContentBody = "Привет, это файл созданный из текста!", + // флаг для обратной совместимостью с Timeweb S3, убрать когда Timeweb S3 актуализируется + DisableDefaultChecksumValidation = true })); Console.WriteLine($"Отправка нового текстового файла."); @@ -49,7 +51,9 @@ await TryExecute( { BucketName = settings.BucketName, Key = "example-from-file.txt", - InputStream = file + InputStream = file, + // флаг для обратной совместимостью с Timeweb S3, убрать когда Timeweb S3 актуализируется + DisableDefaultChecksumValidation = true })); Console.WriteLine($"Получение списка объектов в бакете {settings.BucketName}."); From a2d66924ce33d0b3d3196c092af4391e18912933 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 27 Jun 2025 05:06:46 +0300 Subject: [PATCH 19/20] S3 Object tagging example added --- dotnet/README.md | 11 +++++++---- dotnet/src/sample.cs | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index 23f8b94..f36c195 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -9,11 +9,14 @@ 1. Список бакетов 1. Отправка текста в файл 1. Отправка файла -1. Получение списка объектов -1. Скачивание объекта -1. Получение метаданных объекта +1. Получение списка файловы +1. Скачивание файла +1. Получение метаданных файла 1. Копирование файла -1. Удаление всех объектов +1. Присвоение тега файлу +1. Получение информации о тегах в файле +1. Удаление всех тегов файла +1. Удаление всех файлов 1. Удаление бакета > [!CAUTION] diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index 8115522..ee23747 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -86,6 +86,34 @@ await TryExecute( DestinationKey = "example-from-text-copy.txt" })); +Console.WriteLine("Присвоение тега `deleted=true` файлу `example-from-text-copy.txt`"); +await TryExecute( + s3Client.PutObjectTaggingAsync(new PutObjectTaggingRequest + { + BucketName = settings.BucketName, + Tagging = new Tagging { TagSet = [new Tag { Key = "deleted", Value = "true" }] }, + Key = "example-from-text-copy.txt" + }) +); + +Console.WriteLine("Получение информации о тегах присвоенных файлу `example-from-text-copy.txt`"); +await TryExecute( + s3Client.GetObjectTaggingAsync(new GetObjectTaggingRequest + { + BucketName = settings.BucketName, + Key = "example-from-text-copy.txt" + }) +); + +Console.WriteLine("Удаление всех тегов присвоенных файлу `example-from-text-copy.txt`"); +await TryExecute( + s3Client.DeleteObjectTaggingAsync(new DeleteObjectTaggingRequest + { + BucketName = settings.BucketName, + Key = "example-from-text-copy.txt" + }) +); + Console.WriteLine($"Удаление всех объектов."); await TryExecute( s3Client.DeleteObjectsAsync(new DeleteObjectsRequest From 026fb1880ffa5899ac7fc694632192ff9e416794 Mon Sep 17 00:00:00 2001 From: axtox Date: Fri, 27 Jun 2025 06:03:06 +0300 Subject: [PATCH 20/20] S3 Object Lifecycle example added --- dotnet/README.md | 3 ++ dotnet/src/sample.cs | 85 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/dotnet/README.md b/dotnet/README.md index f36c195..e18594d 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -15,7 +15,10 @@ 1. Копирование файла 1. Присвоение тега файлу 1. Получение информации о тегах в файле +1. Создание правил жизненного цикла файлов (правила по тегу, по названию папок) +1. Получение правил жизненного цикла файлов 1. Удаление всех тегов файла +1. Удаление правил жизненного цикла файлов 1. Удаление всех файлов 1. Удаление бакета diff --git a/dotnet/src/sample.cs b/dotnet/src/sample.cs index ee23747..7e59f86 100644 --- a/dotnet/src/sample.cs +++ b/dotnet/src/sample.cs @@ -15,6 +15,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; var settings = LoadS3Settings(); var s3Client = CreateS3Client(settings); @@ -105,6 +106,53 @@ await TryExecute( }) ); +Console.WriteLine("Создание правил жизненного цикла файлов: " + + "правила по тегу `deleted=true`, по названию папок `deleted/`"); +LifecycleRule[] rules = +[ + new() + { + Id = "DeletedTagRetention", + Status = LifecycleRuleStatus.Enabled, + Filter = new LifecycleFilter + { + LifecycleFilterPredicate = new LifecycleTagPredicate + { + Tag = new Tag { Key = "deleted", Value = "true" } + } + }, + Expiration = new LifecycleRuleExpiration { Days = 1 } + }, + new() + { + Id = "DeletedFolderRetention", + Status = LifecycleRuleStatus.Enabled, + Filter = new LifecycleFilter + { + LifecycleFilterPredicate = new LifecyclePrefixPredicate { Prefix = "deleted/" } + }, + Expiration = new LifecycleRuleExpiration { Days = 1 } + } +]; +await TryExecute( + s3Client.PutLifecycleConfigurationAsync(new PutLifecycleConfigurationRequest + { + BucketName = settings.BucketName, + Configuration = new LifecycleConfiguration + { + Rules = [.. rules] + } + }) +); + +Console.WriteLine("Получение правил жизненного цикла файлов"); +await TryExecute( + s3Client.GetLifecycleConfigurationAsync(new GetLifecycleConfigurationRequest + { + BucketName = settings.BucketName + }) +); + Console.WriteLine("Удаление всех тегов присвоенных файлу `example-from-text-copy.txt`"); await TryExecute( s3Client.DeleteObjectTaggingAsync(new DeleteObjectTaggingRequest @@ -114,6 +162,14 @@ await TryExecute( }) ); +Console.WriteLine("Удаление всех правил жизненного цикла файлов"); +await TryExecute( + s3Client.DeleteLifecycleConfigurationAsync(new DeleteLifecycleConfigurationRequest + { + BucketName = settings.BucketName + }) +); + Console.WriteLine($"Удаление всех объектов."); await TryExecute( s3Client.DeleteObjectsAsync(new DeleteObjectsRequest @@ -162,7 +218,7 @@ static S3Settings LoadS3Settings() return settings; } -#region +#region Рендеринг и обработка ошибок static async Task TryExecute(Task action) where T : AmazonWebServiceResponse { try @@ -172,7 +228,28 @@ static async Task TryExecute(Task action) where T : AmazonWebServiceRespon { WriteIndented = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault, + //твик чтобы показать информацию про правила жизненного цикла объектов + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = + { + ti => + { + if (ti.Type == typeof(LifecycleFilterPredicate)) + { + ti.PolymorphismOptions = new JsonPolymorphismOptions + { + DerivedTypes = + { + new JsonDerivedType(typeof(LifecycleTagPredicate), "tagPredicate"), + new JsonDerivedType(typeof(LifecyclePrefixPredicate), "prefixPredicate") + } + }; + } + } + } + } }); Console.WriteLine($"Операция выполнена успешно. Результат: \n"); // JSON стайлинг в формате Visual Studio Code @@ -221,6 +298,7 @@ static async Task TryRetrieve(Task action) where T : StreamResponse Console.WriteLine($"Ошибка: {ex.Message}\n{ex.StackTrace}\nВложенная ошибка: {ex.InnerException?.Message}\n{ex.InnerException?.StackTrace}"); } } +#endregion /// /// Класс для хранения настроек подключения к Amazon S3. @@ -246,5 +324,4 @@ public bool IsValid() !string.IsNullOrEmpty(ServiceUrl) && !string.IsNullOrEmpty(BucketName) && !string.IsNullOrEmpty(Region); -} -#endregion \ No newline at end of file +} \ No newline at end of file