Быстрая настройка S3 хранилища от Selectel в ASP.NET

Введение

Эта статья предназначена для быстрой и простой настройки работы с S3-хранилищем от Selectel. Мы также обсудим пару важных аспектов, которые часто не рассматриваются в других материалах по этой теме.

Настройка Selectel

Чтобы начать работу, нужно пополнить баланс на 100 рублей. Это даст возможность пользоваться объектным хранилищем.
Вот как его найти:

Быстрая настройка S3 хранилища от Selectel в ASP.NET

Теперь необходимо создать хранилище, сервер выбираем Санкт-Петербург, устанавливаем тип сервера на «публичный». Больше никаких галочек не трогаем — просто добавляем название и создаем.

Быстрая настройка S3 хранилища от Selectel в ASP.NET

После переходим по такому пути:
Аккаунт -> Пользователи -> Сервисные Пользователи

Здесь создаем нового пользователя и в правах указываем "Администратор объектного хранилища".

Быстрая настройка S3 хранилища от Selectel в ASP.NET

Далее нам нужно сгенерировать PUBLIC и SECRET KEY, чтобы через созданного пользователя управлять хранилищем. Для этого нажмите на имя пользователя, чтобы открыть его настройки, и сгенерируйте ключи.

⚠СРАЗУ СОХРАНИТЕ КЛЮЧИ, ЧТОБЫ НЕ ПОТЕРЯТЬ⚠

Быстрая настройка S3 хранилища от Selectel в ASP.NET

На этом базовая настройка в Selectel заканчивается. Если вдруг что-то осталось непонятным по скринам и описанию, вы можете посмотреть видео на YouTube, где подробно рассказывается о преимуществах использования объектного хранилища и о том, как его настроить с примерами (на Python 🙃).

S3 хранилище — Лучший способ хранить файлы на бэкенде | Как работать с S3 через Python. Автор - Артём Шумейко

Работаем с хранилищем с помощью AWS S3 в ASP.NET

Для начала необходимо установить через NuGet библиотеки AWSSDK.S3 и AWSSDK.Extensions.NETCore.Setup, а также отредактировать файл appsettings.json.

В этом файле нужно указать URL, по которому мы будем обращаться к серверу, ключи, имя контейнера (например, test-bucket) и регион.

{ "S3Storage": { "ServiceURL": "https://s3.storage.selcloud.ru", "AccessKey": "", "SecretKey": "", "BucketName": "", "Region": "ru-1" }, "AllowedHosts": "*" }

Настроим DI, добавив данные строчки кода в Program.cs

builder.Services.AddDefaultAWSOptions(builder.Configuration.GetAWSOptions()); builder.Services.AddAWSService<IAmazonS3>();

Далее необходимо создать end points и слой service для работы с S3 хранилищем. Не забудьте добавить конструктор у сервиса.

private readonly IAmazonS3 _s3Client; private readonly string _bucketName; public SelectelStorageService(IConfiguration configuration) { var config = new AmazonS3Config { ServiceURL = configuration["S3Storage:ServiceURL"], ForcePathStyle = true, AuthenticationRegion = configuration["S3Storage:Region"] }; _s3Client = new AmazonS3Client( configuration["S3Storage:AccessKey"], configuration["S3Storage:SecretKey"], config ); _bucketName = configuration["S3Storage:BucketName"]; }

А вот и первый метод для сохранения файлов в хранилище. Чтобы передать файлы, нам необходимо конвертировать их в поток MemoryStream, а затем передать в PutObjectRequest.

public async Task<string> UploadFile(IFormFile file, string keyName) { using MemoryStream memoryStream = new MemoryStream(); file.CopyTo(memoryStream); // Копируем файл в поток var request = new PutObjectRequest { BucketName = _bucketName, Key = keyName, InputStream = memoryStream, // передаем поток }; await _s3Client.PutObjectAsync(request); return keyName; }

Тут очень важный момент: может возникнуть ситуация, когда код выше будет работать некорректно, и при загрузке файлов вы заметите, что они не открываются должным образом. Если вы, например, загрузите текстовый файл, то внутри увидите следующую картину. А ведь изначально в txt было записано — 123.

Страшная бяка
Страшная бяка

Дело в том, что данные отправляются порционно, и из-за этого прилетают "битыми". Чтобы исправить эту проблему надо добавить буквально одну строчку кода.

var request = new PutObjectRequest { BucketName = _bucketName, Key = keyName, InputStream = memoryStream, UseChunkEncoding = false //Да прибудет спаситель };

Теперь если мы попробуем загрузить загрузить картинку в хранилище и перейдем внутрь, увидим следующую картину. Если вы нажмете на стрелочку, то файл откроется в новом окне.

Домен можно использовать для доступа к остальным файлам, например при работе на фронте

Щикарно
Щикарно

Обычные файлы — это, конечно, хорошо, но что делать, если нам понадобится загрузить в облако файл объемом в несколько гигабайт? В этом случае нам потребуется иной подход к решению проблемы. Большой файл необходимо разбить на маленькие кусочки, отправить в хранилище, а уже там он будет собирать эти пакеты воедино. Для этого мы напишем отдельный метод.

Вместо PutObjectRequest мы используем TransferUtilityUploadRequest. Как видно, разницы почти никакой, только дополнительно указываем, сколько максимальной памяти будут занимать пакеты при дроблении, а также некоторые параметры доступа

PublicRead - файл сможет прочитать любой пользователь.

S3StorageClass.StandardInfrequentAccess - предназначен для данных, которые реже запрашиваются, но требуют более быстрого доступа, когда они необходимы.

public async Task<string> UploadBigFile(IFormFile file, string keyName, Action<int> progressCallback = null) { var fileTransferUtility = new TransferUtility(_s3Client); using MemoryStream memoryStream = new MemoryStream(); file.CopyTo(memoryStream); var fileTransferUtilityRequest = new TransferUtilityUploadRequest { BucketName = _bucketName, Key = keyName, InputStream = memoryStream, StorageClass = S3StorageClass.StandardInfrequentAccess, PartSize = 6291456, // 6 MB. CannedACL = S3CannedACL.PublicRead }; fileTransferUtilityRequest.UploadProgressEvent += (s, e) => { progressCallback?.Invoke(e.PercentDone); }; await fileTransferUtility.UploadAsync(fileTransferUtilityRequest); return keyName; }

Кусочек с UploadProgressEvent не обязателен, я его написал для красивого вывода в консоль progress bar. Если он вам не нужен удалите также и progressCallback.

fileTransferUtilityRequest.UploadProgressEvent += (s, e) => { progressCallback?.Invoke(e.PercentDone); };

Методы для удаления и скачивания файлов выглядят следующим образом. Тут ничего интересного.

public async Task<Stream> DownloadFileAsync(string keyName) { var request = new GetObjectRequest { BucketName = _bucketName, Key = keyName }; var response = await _s3Client.GetObjectAsync(request); return response.ResponseStream; } public async Task DeleteFileAsync(string keyName) { var request = new DeleteObjectRequest { BucketName = _bucketName, Key = keyName }; await _s3Client.DeleteObjectAsync(request); }

А вот и код контроллера. Я специально отдельно указал переменную path, так как в хранилище Selectel можно группировать файлы по папкам. Полный путь до файла определяется в переменной keyName.

Если директория не указана, путь будет выглядеть так: "file.txt", а если указана, то так: "text/file.txt". Также можно указать более глубокую вложенность, например: "documents/personal/text/file.txt".

[ApiController] [Route("api/storage")] public class StorageController : ControllerBase { private readonly SelectelStorageService _storageService; public StorageController(SelectelStorageService storageService) { _storageService = storageService; } [HttpPost("upload")] public async Task<IActionResult> UploadFile(IFormFile file, string path = "") { var keyName = string.IsNullOrEmpty(path) ? $"{Ulid.NewUlid()}{Path.GetExtension(file.FileName)}" : $"{path.TrimEnd('/')}/{Ulid.NewUlid()}{Path.GetExtension(file.FileName)}"; var result = await _storageService.UploadFile(file, keyName); return Ok(new { KeyName = result }); } [HttpPost("upload-big")] public async Task<IActionResult> UploadBigFile(IFormFile file, string path = "") { var keyName = string.IsNullOrEmpty(path) ? $"{Ulid.NewUlid()}{Path.GetExtension(file.FileName)}" : $"{path.TrimEnd('/')}/{Ulid.NewUlid()}{Path.GetExtension(file.FileName)}"; var result = await _storageService.UploadBigFile( file, keyName, progress => { Console.WriteLine($"Current progress: {progress}%"); } ); return Ok(new { KeyName = result }); } [HttpGet("download/{keyName}")] public async Task<IActionResult> DownloadFile(string keyName, string path = "") { var fullPath = string.IsNullOrEmpty(path) ? $"{keyName}" : $"{path.TrimEnd('/')}/{keyName}"; var stream = await _storageService.DownloadFileAsync($"{fullPath}"); return File(stream, "application/octet-stream", keyName); } [HttpDelete("{keyName}")] public async Task<IActionResult> DeleteFile(string keyName, string path= "") { var fullPath = string.IsNullOrEmpty(path) ? $"{keyName}" : $"{path.TrimEnd('/')}/{keyName}"; await _storageService.DeleteFileAsync($"{fullPath}"); return NoContent(); } }

Данный кусочек кода отвечает за вывод progress bar в консоль:

progress => { Console.WriteLine($"Current progress: {progress}%"); }

Попробуем загрузить картинку. Все работает.

Быстрая настройка S3 хранилища от Selectel в ASP.NET
Быстрая настройка S3 хранилища от Selectel в ASP.NET

Заключение

Мы рассмотрели ключевые аспекты, такие как загрузка файлов, обработка больших объемов данных и группировка по папкам. Надеюсь, эти советы будут полезны вам в вашей дальнейшей работе с файлами в облаке!

2
Начать дискуссию