Almacenamiento S3
DinaS3ClientC opera sobre un bucket S3 o MinIO y DinaS3MultiClientC replica, repara y detecta deriva entre varios buckets desde .NET.
El módulo S3 del paquete Dinaup se conecta a cualquier almacén compatible con S3 (AWS S3, MinIO, Cloudflare R2, OVH) sobre el cliente de Minio. Trae dos clientes en el namespace Dinaup.S3: DinaS3ClientC para un bucket y DinaS3MultiClientC para replicar y reparar sobre varios.
Antes de empezar
- Paquete
Dinaupinstalado. Ver SDK .NET. - Clave de acceso, clave secreta, endpoint y nombre del bucket. Guárdalos en el Vault, no en el código.
using Dinaup.S3;Un bucket: DinaS3ClientC
Conectar
var s3 = new DinaS3ClientC(
_s3Token: "ACCESS_KEY",
_s3Secret: "SECRET_KEY",
_s3EndPoint: "s3.tu-dominio.com",
_Bucket: "mi-bucket",
_BaseDir: "app/"); // prefijo opcional dentro del bucket
// Por cadena de conexión
var s3b = new DinaS3ClientC("s3://ACCESS:SECRET@s3.tu-dominio.com/mi-bucket/app");
// Por diccionario: accessKey, secretKey, endpoint, bucketName, prefix, region
var s3c = new DinaS3ClientC(parametros);| Constructor | Detalle |
|---|---|
New(_s3Token, _s3Secret, _s3EndPoint, _Bucket, _BaseDir = "", _regionForazada = "") | Parámetros sueltos. _regionForazada salta la detección de región por proveedor. |
New(_connectionString, _regionForazada = "") | s3://ACCESS:SECRET@host[:puerto]/bucket[/prefijo]. Con http:// delante desactiva TLS; por defecto usa TLS. |
New(param As Dictionary(Of String, String)) | Claves accessKey, secretKey, endpoint, bucketName, prefix, region. |
La conexión se abre en la primera operación (Iniciar()). La región se deduce del endpoint para R2 (auto) y OVH. EntrarDir(dir) añade un subdirectorio al prefijo.
Operar con objetos
Las rutas son relativas al bucket y al prefijo.
// Subir bytes
await s3.BytesUploadAsync("informes/2026-07.json", contenidoBytes);
// Descargar bytes
byte[] datos = await s3.BytesReadAsync("informes/2026-07.json");
// Comprobar si existe (clave exacta, no prefijo)
bool hay = await s3.FileExistAsync("informes/2026-07.json");
// Subir y descargar archivos de disco
await s3.FileUploadAsync("informes/2026-07.pdf", @"C:\temp\informe.pdf");
await s3.FileDownloadAsync("informes/2026-07.pdf", @"C:\temp\bajado.pdf");
// Sube solo si el contenido difiere del que ya hay
await s3.FileUploadIfIsDifferentAsync("logo.png", @"C:\assets\logo.png");
// Listar
var items = await s3.FileList("informes/", recursive: true);
// Borrar
await s3.FileRemoveAsync("informes/2026-07.json");| Método | Qué hace |
|---|---|
BytesUploadAsync(ruta, bytes) | Sube bytes. Si el PUT falla, lo registra en el log y no lanza. |
BytesUploadThrowAsync(ruta, bytes) | Igual, pero propaga la excepción del PUT. |
StreamUploadAsync(ruta, stream) | Sube desde un Stream o MemoryStream. |
BytesReadAsync(ruta) | Descarga el objeto como bytes. |
FileUploadAsync(ruta, archivoLocal) / FileDownloadAsync(ruta, destinoLocal) | Sube o descarga un archivo de disco. |
FileUploadIfIsDifferentAsync(ruta, archivoLocal) | Sube solo si el contenido cambió. |
FileExistAsync(ruta) | true si existe ese objeto. |
FileRemoveAsync(ruta) | Borra el objeto. |
LastModified(ruta) | Fecha de última modificación. |
GetFileMetadataAsync(ruta) | ObjectStat de Minio con tamaño, ETag y metadatos. |
FileList(prefijo, recursive = true, limit = -1) | Lista objetos bajo un prefijo. |
DirList(prefijo, recursive = true) | Lista directorios. |
ListAll(prefijo, files = true, directories = false, recursive = true) / ListAll_GetEnumerator | Lista archivos y directorios; la variante enumerador no carga todo en memoria. |
JObjectWriteAsync<T>(ruta, objeto) / JObjectReadAsync<T>(ruta) | Serializa y deserializa JSON. |
NoSQL_WriteAsync(ruta, texto) / NoSQL_ReadAsync(ruta) / NoSQL_RemoveAsync(ruta) | Texto plano como clave-valor. |
Los contadores TotalRead, TotalWrite, TotalList, TotalDelete, TotalStat, TotalFileUpload y TotalFileDownload cuentan intentos por operación; ResetCounters() los pone a cero.
URLs firmadas
Genera URLs temporales de lectura o de subida sin exponer las claves. Caducan a los 600 segundos si no indicas otro valor.
// Lectura
string urlLectura = s3.SignGet("informes/2026-07.pdf", expirationSeconds: 600);
// Lectura con nombre de descarga y sin cabecera Content-Disposition
string urlInline = s3.SignGet("informes/2026-07.pdf", NombreArchivo: "informe.pdf", disposition: false);
// Escritura directa desde el navegador
string urlSubida = s3.SignPut("subidas/nuevo.pdf", expirationSeconds: 600);Salud
bool ok = await s3.HealthCheckAsync();
var salud = await s3.SaludAsync();
// salud.Estado: Ok, SoloLectura, Credenciales, Bucket, Red, SinConfigurar o Desconocido
// salud.Motivo: el texto del fallo; salud.Ok: true solo con Estado OkSaludAsync lee el marcador HealthCheck.dat y, si no existe, lo escribe: distingue una clave de solo lectura (estado SoloLectura) de un bucket inaccesible. Ping() escribe y relee una clave de prueba. DinaS3ClientC implementa IHealthCheck: registrado con AddHealthChecks().AddCheck("s3", s3), devuelve Healthy, Degraded (solo lectura) o Unhealthy.
Varios buckets: DinaS3MultiClientC
Envuelve una lista de DinaS3ClientC y opera sobre todos: escribe en todos y lee de la réplica con la versión más reciente. Si a un bucket le falta el objeto o lo tiene atrasado, lo repara desde la réplica buena antes de devolver.
var multi = new DinaS3MultiClientC(
new List<DinaS3ClientC> { s3Primario, s3Secundario },
_autoRepair: true);
// Escribe en todos los buckets
var res = await multi.WriteBytesAsync("informes/2026-07.json", datos);
if (res.AllOk == false)
Console.WriteLine($"Fallaron: {string.Join(", ", res.FailedHosts)}");
// Lee de la réplica más reciente
byte[] leido = await multi.ReadBytesAsync("informes/2026-07.json");
// Objetos serializados en JSON y texto
await multi.WriteObjectAsync("config/app.json", miConfig);
var config = await multi.ReadObjectAsync<AppConfig>("config/app.json");
string texto = await multi.ReadStringAsync("config/version.txt");
// Existencia, listado, archivos y borrado
bool hay = await multi.ExistsAsync("informes/2026-07.json");
var items = await multi.ListAsync("informes/", recursive: true);
await multi.UploadFileAsync("informes/2026-07.pdf", @"C:\temp\informe.pdf");
await multi.DownloadFileAsync(@"C:\temp\bajado.pdf", "informes/2026-07.pdf");
bool borrado = await multi.DeleteAsync("informes/2026-07.json");| Método | Qué hace |
|---|---|
WriteBytesAsync(ruta, bytes) / WriteObjectAsync<T>(ruta, objeto) | Escribe en todas las réplicas en paralelo. Devuelve WriteResult con OkHosts, FailedHosts, AllOk y AnyOk. |
ReadBytesAsync(ruta) / ReadObjectAsync<T>(ruta) / ReadStringAsync(ruta) | Lee de la réplica más reciente. Con AutoRepair, repara las atrasadas antes de devolver. null si ninguna tiene el objeto. |
ExistsAsync(ruta) | true si al menos una réplica que responde lo tiene. |
ListAsync(prefijo, recursive = true, limit = -1) | Unión de todas las réplicas, sin duplicados por clave. Una réplica caída se ignora. |
UploadFileAsync(ruta, archivoLocal) / DownloadFileAsync(destinoLocal, ruta) | Archivos de disco. DownloadFileAsync recibe primero el destino local. |
DeleteAsync(ruta) | Borra en todas, con dos intentos por réplica. true solo si todas confirmaron; si una falla, repite el borrado cuando vuelva. |
ResyncAsync(ruta) | Fuerza la sincronización de una ruta y devuelve un WriteResult con dónde quedó el objeto. |
RepairAsync(ruta) | Igual que ResyncAsync, con respuesta bool. Ignora AutoRepair. |
CheckDriftAsync(prefijo, sampleCount = 50) | Muestrea hasta sampleCount rutas y compara versiones entre réplicas. Solo diagnostica. |
HealthCheckAsync() | true solo si todas las réplicas responden. |
Clients expone la lista de réplicas, AutoRepair activa o desactiva la reparación en lecturas y TotalRepairs cuenta las subidas de reparación.
Deriva entre réplicas
var drift = await multi.CheckDriftAsync("informes/", sampleCount: 50);
if (drift.HasDrift)
Console.WriteLine($"{drift.Drifted.Count} objetos difieren entre buckets");CheckDriftAsync devuelve un DriftReport con TotalChecked, InSync, Drifted (rutas que difieren) y Missing (por host, qué rutas faltan). Para corregir, llama a ResyncAsync por cada ruta de Drifted.
La replicación no es transaccional: WriteBytesAsync escribe en todas las réplicas y te devuelve el resultado por host. Comprueba AllOk y repite la escritura en los hosts de FailedHosts cuando vuelvan.
Los archivos que suben los usuarios de Dinaup no pasan por este módulo: van al almacenamiento de la plataforma con File_UploadBytesAsync del Cliente Dinaup.