Files
I150/srcs/RobotNet10/Commons/RobotNet10.StorageManager
2026-07-03 16:37:12 +07:00
..
2026-07-03 16:37:12 +07:00
2026-07-03 16:37:12 +07:00
2026-07-03 16:37:12 +07:00
2026-07-03 16:37:12 +07:00
2026-07-03 16:37:12 +07:00

RobotNet10.StorageManager

Thư viện quản lý lưu trữ file hỗ trợ cả Local File System và MinIO Object Storage. Được thiết kế để lưu trữ và quản lý các file đa dạng (ảnh, PDF, JSON, v.v.) với khả năng tự động detect file extension từ Content-Type.

📋 Mục lục

Tính năng

  • Dual Storage Support: Hỗ trợ cả Local File System và MinIO Object Storage
  • Multi-format Support: Tự động detect và hỗ trợ nhiều file types (PNG, JPG, PDF, JSON, TXT, v.v.)
  • Retry Logic: Tự động retry với exponential backoff cho MinIO operations
  • Path Security: Validate paths để tránh path traversal attacks
  • Additional Operations: Exists, List, Copy, GetMetadata
  • Backup Support: Tự động backup file cũ khi upload file mới (Local storage)
  • Presigned URLs: Hỗ trợ presigned URLs cho MinIO (24h expiry)

📦 Cài đặt

Thêm Project Reference

Thêm reference vào project của bạn:

<ProjectReference Include="..\Commons\RobotNet10.StorageManager\RobotNet10.StorageManager.csproj" />

Dependencies

Project này sử dụng:

  • Minio (Version 7.0.0) - Cho MinIO object storage support
  • .NET 10.0

⚙️ Cấu hình

StorageConfig

Tạo instance của StorageConfig để cấu hình storage:

using RobotNet10.StorageManager;

// Cấu hình cho Local Storage
var localConfig = new StorageConfig
{
    UsingLocal = true,
    LocalFolder = "MapImages",  // Thư mục lưu trữ local
    RetryCount = 3              // Số lần retry (mặc định: 3)
};

// Cấu hình cho MinIO Storage
var minioConfig = new StorageConfig
{
    UsingLocal = false,
    Bucket = "my-bucket",       // Tên bucket
    RetryCount = 3,
    MinioConfig = new MinioConfig
    {
        Endpoint = "localhost:9000",
        User = "minioadmin",
        Password = "minioadmin",
        EnableSSL = false
    }
};

MinioConfig Properties

Property Type Mô tả
Endpoint string MinIO server endpoint (ví dụ: "localhost:9000")
User string Access key / Username
Password string Secret key / Password
EnableSSL bool Bật/tắt SSL connection

StorageConfig Properties

Property Type Mô tả
UsingLocal bool true = Local storage, false = MinIO storage
LocalFolder string Tên thư mục cho local storage (mặc định: "Images")
Bucket string Tên bucket cho MinIO (required khi UsingLocal = false)
MinioConfig MinioConfig? Cấu hình MinIO (required khi UsingLocal = false)
RetryCount int Số lần retry cho MinIO operations (mặc định: 3)

🚀 Cách sử dụng

Khởi tạo StorageManager

using RobotNet10.StorageManager;

// Tạo config
var config = new StorageConfig
{
    UsingLocal = true,
    LocalFolder = "MapImages"
};

// Tạo StorageManager instance
var storageManager = new StorageManager(config);

// Nhớ dispose khi không dùng nữa
storageManager.Dispose();

Upload File

// Upload file từ Stream
var fileData = new MemoryStream(File.ReadAllBytes("map.png"));
await storageManager.UploadAsync(
    path: "maps/level1",
    objectName: "map_image",
    data: fileData,
    size: fileData.Length,
    contentType: "image/png",
    cancellationToken: CancellationToken.None
);

// Upload file từ FileStream
using var fileStream = new FileStream("document.pdf", FileMode.Open);
await storageManager.UploadAsync(
    path: "documents",
    objectName: "manual",
    data: fileStream,
    size: fileStream.Length,
    contentType: "application/pdf",
    cancellationToken: CancellationToken.None
);

Lưu ý: File extension sẽ được tự động detect từ contentType:

  • image/png.png
  • image/jpeg.jpg
  • application/pdf.pdf
  • application/json.json
  • Và nhiều format khác...

Get URL

// Local storage: Trả về file path
var url = await storageManager.GetUrlAsync("maps/level1", "map_image");
// Kết quả: "C:\...\MapImages\maps\level1\map_image.png"

// MinIO storage: Trả về presigned URL (24h expiry)
var url = await storageManager.GetUrlAsync("maps/level1", "map_image");
// Kết quả: "https://minio-server:9000/bucket/maps/level1/map_image?X-Amz-Algorithm=..."

Get File

// Lấy file dưới dạng Stream
using var fileStream = await storageManager.GetFileAsync(
    path: "maps/level1",
    objectName: "map_image",
    cancellationToken: CancellationToken.None
);

// Đọc dữ liệu từ stream
var buffer = new byte[fileStream.Length];
await fileStream.ReadAsync(buffer, 0, (int)fileStream.Length);

// Hoặc copy sang file khác
using var outputFile = new FileStream("output.png", FileMode.Create);
await fileStream.CopyToAsync(outputFile);

Lưu ý:

  • Local storage: Trả về FileStream đọc trực tiếp từ file system
  • MinIO storage: Trả về MemoryStream chứa toàn bộ nội dung file đã download
  • Quan trọng: Luôn sử dụng using statement để dispose stream sau khi sử dụng

Delete File

await storageManager.DeleteAsync(
    path: "maps/level1",
    objectName: "map_image",
    cancellationToken: CancellationToken.None
);

Check File Exists

var exists = await storageManager.ExistsAsync(
    path: "maps/level1",
    objectName: "map_image",
    cancellationToken: CancellationToken.None
);

if (exists)
{
    Console.WriteLine("File exists!");
}

List Files

// List files trong path (non-recursive)
var files = await storageManager.ListAsync(
    path: "maps",
    recursive: false,
    cancellationToken: CancellationToken.None
);
// Kết quả: ["level1/map_image.png", "level2/map_image.png"]

// List files recursive (bao gồm subfolders)
var allFiles = await storageManager.ListAsync(
    path: "maps",
    recursive: true,
    cancellationToken: CancellationToken.None
);
// Kết quả: ["level1/map_image.png", "level1/subfolder/other.png", "level2/map_image.png"]

Copy File

await storageManager.CopyAsync(
    sourcePath: "maps/level1",
    destPath: "maps/backup",
    objectName: "map_image",
    cancellationToken: CancellationToken.None
);

Get File Metadata

var metadata = await storageManager.GetMetadataAsync(
    path: "maps/level1",
    objectName: "map_image",
    cancellationToken: CancellationToken.None
);

Console.WriteLine($"Size: {metadata.Size} bytes");
Console.WriteLine($"ContentType: {metadata.ContentType}");
Console.WriteLine($"LastModified: {metadata.LastModified}");
Console.WriteLine($"ETag: {metadata.ETag}"); // Chỉ có cho MinIO

📚 API Reference

IStorageManager Interface

Task UploadAsync(string path, string objectName, Stream data, long size, string contentType, CancellationToken cancellationToken)

Upload file vào storage.

Parameters:

  • path: Đường dẫn/thư mục để lưu file
  • objectName: Tên file/object
  • data: Stream chứa dữ liệu file
  • size: Kích thước file (bytes)
  • contentType: MIME type của file (ví dụ: "image/png", "application/pdf")
  • cancellationToken: Cancellation token

Throws:

  • ArgumentNullException: Khi data hoặc config là null
  • ArgumentException: Khi path, objectName, contentType invalid hoặc size < 0
  • ArgumentException: Khi path chứa .. hoặc invalid characters

Task<string> GetUrlAsync(string path, string objectName)

Lấy URL/path của file.

Parameters:

  • path: Đường dẫn/thư mục
  • objectName: Tên file/object

Returns:

  • Local storage: File path (string)
  • MinIO storage: Presigned URL (string, 24h expiry)
  • Không tìm thấy: Empty string

Throws:

  • ArgumentException: Khi path hoặc objectName invalid

Task<Stream> GetFileAsync(string path, string objectName, CancellationToken cancellationToken)

Lấy file dưới dạng Stream để đọc nội dung.

Parameters:

  • path: Đường dẫn/thư mục
  • objectName: Tên file/object
  • cancellationToken: Cancellation token

Returns:

  • Local storage: FileStream đọc trực tiếp từ file system
  • MinIO storage: MemoryStream chứa toàn bộ nội dung file đã download

Throws:

  • ArgumentException: Khi path hoặc objectName invalid
  • FileNotFoundException: Khi file không tồn tại

Lưu ý: Luôn sử dụng using statement để dispose stream sau khi sử dụng xong.


Task DeleteAsync(string path, string objectName, CancellationToken cancellationToken)

Xóa file từ storage.

Parameters:

  • path: Đường dẫn/thư mục
  • objectName: Tên file/object
  • cancellationToken: Cancellation token

Throws:

  • ArgumentException: Khi path hoặc objectName invalid

Task<bool> ExistsAsync(string path, string objectName, CancellationToken cancellationToken)

Kiểm tra file có tồn tại không.

Parameters:

  • path: Đường dẫn/thư mục
  • objectName: Tên file/object
  • cancellationToken: Cancellation token

Returns:

  • true: File tồn tại
  • false: File không tồn tại

Throws:

  • ArgumentException: Khi path hoặc objectName invalid

Task<List<string>> ListAsync(string path, bool recursive, CancellationToken cancellationToken)

Liệt kê files trong path.

Parameters:

  • path: Đường dẫn/thư mục
  • recursive: true = bao gồm subfolders, false = chỉ files trong path hiện tại
  • cancellationToken: Cancellation token

Returns:

  • List các file paths (relative paths)

Throws:

  • ArgumentException: Khi path invalid

Task CopyAsync(string sourcePath, string destPath, string objectName, CancellationToken cancellationToken)

Copy file từ source path sang dest path.

Parameters:

  • sourcePath: Đường dẫn nguồn
  • destPath: Đường dẫn đích
  • objectName: Tên file/object
  • cancellationToken: Cancellation token

Throws:

  • ArgumentException: Khi sourcePath, destPath, hoặc objectName invalid

Task<StorageMetadata> GetMetadataAsync(string path, string objectName, CancellationToken cancellationToken)

Lấy metadata của file.

Parameters:

  • path: Đường dẫn/thư mục
  • objectName: Tên file/object
  • cancellationToken: Cancellation token

Returns:

  • StorageMetadata object chứa thông tin file

Throws:

  • ArgumentException: Khi path hoặc objectName invalid
  • FileNotFoundException: Khi file không tồn tại

StorageMetadata Class

public class StorageMetadata
{
    public string Path { get; set; }           // Full path của file
    public string ObjectName { get; set; }     // Tên file/object
    public long Size { get; set; }             // Kích thước file (bytes)
    public string ContentType { get; set; }    // MIME type
    public DateTime? LastModified { get; set; } // Thời gian sửa đổi cuối
    public string? ETag { get; set; }          // ETag (chỉ có cho MinIO)
}

⚠️ Xử lý lỗi

StorageManager không catch exceptions - tất cả exceptions sẽ bubble up để caller xử lý:

try
{
    await storageManager.UploadAsync(path, objectName, data, size, contentType, ct);
}
catch (ArgumentNullException ex)
{
    // Handle null argument
    Console.WriteLine($"Null argument: {ex.Message}");
}
catch (ArgumentException ex)
{
    // Handle invalid argument
    Console.WriteLine($"Invalid argument: {ex.Message}");
}
catch (IOException ex)
{
    // Handle I/O errors (local storage)
    Console.WriteLine($"I/O error: {ex.Message}");
}
catch (Minio.Exceptions.MinioException ex)
{
    // Handle MinIO errors
    Console.WriteLine($"MinIO error: {ex.Message}");
}

Common Exceptions

Exception Nguyên nhân
ArgumentNullException Required parameter là null
ArgumentException Invalid path, objectName, hoặc config
FileNotFoundException File không tồn tại (GetMetadataAsync)
IOException I/O errors (local storage)
Minio.Exceptions.MinioException MinIO operation errors
InvalidOperationException Retry failed sau nhiều lần thử

💡 Best Practices

1. Sử dụng using statement

using var storageManager = new StorageManager(config);
// StorageManager sẽ tự động dispose khi out of scope

2. Validate input trước khi upload

if (string.IsNullOrWhiteSpace(fileName))
    throw new ArgumentException("FileName cannot be empty");

if (fileStream.Length == 0)
    throw new ArgumentException("File cannot be empty");

3. Sử dụng CancellationToken

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await storageManager.UploadAsync(path, objectName, data, size, contentType, cts.Token);

4. Xử lý large files

// Đối với large files, nên stream trực tiếp từ file
using var fileStream = new FileStream("large-file.pdf", FileMode.Open, FileAccess.Read);
await storageManager.UploadAsync(
    path: "documents",
    objectName: "large-file",
    data: fileStream,
    size: fileStream.Length,
    contentType: "application/pdf",
    cancellationToken: CancellationToken.None
);

5. Check file exists trước khi operations

if (await storageManager.ExistsAsync(path, objectName, ct))
{
    var metadata = await storageManager.GetMetadataAsync(path, objectName, ct);
    Console.WriteLine($"File size: {metadata.Size} bytes");
}

6. Sử dụng ListAsync với recursive cẩn thận

// Với large directories, recursive listing có thể chậm
// Nên sử dụng non-recursive nếu có thể
var files = await storageManager.ListAsync(path, recursive: false, ct);

7. Configure RetryCount phù hợp

var config = new StorageConfig
{
    UsingLocal = false,
    Bucket = "my-bucket",
    RetryCount = 5,  // Tăng retry count cho unreliable networks
    MinioConfig = new MinioConfig { ... }
};

📝 Ví dụ hoàn chỉnh

using RobotNet10.StorageManager;
using System.IO;

// 1. Cấu hình
var config = new StorageConfig
{
    UsingLocal = true,
    LocalFolder = "MapImages",
    RetryCount = 3
};

// 2. Tạo StorageManager
using var storageManager = new StorageManager(config);

// 3. Upload file
var imageData = File.ReadAllBytes("map.png");
using var imageStream = new MemoryStream(imageData);
await storageManager.UploadAsync(
    path: "maps/level1",
    objectName: "map_image",
    data: imageStream,
    size: imageData.Length,
    contentType: "image/png",
    cancellationToken: CancellationToken.None
);

// 4. Check file exists
if (await storageManager.ExistsAsync("maps/level1", "map_image", CancellationToken.None))
{
    // 5. Get metadata
    var metadata = await storageManager.GetMetadataAsync(
        "maps/level1", 
        "map_image", 
        CancellationToken.None
    );
    Console.WriteLine($"Uploaded: {metadata.Size} bytes");

    // 6. Get URL
    var url = await storageManager.GetUrlAsync("maps/level1", "map_image");
    Console.WriteLine($"File URL: {url}");

    // 6.5. Get File
    using var fileStream = await storageManager.GetFileAsync(
        "maps/level1", 
        "map_image", 
        CancellationToken.None
    );
    var fileData = new byte[fileStream.Length];
    await fileStream.ReadAsync(fileData, 0, (int)fileStream.Length);
    Console.WriteLine($"File data length: {fileData.Length} bytes");

    // 7. List files
    var files = await storageManager.ListAsync("maps", recursive: false, CancellationToken.None);
    Console.WriteLine($"Total files: {files.Count}");

    // 8. Copy file
    await storageManager.CopyAsync(
        "maps/level1",
        "maps/backup",
        "map_image",
        CancellationToken.None
    );

    // 9. Delete file
    await storageManager.DeleteAsync("maps/level1", "map_image", CancellationToken.None);
}

🔧 Supported Content Types

StorageManager tự động detect file extension từ các Content-Type sau:

Content-Type Extension
image/png .png
image/jpeg, image/jpg .jpg
image/gif .gif
image/bmp .bmp
image/webp .webp
application/pdf .pdf
application/json .json
text/plain .txt
text/csv .csv
application/xml .xml
application/zip .zip
Others .bin (default)

📄 License

[Thêm license information nếu có]

🤝 Contributing

[Thêm contributing guidelines nếu có]