DMS.Foundation
1.0.0
dotnet add package DMS.Foundation --version 1.0.0
NuGet\Install-Package DMS.Foundation -Version 1.0.0
<PackageReference Include="DMS.Foundation" Version="1.0.0" />
<PackageVersion Include="DMS.Foundation" Version="1.0.0" />
<PackageReference Include="DMS.Foundation" />
paket add DMS.Foundation --version 1.0.0
#r "nuget: DMS.Foundation, 1.0.0"
#:package DMS.Foundation@1.0.0
#addin nuget:?package=DMS.Foundation&version=1.0.0
#tool nuget:?package=DMS.Foundation&version=1.0.0
Core DMS Software
Librería central de uso interno para proyectos DMS Software.
Provee servicios transversales reutilizables: seguridad, acceso a datos, notificaciones, logging, mapeo, helpers y gestión de tenants, todos integrados con la capa transversal DMS.
Tabla de contenido
- Requisitos
- Instalación
- Configuración
- Módulos
- Helpers
- Registro de dependencias
- Validación de licencia
- Licencia
Requisitos
- .NET 8.0
appsettings.jsoncorrectamente configurado (ver Configuración)- Acceso a la capa transversal de DMS Software
- Archivo de licencia válido emitido por DMS Software
Instalación
dotnet add package Core.DMS.Software --version 1.0.0
Configuración
Agrega las siguientes secciones en tu appsettings.json:
{
"TransversalLayer": {
"Service": "https://tu-capa-transversal/api",
"ServiceNotification": "https://tu-capa-transversal/notifications",
"API-KEY": "tu-api-key"
},
"TokenSettings": {
"ApplicationId": "tu-application-id"
},
"OAuth": {
"tenantId": "tu-tenant-id",
"clientId": "tu-client-id",
"clientSecret": "tu-client-secret"
},
"License": {
"PublicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
"LicenseFilePath": "license.json"
},
"PathLogs": "logs/errors.log",
"PathInfo": "logs/info.log"
}
Módulos
🔐 Seguridad (DMS.Security)
LicenseManager / LicenseValidator
Gestiona la validación de licencias mediante firma RSA-SHA256. Verifica que el archivo de licencia exista, no haya expirado y que su firma sea auténtica e inalterada.
// Validar la licencia al iniciar la aplicación (Program.cs)
LicenseValidator.ValidateLicense();
// Verificar en cualquier punto del flujo de negocio
LicenseValidator.EnsureLicenseIsValid();
Estructura esperada del archivo license.json:
{
"LicenseId": "uuid-de-la-licencia",
"Owner": "Nombre del cliente",
"ExpirationDate": "2026-12-31T00:00:00Z",
"Signature": "base64-de-la-firma-rsa"
}
LicenseValidationMiddleware
Middleware ASP.NET Core que valida la licencia en cada solicitud HTTP entrante. Si la licencia no es válida, rechaza la solicitud antes de que llegue al controlador.
// En Program.cs
app.UseMiddleware<LicenseValidationMiddleware>();
AESCryptoService / AESDecryptService
Cifrado y descifrado simétrico con AES-CBC. El cifrado requiere licencia válida activa.
byte[] key = Convert.FromBase64String("tu-clave-aes-base64-32-bytes");
byte[] iv = Convert.FromBase64String("tu-iv-base64-16-bytes");
// Cifrar
var encryptor = new AESCryptoService(key, iv);
string textoCifrado = encryptor.Encrypt("texto plano");
// Descifrar
var decryptor = new AESDecryptService(key, iv);
string textoOriginal = decryptor.Decrypt(textoCifrado);
⚠️ La clave debe tener exactamente 32 bytes (AES-256) y el IV 16 bytes. Generarlos con
RandomNumberGeneratory almacenarlos de forma segura en el sistema de secretos del proyecto.
🗄️ Acceso a datos (DMS.Repository)
StoredProcedureExecutor
Ejecutor avanzado de procedimientos almacenados y consultas SQL. Soporta medición de rendimiento, transacciones, paginación, múltiples result sets y auditoría automática. Todos los métodos retornan OperationResult<T> con tiempo de ejecución incluido.
Ejecutar sin retorno (INSERT / UPDATE / DELETE):
var result = await executor.ExecuteAsync(
"sp_CrearCliente",
new { Nombre = "Juan", Apellido = "Pérez" }
);
if (result.IsSuccess)
Console.WriteLine($"Filas afectadas: {result.Data}");
Ejecutar con parámetro OUTPUT:
var result = await executor.ExecuteWithOutputAsync(
"sp_InsertarProducto",
new { Nombre = "Producto A", Precio = 9900 },
outputParameterName: "@IdRetorno"
);
Console.WriteLine($"ID generado: {result.GeneratedId}");
Obtener un objeto:
var result = await executor.ExecuteSingleAsync<ClienteDTO>(
"sp_ObtenerClientePorId",
new { Id = 42 }
);
Obtener una lista:
var result = await executor.ExecuteListAsync<ProductoDTO>(
"sp_ListarProductos",
new { CategoriaId = 3 }
);
Paginación:
var result = await executor.ExecutePagedAsync<ProductoDTO>(
"sp_ListarProductosPaginado",
new { Page = 1, PageSize = 20, CategoriaId = 3 }
);
Console.WriteLine($"Total registros: {result.Data.TotalRecords}");
Console.WriteLine($"Página: {result.Data.Page} de {result.Data.TotalPages}");
Resultado JSON desde el SP:
var result = await executor.ExecuteJsonAsync(
"sp_ObtenerReporteJSON",
new { FechaInicio = "2025-01-01" },
jsonColumnName: "JSONString"
);
Múltiples result sets:
var mappers = new Dictionary<int, Func<SqlDataReader, CancellationToken, Task<object>>>
{
[0] = async (r, ct) => await DataMapper.MapToListAsync<ClienteDTO>(r, ct),
[1] = async (r, ct) => await DataMapper.MapToListAsync<PedidoDTO>(r, ct)
};
var result = await executor.ExecuteMultipleAsync("sp_ObtenerClientesYPedidos", mappers);
var clientes = result.Data.GetResultSet<List<ClienteDTO>>(0);
var pedidos = result.Data.GetResultSet<List<PedidoDTO>>(1);
Lote de operaciones en una misma transacción:
var operaciones = new List<(string, object?)>
{
("sp_InsertarCabecera", new { Total = 50000 }),
("sp_InsertarDetalle", new { ProductoId = 1, Cantidad = 2 }),
("sp_InsertarDetalle", new { ProductoId = 5, Cantidad = 1 })
};
var result = await executor.ExecuteBatchAsync(operaciones);
Con auditoría automática:
var result = await executor.ExecuteWithAuditAsync(
"sp_CrearOrden",
createdBy: 101,
userName: "jperez",
tenantId: 5,
ipAddress: "192.168.1.10",
parameters: new { ClienteId = 42, Total = 75000 }
);
Opciones avanzadas (StoredProcedureOptions):
var options = new StoredProcedureOptions
{
CommandTimeout = 60, // segundos
LogPerformance = true, // registra tiempo de ejecución
TenantId = "tenant-abc",
LoginType = "External",
IsExternal = true
};
var result = await executor.ExecuteListAsync<ReporteDTO>("sp_Reporte", parameters, options);
📢 Notificaciones (DMS.Services.NotificationService)
Envío de mensajes WhatsApp (Gupshup) y correos electrónicos a través de la capa transversal DMS.
// Mensaje de texto simple
await notificationService.SendMessageText(rootObjectGupshupTextDTO);
// Template WhatsApp (registra seguimiento automático)
await notificationService.SendMessageGupshupTemplate(messageGupshupDTO);
// Documento por WhatsApp
await notificationService.SendDocmentGupshupTemplate(messageDocumentGupshupDTO);
// Correo electrónico
await notificationService.SendTrasnversalLayerEmail(dataEmailsDTO);
SendMessageGupshupTemplateregistra automáticamente el tracking del mensaje viaWhatsAppTrackingService.
📊 Seguimiento de WhatsApp (DMS.Services.WhatsAppTrackingService)
Registra el volumen de mensajes enviados por tenant y aplicación, contabilizando templates, videos y documentos enviados en cada mensaje.
await whatsAppTrackingService.CreateWhatsAppTracking(messageGupshupDTO);
📝 Logging (DMS.Services.LogService)
Registro de logs hacia la capa transversal DMS y en archivos locales con rotación automática al superar 100 MB.
// Asíncrono hacia la capa transversal (recomendado en servicios)
await logService.SaveLogsMessagesAsync(
"Ocurrió un error al procesar el pago",
tenantId: 1,
applicationId: 2,
errorType: 3
);
// Sincrónico a archivo local de errores
logService.SaveLogsMessages("Error crítico en proceso X");
// Sincrónico a archivo local informativo
logService.SaveInfoMessages("Proceso de facturación completado correctamente");
🌐 HTTP Transversal (DMS.Services.HttpDMSService)
Cliente HTTP genérico para consumir endpoints de la capa transversal con autenticación Bearer token.
var result = await httpDMSService.Get<ResponseDTO>(
baseUrl: "https://capa-transversal/api",
endpoint: "/GetRecurso",
token: "bearer-token",
parameters: new Dictionary<string, string>
{
{ "id", "123" },
{ "estado", "activo" }
}
);
🔑 OAuth (DMS.Services.TokenOAuthCrossLayerService)
Obtiene tokens de acceso OAuth 2.0 desde la capa transversal usando las credenciales configuradas en appsettings.json.
string token = await tokenService.GetToken<ResponseDTO>();
🏢 Tenant (DMS.Services.TenantService / GetDescriptionByTenantService)
Obtiene la información descriptiva del tenant activo a partir del token JWT presente en el contexto HTTP.
var tenant = await tenantService.GetDescriptionByTenantAsync(httpContextAccessor);
Console.WriteLine(tenant.Name);
🏗️ Aplicación (DMS.Services.ApplicationService)
Recupera la cadena de conexión dinámica y los datos visuales (logo, colores corporativos) del tenant activo.
// Cadena de conexión por tenant y tipo de entorno
string connString = await applicationService.GetApplicationDataAsync(tenantId, loginType);
// Logo y colores corporativos
var desc = await applicationService.GetDescriptionApplication(tenantId);
Console.WriteLine($"Color primario: {desc.PrimaryColor}");
🔄 Acceso automático a tenants (DMS.Services.AccessAutoAsync)
Consulta los tenants asociados a un servicio específico en la capa transversal, útil para flujos multi-tenant automáticos.
var tenants = await accessAutoAsync.GetAccessAutoAsyncTenants<List<TenantDTO>>(token);
Helpers
BlobAzureHelpers
Operaciones sobre Azure Blob Storage. Todos los métodos requieren licencia válida.
// Obtener cliente de blob
var blobClient = BlobAzureHelpers.GetBlobClient(blobContainerUrl, "carpeta/archivo.pdf");
// Descargar como bytes
byte[] bytes = await BlobAzureHelpers.DownloadBlobAsync(blobClient);
// Descargar como Base64
string base64 = await BlobAzureHelpers.DownloadBlobAsBase64Async(blobClient);
// Subir archivo desde Base64
await BlobAzureHelpers.UploadFileAsync(
blobContainerUrl,
"application/pdf",
"carpeta/archivo.pdf",
base64,
logService
);
// Generar URL de acceso temporal (SAS read-only)
Uri sasUri = BlobAzureHelpers.GenerateReadOnlySasUri(
blobClient,
duration: TimeSpan.FromHours(2),
accountName: "storageAccount",
accountKey: "storageKey"
);
// Obtener propiedades del blob
BlobProperties props = await BlobAzureHelpers.GetFilesBlob(
blobContainerUrl, "carpeta/archivo.pdf", logService
);
Console.WriteLine($"Tamaño: {props.ContentLength} bytes");
// Generar URL combinando base SAS con ruta
string url = BlobAzureHelpers.GenerateBlobUrlWithSas(blobUrlWithSasToken, "carpeta/archivo.pdf");
DataMapper
Mapeo de alto rendimiento de SqlDataReader a objetos y listas. Usa caché interno de propiedades y columnas para minimizar la reflexión en llamadas repetidas. Respeta [JsonPropertyName] para el nombre de columna y [NotMapped] para excluir propiedades.
// Mapear a lista
var clientes = await DataMapper.MapToListAsync<ClienteDTO>(reader);
// Mapear a objeto único
var cliente = await DataMapper.MapToObjectAsync<ClienteDTO>(reader);
// Mapear resultado JSON (columna específica)
string? json = await DataMapper.MapJsonStringAsync(reader, columnName: "JSONString");
// Verificar errores retornados por el SP
string? error = await DataMapper.CheckForErrorAsync(reader, errorColumnName: "error");
if (error != null) throw new Exception(error);
// Verificar permisos de acceso
bool tieneAcceso = await DataMapper.CheckAccessAsync(reader);
// Múltiples result sets con mappers personalizados
var mappers = new Dictionary<int, Func<SqlDataReader, CancellationToken, Task<object>>>
{
[0] = async (r, ct) => await DataMapper.MapToListAsync<ClienteDTO>(r, ct),
[1] = async (r, ct) => await DataMapper.MapToObjectAsync<ResumenDTO>(r, ct)
};
var sets = await DataMapper.MapMultipleResultSetsAsync(reader, mappers);
Tipos especiales soportados: TimeOnly (desde TimeSpan), DateOnly (desde DateTime), tipos Nullable<T>.
ClaimsHelper
Extracción de claims del token JWT desde el contexto HTTP. Busca el claim por nombre primario y, si no lo encuentra, por nombre alternativo (camelCase).
string tenantId = ClaimsHelper.GetTenantIdFromClaims(httpContextAccessor);
string appId = ClaimsHelper.GetApplicationIdFromClaims(httpContextAccessor);
string loginType = ClaimsHelper.GetTypeLoginFromClaims(httpContextAccessor);
string nombre = ClaimsHelper.GetNameFromClaims(httpContextAccessor);
string telefono = ClaimsHelper.GetNumberIdFromClaims(httpContextAccessor);
string imagen = ClaimsHelper.GetImage(httpContextAccessor);
string advanceId = ClaimsHelper.GetAdvanceIdFromClaims(httpContextAccessor);
string advanceUser = ClaimsHelper.GetUserAdvance(httpContextAccessor);
Lanza
InvalidOperationExceptionsi el claim requerido no está presente en el token.
Base64ToImageHelper
Convierte una cadena Base64 a un archivo .png en disco. Crea el directorio de salida si no existe. Requiere licencia válida.
string nombreArchivo = Base64ToImageHelper.ConvertBase64ToImg(
base64String: imageBase64,
outputPath: "wwwroot/images/uploads",
logService: logService
);
// Retorna el nombre del archivo generado, ej: "3f4a9c1b-....png"
ImageToBase64Helper
Convierte un archivo de imagen en disco a cadena Base64. Requiere licencia válida.
string base64 = ImageToBase64Helper.ConvertImgToBase64(
imagePath: "wwwroot/images/logo.png",
logService: logService
);
DeleteImageHelper
Elimina un archivo del sistema de archivos de forma segura, sin propagar excepciones no controladas. Requiere licencia válida.
bool eliminado = DeleteImageHelper.DeleteImageIfExists(
imagePath: "wwwroot/images/uploads/foto.png",
logService: logService
);
DTOtoObjectHelper
Mapeo por convención de nombre de propiedades entre un DTO origen y cualquier objeto destino. Disponible como extension method sobre cualquier objeto. Requiere licencia válida.
var clienteDTO = new ClienteDTO { Nombre = "Juan", Email = "juan@mail.com" };
var clienteEntity = clienteDTO.ToObject<ClienteEntity>();
MapToListHelper / MapToObjHelper
Mapeadores síncronos y asíncronos de IDataReader / SqlDataReader a listas y objetos. Respetan [JsonPropertyName] para el nombre de columna. Requieren licencia válida.
// Síncrono a lista
var lista = MapToListHelper.MapToList<ProductoDTO>(dataReader);
// Asíncrono a lista
var lista = await MapToListHelper.MapToListAsync<ProductoDTO>(dataReader);
// Asíncrono a objeto único (SqlDataReader)
var obj = await MapToObjHelper.MapToObj<ClienteDTO>(sqlDataReader);
// Verificar si una columna existe en el reader
bool existe = MapToObjHelper.FieldExists(sqlDataReader, "NombreColumna");
Para nuevos desarrollos se recomienda
DataMapper, que incluye caché de columnas y soporte nativo paraDateOnly/TimeOnly.
ParameterHelper
Construcción de parámetros SQL para SqlCommand desde objetos anónimos o tipados. Respeta los atributos [SpParam] (nombre personalizado) y [NonSpParam] (excluir propiedad).
// Parámetros de entrada
ParameterHelper.AddParameters(command, new
{
ClienteId = 42,
Estado = "Activo"
});
// Genera: @ClienteId = 42, @Estado = 'Activo'
// Parámetro OUTPUT
var outputParam = ParameterHelper.AddOutputParameter(
command,
parameterName: "@IdRetorno",
sqlDbType: SqlDbType.Int
);
// Leer después de ExecuteNonQuery:
int idGenerado = (int)outputParam.Value;
// Parámetro INPUT/OUTPUT
var ioParam = ParameterHelper.AddInputOutputParameter(
command,
parameterName: "@Contador",
sqlDbType: SqlDbType.Int,
value: 0
);
PasswordHashHelper
Hashing seguro de contraseñas con Argon2id (ganador del Password Hashing Competition). Configuración: 8 hilos, 64 MB de memoria, 4 iteraciones, salt de 16 bytes generado criptográficamente.
// Al registrar usuario
var (hash, salt) = PasswordHashHelper.HashPassword("MiContraseña123!");
// Almacenar hash y salt en la base de datos
// Al hacer login
byte[] saltBytes = Convert.FromBase64String(storedSalt);
bool esValida = PasswordHashHelper.VerifyPassword(
password: "MiContraseña123!",
storedHash: storedHash,
storedSalt: saltBytes
);
Nunca reutilizar el mismo salt entre contraseñas distintas. Cada llamada a
HashPasswordgenera un salt único.
ExceptionHelper
Manejo centralizado de excepciones no controladas: registra el error en el log y retorna un ResponseDTO estandarizado con IsSuccess = false. Requiere licencia válida.
try
{
// lógica de negocio
}
catch (Exception ex)
{
return ExceptionHelper.HandleException(
logService: logService,
methodName: nameof(MiMetodo),
ex: ex,
tenantId: 1,
applicationId: 2,
errorType: 3
);
}
HandleResponsesController
Wrapper para controladores ASP.NET Core que estandariza el manejo de respuestas y excepciones de validación, retornando siempre ActionResult con ResponseDTO.
[HttpPost]
public async Task<ActionResult> CrearCliente([FromBody] ClienteDTO dto)
{
return await HandleResponsesController.HandleResponse(
async () => await _clienteService.CrearClienteAsync(dto),
_logService,
nameof(ClienteController)
);
}
DisposableHelper
Utilidad para implementar el patrón IDisposable / IAsyncDisposable de forma segura, lanzando ObjectDisposedException con nombre contextual automático vía [CallerMemberName].
public class MiServicio : IDisposable
{
private bool _disposed;
public void HacerAlgo()
{
DisposableHelper.ThrowIfDisposed(_disposed);
// lógica segura
}
public void Dispose() => _disposed = true;
}
Registro de dependencias
// HTTP Clients
builder.Services.AddHttpClient<IHttpDMSService, HttpDMSService>();
builder.Services.AddHttpClient<ILogService, LogService>();
builder.Services.AddHttpClient<ITenantService, TenantService>();
builder.Services.AddHttpClient<IGetDescriptionByTenantService, GetDescriptionByTenantService>();
builder.Services.AddHttpClient<ITokenOAuthCrossLayerService, TokenOAuthCrossLayerService>();
builder.Services.AddHttpClient<IAccessAutoAsync, AccessAutoAsync>();
// Servicios Scoped
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IWhatsAppTrackingService, WhatsAppTrackingService>();
builder.Services.AddScoped<IApplicationService, ApplicationService>();
builder.Services.AddScoped<IContextConectionService, ContextConectionService>();
builder.Services.AddScoped<IStoredProcedureExecutor, StoredProcedureExecutor>();
builder.Services.AddScoped<IAdvancedStoredProcedureExecutor, StoredProcedureExecutor>();
// AES — las claves deben venir de tu sistema de secretos, nunca hardcodeadas
byte[] aesKey = Convert.FromBase64String(builder.Configuration["Security:AesKey"]!);
byte[] aesIv = Convert.FromBase64String(builder.Configuration["Security:AesIv"]!);
builder.Services.AddSingleton<IAESCryptoService>(_ => new AESCryptoService(aesKey, aesIv));
builder.Services.AddSingleton<IAESDecryptService>(_ => new AESDecryptService(aesKey, aesIv));
// Infraestructura
builder.Services.AddHttpContextAccessor();
Validación de licencia
// Program.cs — orden recomendado
// 1. Validar antes de construir la app
LicenseValidator.ValidateLicense();
var app = builder.Build();
// 2. Middleware opcional: valida en cada request entrante
app.UseMiddleware<LicenseValidationMiddleware>();
app.Run();
Si la licencia no es válida, ha expirado o ha sido alterada, se lanza UnauthorizedAccessException con el detalle del motivo.
Licencia
El uso de esta librería requiere una licencia válida emitida por DMS Software.
Contacta a soporte@dms.software para adquirir, renovar o consultar el estado de tu licencia.
© 2025 DMS Software. Todos los derechos reservados.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Azure.Storage.Blobs (>= 12.23.0)
- Konscious.Security.Cryptography.Argon2 (>= 1.3.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.3.0)
- Microsoft.AspNetCore.Mvc (>= 2.3.0)
- Microsoft.Data.SqlClient (>= 6.0.1)
- Microsoft.Extensions.Configuration (>= 9.0.2)
- Microsoft.Extensions.Configuration.Json (>= 9.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 126 | 5/13/2026 |
Versión inicial de DMS Foundation.
Módulos incluidos:
- Seguridad: validación de licencias RSA, middleware de licencia, cifrado/descifrado AES.
- Notificaciones: envío de mensajes WhatsApp (Gupshup) y correo electrónico vía capa transversal.
- Seguimiento WhatsApp: registro de mensajes enviados por tenant y aplicación.
- Conexión a datos: gestión de conexiones SQL Server con ciclo de vida controlado.
- Logging: registro hacia la capa transversal y en archivos locales con rotación automática.
- HTTP Transversal: cliente HTTP genérico con autenticación Bearer.
- OAuth: obtención de tokens desde la capa transversal.
- Tenant y Aplicación: cadena de conexión, logo y colores corporativos por tenant.