Pitasoft.Error
5.3.14
dotnet add package Pitasoft.Error --version 5.3.14
NuGet\Install-Package Pitasoft.Error -Version 5.3.14
<PackageReference Include="Pitasoft.Error" Version="5.3.14" />
<PackageVersion Include="Pitasoft.Error" Version="5.3.14" />
<PackageReference Include="Pitasoft.Error" />
paket add Pitasoft.Error --version 5.3.14
#r "nuget: Pitasoft.Error, 5.3.14"
#:package Pitasoft.Error@5.3.14
#addin nuget:?package=Pitasoft.Error&version=5.3.14
#tool nuget:?package=Pitasoft.Error&version=5.3.14
Pitasoft.Error
Pitasoft.Error is a class library designed to facilitate the handling and management of error collections in .NET applications. It allows grouping errors by property, handling change notifications, and serializing/deserializing error collections to/from JSON.
English
Key Features
- ErrorCollection: A robust collection for storing errors associated with property names.
- Factory Properties: Access
ErrorCollection.Emptyfor a fresh, empty collection instance. - IErrorCollectionContainer: A standard interface for objects (like ViewModels) that expose an
ErrorCollection. - NuGet Package Scope: The NuGet package contains the class library only (no UI sample applications are shipped inside the package).
- High Performance: Optimized for low memory allocation and fast execution, especially in filtering and mass clearing operations. It uses
[MethodImpl(MethodImplOptions.AggressiveInlining)]on critical paths and has been refactored to eliminate LINQ overhead in core methods. - Advanced Filtering: Flexible error retrieval using
IntersectorExceptoperations withJointActionErrors. - INotifyDataErrorInfo Support: Fully implements
INotifyDataErrorInfofor seamless integration with WPF, Avalonia UI, MAUI, and other data-binding frameworks. - Notifications: Support for events when the error collection changes.
- Total Error Counting: The
Countproperty returns the total number of individual error messages across all properties. - JSON Serialization: Built-in extensions to convert error collections to JSON and vice versa.
- Safer Construction and Deserialization: Constructor copies are list-safe (no shared mutable lists), and JSON deserialization ignores invalid entries (empty keys or null/empty error lists).
- Lambda-based Extensions: Type-safe property selection via
Expression<Func<T, object?>>extensions (no magic strings). - Multi-target Support: Compatible with .NET 8.0, .NET 9.0, and .NET 10.0.
- Fluent API: Chainable methods for fast and readable error collection setup.
- Exception Error Detection: Automatic error message extraction from complex exception hierarchies.
Installation
You can install this package via NuGet:
dotnet add package Pitasoft.Error
Package Scope and UI Compatibility
Pitasoft.ErrorNuGet package ships only the error-management library.- The package is designed to work with
INotifyDataErrorInfo, so it is compatible with WPF, MAUI, Avalonia UI, and other MVVM-friendly UI frameworks.
What's New / Current Capabilities
ErrorCollection.Emptyreturns a new instance each time (no shared mutable singleton).Countreturns the total number of individual messages across all properties.Clear(...)andClearAll(...)are the preferred APIs (Remove*methods are kept only for backward compatibility and are marked obsolete).- Lambda-based extensions remove magic strings (
Add<T>,AnyError<T>,Clear<T>, andCreate<T>). - JSON serialization can be customized with
SetJsonSerializerOptions(...)for the current async flow without changing process-wide defaults. - Constructors and JSON converter normalize/copy data to avoid shared list references and invalid entries.
nullinputs in constructors and bulk APIs are treated defensively as empty/no-op, andnull/empty/whitespace error messages are ignored.Combine(...)merges nullable collections without extra null checks (returnsnullonly when both collections are null/empty).WithPrefix(...)creates a new collection with prefixed keys (useful for nested objects likeAddress.Street).- Exception-based APIs are shallow by default; pass
deep: truewhen you want to include inner exceptions recursively.
Newly Added Behaviors (Detailed)
1) Safe copy semantics in constructors
When you build a collection from existing dictionaries/lists, internal lists are copied to avoid accidental shared mutations.
var source = new List<string> { "E1" };
var ec = new ErrorCollection(new[]
{
new KeyValuePair<string, List<string>>("Name", source)
});
source.Add("E2"); // does not mutate ec
2) Defensive JSON deserialization
Deserialization now ignores invalid entries (empty keys, null lists, empty lists) and keeps only valid errors.
var json = "{\"Valid\":[\"E1\"],\"Empty\":[],\"Null\":null,\" \":[\"Ignored\"]}";
var ec = json.ToErrorCollection();
// Only "Valid" remains
3) Null/empty JSON input is safe
ToErrorCollection always returns a non-null instance for null/empty input.
string? raw = null;
var ec = raw.ToErrorCollection(); // empty collection, never null
4) Defensive null handling in bulk APIs
Bulk add, clear, and filter operations treat null inputs as empty/no-op instead of throwing.
var ec = new ErrorCollection();
ec.Add("Name", (string[]?)null); // no-op
ec.Clear((IEnumerable<string>?)null); // no-op
var all = ec.GetErrors(JointActionErrors.Except, (string[]?)null);
5) Exception handling defaults are explicit
Exception-based creation and conversion methods add only the top-level exception message by default. Use deep: true to include the full inner-exception chain.
var shallow = ErrorCollection.Create(ex); // top-level message only
var deep = ErrorCollection.Create(ex, deep: true); // includes inner exceptions
Backend Usage (Minimal API / Web API)
Pitasoft.Error is also useful in backend services to standardize validation and error payloads.
using Microsoft.AspNetCore.Mvc;
using Pitasoft.Error;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/users", ([FromBody] CreateUserRequest request) =>
{
var errors = ErrorCollection.Empty;
if (string.IsNullOrWhiteSpace(request.Email))
errors.Add(nameof(request.Email), "Email is required.");
if (!request.AcceptTerms)
errors.Add(nameof(request.AcceptTerms), "Terms must be accepted.");
if (errors.HasErrors)
return Results.ValidationProblem(
errors.ToDictionary(kv => kv.Key, kv => kv.Value.ToArray()));
return Results.Created($"/users/{Guid.NewGuid()}", new { ok = true });
});
app.Run();
public record CreateUserRequest(string? Email, bool AcceptTerms);
Basic Usage
Quick API Reference
| API | Purpose | Example |
|---|---|---|
ErrorCollection.Empty |
Create a fresh empty collection | var errors = ErrorCollection.Empty; |
Add(...) |
Add one or more errors | errors.Add("Email", "Required"); |
Clear(...) / ClearAll() |
Remove errors by property or all | errors.Clear("Email"); |
AnyError(...) |
Fast checks for existing errors | errors.AnyError("Email"); |
GetErrors(...) |
Read error messages | errors.GetErrors(); |
GetProperties(...) |
Read properties with errors | errors.GetProperties(); |
FirstOrDefault(...) |
Get first error globally or by property | errors.FirstOrDefault("Email"); |
ToJson() / ToErrorCollection() |
Serialize/deserialize errors | var json = errors.ToJson(); |
Add<T>(...) / Clear<T>(...) |
Type-safe lambda helpers | errors.Add<UserVm>(x => x.Email, "Required"); |
ErrorCollection.Create(...) |
Factory methods for fast creation | var e = ErrorCollection.Create("General", "Fail"); |
Quick Error Access
// Get the first error message across all properties (useful for summary UI)
string? firstError = errors.FirstOrDefault();
// Check for errors on a single property (fast O(1) check)
bool hasNameErrors = errors.AnyError("Name");
Creating an Error Collection and Adding/Clearing Errors
using Pitasoft.Error;
// Create using constructor or factory property
var errors = ErrorCollection.Empty;
// Fluent API: chain additions seamlessly
errors.Add("Name", "The name is required.")
.Add("Email", "The email format is invalid.");
// Count returns the total number of individual error messages (2 in this case)
int totalErrors = errors.Count;
// Support for multiple errors at once (params string[])
errors.Add("Status", "Invalid value", "Out of range");
if (errors.AnyError())
{
// Handle errors globally
}
// Clear errors for a specific property
errors.Clear("Name");
// Clear all errors
errors.ClearAll();
Note: The
RemoveandRemoveAllmethods are now deprecated in favor ofClearandClearAllto maintain naming consistency with standard .NET collections.
Methods Without Notifications
If you need to add errors without triggering the ErrorsChanged event:
errors.AddWithoutNotification("SilentProperty", "This error won't trigger an event.");
UI Frameworks Integration (MVVM)
ErrorCollection is designed to be easily integrated into ViewModels. You can implement IErrorCollectionContainer to provide a standard way of exposing errors:
public class MyViewModel : INotifyDataErrorInfo, IErrorCollectionContainer
{
public ErrorCollection Errors { get; set; } = new();
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
{
add => Errors.ErrorsChanged += value;
remove => Errors.ErrorsChanged -= value;
}
public System.Collections.IEnumerable GetErrors(string? propertyName) =>
((INotifyDataErrorInfo)Errors).GetErrors(propertyName);
public bool HasErrors => Errors.HasErrors;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
ValidateName();
}
}
private void ValidateName()
{
Errors.Clear(nameof(Name));
if (string.IsNullOrWhiteSpace(Name))
Errors.Add(nameof(Name), "Name is required");
}
}
INotifyDataErrorInfo Compatibility Example (WPF / MAUI / Avalonia)
ErrorCollection already implements INotifyDataErrorInfo, so your ViewModel can delegate directly:
public sealed class LoginViewModel : INotifyDataErrorInfo
{
public ErrorCollection Errors { get; } = new();
public bool HasErrors => Errors.HasErrors;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
{
add => Errors.ErrorsChanged += value;
remove => Errors.ErrorsChanged -= value;
}
public System.Collections.IEnumerable GetErrors(string? propertyName) =>
((INotifyDataErrorInfo)Errors).GetErrors(propertyName);
public void ValidateUser(string? userName)
{
Errors.Clear(nameof(UserName));
if (string.IsNullOrWhiteSpace(userName))
{
Errors.Add(nameof(UserName), "User name is required.");
}
}
public string? UserName { get; set; }
}
Advanced Filtering
You can filter errors using JointActionErrors to include or exclude specific properties:
// Get errors only for specific properties
var relevantErrors = errors.GetErrors(JointActionErrors.Intersect, "Username", "Password");
// Get errors for all properties EXCEPT the ones specified
var otherErrors = errors.GetErrors(JointActionErrors.Except, "IsAcceptedTerms");
// Check if any error exists for specific properties
bool hasCriticalErrors = errors.AnyError(JointActionErrors.Intersect, "System", "Database");
Using Static Creation Methods
var errors = ErrorCollection.Create("General", "An unexpected error has occurred.");
JSON Serialization
using Pitasoft.Error.Extensions;
string json = errors.ToJson();
Deserialization from JSON
string json = "{\"name\":[\"Error 1\"]}";
var errors = json.ToErrorCollection();
Lambda-friendly Extensions
These helpers live in the Pitasoft.Error.Extensions namespace.
using Pitasoft.Error;
using Pitasoft.Error.Extensions;
var errors = new ErrorCollection();
// Add errors without magic strings
errors.Add<MyViewModel>(vm => vm.Name, "Name is required");
errors.Add<MyViewModel>(vm => vm.Email, "Invalid format", "Domain not allowed");
// Query errors
bool hasEmailErrors = errors.AnyError<MyViewModel>(vm => vm.Email);
// Joint checks (Intersect/Except)
bool anyUserFieldHasErrors = errors.AnyError<MyViewModel>(JointActionErrors.Intersect,
vm => vm.Name, vm => vm.Email);
// Clear by property expression
errors.Clear<MyViewModel>(vm => vm.Email);
errors.Clear<MyViewModel>(vm => vm.Name, vm => vm.Email);
errors.Clear<MyViewModel>(notification: false, vm => vm.Name);
// Factory methods
var created = ErrorCollectionExtensions.Create<MyViewModel>(vm => vm.Name, "Required");
// Combine nullable collections
ErrorCollection? serverErrors = ErrorCollection.Create("Email", "Already in use");
ErrorCollection? modelErrors = null;
var merged = modelErrors.Combine(serverErrors);
// Prefix keys for nested object validation
var addressErrors = ErrorCollection.Create("Street", "Street is required")
.WithPrefix("Address"); // key becomes "Address.Street"
Note: Lambdas are implemented as extension methods to keep
ErrorCollectionlean. Performance is comparable to string-based overloads for typical MVVM usage.
Event Notifications
You can subscribe to changes in the error collection:
var errors = new ErrorCollection();
errors.ErrorsChanged += (sender, e) =>
{
Console.WriteLine($"Property '{e.PropertyName}' was updated.");
};
errors.Add("Username", "Already exists.");
Exception Handling
Easily convert exceptions into error collections:
try
{
// Some code that throws
}
catch (Exception ex)
{
// Default behavior is shallow
var errors = ErrorCollection.Create(ex);
// Request recursive capture explicitly
var deepErrors = ErrorCollection.Create(ex, deep: true);
// OR using extension method
var errorsExt = ex.ToErrorCollection(deep: true);
// OR using implicit operator (also shallow by default)
ErrorCollection errorsImplicit = ex;
}
License
This project is licensed under the terms specified in the LICENSE.txt file.
Benchmarks
The project is highly optimized for performance. The following numbers come from BenchmarkDotNet ShortRun on Apple M5, measuring the current library behavior in .NET 8.0, .NET 9.0, and .NET 10.0:
| Method | .NET 8.0 | .NET 9.0 | .NET 10.0 | Allocated Memory |
|---|---|---|---|---|
AnyErrorProperty |
4.40 ns |
4.79 ns |
3.42 ns |
0 B |
GetProperties |
22.58 ns |
22.85 ns |
17.52 ns |
96 B |
GetProperties_ExceptNullFilter |
24.46 ns |
23.85 ns |
18.43 ns |
96 B |
AddError |
35.52 ns |
38.15 ns |
27.51 ns |
312 B |
AddMultipleErrors |
76.79 ns |
61.48 ns |
57.39 ns |
392-472 B |
AddMultipleErrors_WithSanitization |
62.20 ns |
61.96 ns |
60.24 ns |
384 B |
GetErrors_SingleProperty |
55.26 ns |
44.06 ns |
34.37 ns |
232 B |
GetErrors |
132.85 ns |
137.84 ns |
114.05 ns |
360-832 B |
GetErrors_ExceptNullFilter |
127.64 ns |
136.32 ns |
113.87 ns |
360-832 B |
GetEntries_ExceptNullFilter |
169.20 ns |
185.23 ns |
119.78 ns |
872 B |
CreateFromException_Shallow |
35.52 ns |
38.53 ns |
25.85 ns |
312 B |
CreateFromException_Deep |
69.23 ns |
81.48 ns |
55.45 ns |
408 B |
DeserializeJson_WithSanitization |
859.33 ns |
785.90 ns |
629.56 ns |
1912-1944 B |
Notes:
.NET 10.0is the fastest runtime in almost every measured scenario.- The new defensive behaviors (
nullfilters, sanitization, shallow/deep exception handling) remain in the same performance tier as the previous API surface. AnyErroris so fast that withShortRunit collapses to0.000 ns; treat it as effectively sub-nanosecond rather than literally zero.
Author
Sebastián Martínez Pérez - Pitasoft, S.L.
Español
Pitasoft.Error es una biblioteca de clases diseñada para facilitar el manejo y la gestión de colecciones de errores en aplicaciones .NET. Permite agrupar errores por propiedad, manejar notificaciones de cambios y serializar/deserializar colecciones de errores a JSON.
Características principales
- ErrorCollection: Una colección robusta para almacenar errores asociados a nombres de propiedades.
- Propiedades de Factoría: Accede a
ErrorCollection.Emptypara obtener una nueva instancia vacía de la colección. - IErrorCollectionContainer: Interfaz estándar para objetos (como ViewModels) que exponen una
ErrorCollection. - Alcance del Paquete NuGet: El paquete NuGet contiene solo la biblioteca de clases (no incluye aplicaciones de ejemplo UI).
- Alto Rendimiento: Optimizada para minimizar las asignaciones de memoria y maximizar la velocidad, especialmente en operaciones de filtrado y limpieza masiva. Utiliza
[MethodImpl(MethodImplOptions.AggressiveInlining)]en rutas críticas y ha sido refactorizada para eliminar la sobrecarga de LINQ en los métodos principales. - Filtrado Avanzado: Recuperación flexible de errores mediante operaciones
IntersectoExceptconJointActionErrors. - Soporte INotifyDataErrorInfo: Implementa completamente
INotifyDataErrorInfopara una integración fluida con WPF, Avalonia UI, MAUI y otros marcos de trabajo con enlace de datos. - Notificaciones: Soporte para eventos cuando la colección de errores cambia.
- Conteo Total de Errores: La propiedad
Countdevuelve el número total de mensajes de error individuales en todas las propiedades. - Serialización JSON: Extensiones integradas para convertir colecciones de errores a JSON y viceversa.
- Construcción y Deserialización Más Seguras: Las copias por constructor no comparten listas mutables y la deserialización JSON ignora entradas inválidas (claves vacías o listas nulas/vacías).
- Métodos de extensión con expresiones lambda: Selección tipada de propiedades mediante
Expression<Func<T, object?>>(sin cadenas mágicas). - Soporte Multi-target: Compatible con .NET 8.0, .NET 9.0 y .NET 10.0.
- Fluent API: Métodos encadenables para una configuración rápida y legible de las colecciones de errores.
- Detección de Errores de Excepción: Extracción automática de mensajes de error de jerarquías de excepciones complejas.
Instalación
Puedes instalar este paquete a través de NuGet:
dotnet add package Pitasoft.Error
Alcance del paquete y compatibilidad UI
- El paquete NuGet
Pitasoft.Errordistribuye exclusivamente la biblioteca de manejo de errores. - Está diseñado para trabajar con
INotifyDataErrorInfo, por lo que es compatible con WPF, MAUI, Avalonia UI y otros frameworks orientados a MVVM.
Novedades / capacidades actuales
ErrorCollection.Emptydevuelve una instancia nueva en cada acceso (sin estado mutable compartido).Countdevuelve el total de mensajes individuales entre todas las propiedades.Clear(...)yClearAll(...)son las APIs recomendadas (Remove*se mantiene por compatibilidad y está obsoleto).- Las extensiones con lambda eliminan cadenas mágicas (
Add<T>,AnyError<T>,Clear<T>yCreate<T>). - La serialización JSON se puede personalizar con
SetJsonSerializerOptions(...)para el flujo async actual sin cambiar los valores predeterminados del proceso. - Constructores y convertidor JSON normalizan/copían datos para evitar listas compartidas y entradas inválidas.
- Las entradas
nullen constructores y APIs bulk se tratan de forma defensiva como vacío/no-op, y los mensajes de errornull/vacíos/con espacios se ignoran. Combine(...)fusiona colecciones anulables sin comprobaciones de null adicionales (devuelvenullsolo si ambas son nulas/vacías).WithPrefix(...)crea una nueva colección con claves prefijadas (útil para objetos anidados comoDireccion.Calle).- Las APIs basadas en excepciones son superficiales por defecto; usa
deep: truecuando quieras incluir recursivamente las excepciones internas.
Nuevos comportamientos añadidos (detallado)
1) Copia segura en constructores
Al crear una colección desde diccionarios/listas existentes, las listas internas se copian para evitar mutaciones compartidas accidentales.
var source = new List<string> { "E1" };
var ec = new ErrorCollection(new[]
{
new KeyValuePair<string, List<string>>("Nombre", source)
});
source.Add("E2"); // no modifica ec
2) Deserialización JSON defensiva
La deserialización ignora entradas inválidas (claves vacías, listas null, listas vacías) y conserva solo errores válidos.
var json = "{\"Valido\":[\"E1\"],\"Vacio\":[],\"Nulo\":null,\" \":[\"Ignorado\"]}";
var ec = json.ToErrorCollection();
// Solo queda "Valido"
3) Entrada JSON nula/vacía segura
ToErrorCollection devuelve siempre una instancia no nula para entrada null o vacía.
string? raw = null;
var ec = raw.ToErrorCollection(); // colección vacía, nunca null
4) Manejo defensivo de null en APIs bulk
Las operaciones bulk de añadir, limpiar y filtrar tratan null como vacío/no-op en lugar de lanzar.
var ec = new ErrorCollection();
ec.Add("Nombre", (string[]?)null); // no-op
ec.Clear((IEnumerable<string>?)null); // no-op
var todos = ec.GetErrors(JointActionErrors.Except, (string[]?)null);
5) Los valores por defecto al tratar excepciones son explícitos
Los métodos de creación y conversión basados en excepciones añaden solo el mensaje de la excepción superior por defecto. Usa deep: true para incluir toda la cadena de excepciones internas.
var superficial = ErrorCollection.Create(ex); // solo mensaje principal
var profunda = ErrorCollection.Create(ex, deep: true); // incluye inner exceptions
Uso en backend (Minimal API / Web API)
Pitasoft.Error también es útil en servicios backend para estandarizar validaciones y respuestas de error.
using Microsoft.AspNetCore.Mvc;
using Pitasoft.Error;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapPost("/usuarios", ([FromBody] CrearUsuarioRequest request) =>
{
var errors = ErrorCollection.Empty;
if (string.IsNullOrWhiteSpace(request.Email))
errors.Add(nameof(request.Email), "El email es obligatorio.");
if (!request.AceptaTerminos)
errors.Add(nameof(request.AceptaTerminos), "Debes aceptar los términos.");
if (errors.HasErrors)
return Results.ValidationProblem(
errors.ToDictionary(kv => kv.Key, kv => kv.Value.ToArray()));
return Results.Created($"/usuarios/{Guid.NewGuid()}", new { ok = true });
});
app.Run();
public record CrearUsuarioRequest(string? Email, bool AceptaTerminos);
Uso básico
Tabla rápida de API
| API | Propósito | Ejemplo |
|---|---|---|
ErrorCollection.Empty |
Crear una colección vacía nueva | var errors = ErrorCollection.Empty; |
Add(...) |
Agregar uno o varios errores | errors.Add("Email", "Obligatorio"); |
Clear(...) / ClearAll() |
Limpiar errores por propiedad o todos | errors.Clear("Email"); |
AnyError(...) |
Comprobaciones rápidas de errores | errors.AnyError("Email"); |
GetErrors(...) |
Obtener mensajes de error | errors.GetErrors(); |
GetProperties(...) |
Obtener propiedades con errores | errors.GetProperties(); |
FirstOrDefault(...) |
Obtener el primer error global o por propiedad | errors.FirstOrDefault("Email"); |
ToJson() / ToErrorCollection() |
Serializar/deserializar errores | var json = errors.ToJson(); |
Add<T>(...) / Clear<T>(...) |
Helpers tipados con lambda | errors.Add<UsuarioVm>(x => x.Email, "Obligatorio"); |
ErrorCollection.Create(...) |
Métodos factoría para creación rápida | var e = ErrorCollection.Create("General", "Fallo"); |
Acceso rápido a errores
// Obtener el primer mensaje de error de todas las propiedades (útil para resúmenes en la UI)
string? primerError = errors.FirstOrDefault();
// Comprobar errores en una propiedad específica (comprobación rápida O(1))
bool tieneErroresNombre = errors.AnyError("Nombre");
Crear una colección de errores, añadir y limpiar errores
using Pitasoft.Error;
// Crear mediante constructor o propiedad de factoría
var errors = ErrorCollection.Empty;
// Fluent API: encadenar adiciones de forma fluida
errors.Add("Nombre", "El nombre es obligatorio.")
.Add("Email", "El formato del email no es válido.");
// Count devuelve el número total de mensajes de error individuales (2 en este caso)
int totalErrores = errors.Count;
// Soporte para múltiples errores a la vez (params string[])
errors.Add("Estado", "Valor inválido", "Fuera de rango");
if (errors.AnyError())
{
// Manejar errores de forma global
}
// Limpiar errores de una propiedad específica
errors.Clear("Nombre");
// Limpiar todos los errores
errors.ClearAll();
Nota: Los métodos
RemoveyRemoveAllhan sido marcados como obsoletos en favor deClearyClearAllpara mantener la consistencia con las colecciones estándar de .NET.
Métodos sin notificaciones
Si necesitas añadir errores sin disparar el evento ErrorsChanged:
errors.AddWithoutNotification("PropiedadSilenciosa", "Este error no disparará un evento.");
Integración con Frameworks de UI (MVVM)
ErrorCollection está diseñado para integrarse fácilmente en ViewModels. Puedes implementar IErrorCollectionContainer para proporcionar una forma estándar de exponer errores:
public class MiViewModel : INotifyDataErrorInfo, IErrorCollectionContainer
{
public ErrorCollection Errors { get; set; } = new();
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
{
add => Errors.ErrorsChanged += value;
remove => Errors.ErrorsChanged -= value;
}
public System.Collections.IEnumerable GetErrors(string? propertyName) =>
((INotifyDataErrorInfo)Errors).GetErrors(propertyName);
public bool HasErrors => Errors.HasErrors;
private string _nombre;
public string Nombre
{
get => _nombre;
set
{
_nombre = value;
ValidarNombre();
}
}
private void ValidarNombre()
{
Errors.Clear(nameof(Nombre));
if (string.IsNullOrWhiteSpace(Nombre))
Errors.Add(nameof(Nombre), "El nombre es obligatorio");
}
}
Ejemplo de compatibilidad INotifyDataErrorInfo (WPF / MAUI / Avalonia)
ErrorCollection ya implementa INotifyDataErrorInfo, así que el ViewModel puede delegar directamente:
public sealed class LoginViewModel : INotifyDataErrorInfo
{
public ErrorCollection Errors { get; } = new();
public bool HasErrors => Errors.HasErrors;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged
{
add => Errors.ErrorsChanged += value;
remove => Errors.ErrorsChanged -= value;
}
public System.Collections.IEnumerable GetErrors(string? propertyName) =>
((INotifyDataErrorInfo)Errors).GetErrors(propertyName);
public void ValidarUsuario(string? nombreUsuario)
{
Errors.Clear(nameof(NombreUsuario));
if (string.IsNullOrWhiteSpace(nombreUsuario))
{
Errors.Add(nameof(NombreUsuario), "El nombre de usuario es obligatorio.");
}
}
public string? NombreUsuario { get; set; }
}
Filtrado Avanzado
Puedes filtrar errores usando JointActionErrors para incluir o excluir propiedades específicas:
// Obtener errores solo para propiedades específicas
var erroresRelevantes = errors.GetErrors(JointActionErrors.Intersect, "Usuario", "Password");
// Obtener errores para todas las propiedades EXCEPTO las especificadas
var otrosErrores = errors.GetErrors(JointActionErrors.Except, "AceptaTerminos");
// Comprobar si existe algún error para propiedades específicas
bool tieneErroresCriticos = errors.AnyError(JointActionErrors.Intersect, "Sistema", "BaseDeDatos");
Uso de métodos estáticos de creación
var errors = ErrorCollection.Create("General", "Ha ocurrido un error inesperado.");
Serialización a JSON
using Pitasoft.Error.Extensions;
string json = errors.ToJson();
Deserialización desde JSON
string json = "{\"nombre\":[\"Error 1\"]}";
var errors = json.ToErrorCollection();
Extensiones con expresiones lambda
Estas utilidades residen en el espacio de nombres Pitasoft.Error.Extensions.
using Pitasoft.Error;
using Pitasoft.Error.Extensions;
var errors = new ErrorCollection();
// Añadir errores sin cadenas mágicas
errors.Add<MiViewModel>(vm => vm.Nombre, "El nombre es obligatorio");
errors.Add<MiViewModel>(vm => vm.Email, "Formato inválido", "Dominio no permitido");
// Consultar errores
bool tieneErroresEmail = errors.AnyError<MiViewModel>(vm => vm.Email);
// Consultas conjuntas (Intersect/Except)
bool algunCampoUsuarioConErrores = errors.AnyError<MiViewModel>(JointActionErrors.Intersect,
vm => vm.Nombre, vm => vm.Email);
// Limpiar por expresión de propiedad
errors.Clear<MiViewModel>(vm => vm.Email);
errors.Clear<MiViewModel>(vm => vm.Nombre, vm => vm.Email);
errors.Clear<MiViewModel>(notification: false, vm => vm.Nombre);
// Factorías
var creado = ErrorCollectionExtensions.Create<MiViewModel>(vm => vm.Nombre, "Requerido");
// Combinar colecciones anulables
ErrorCollection? erroresServidor = ErrorCollection.Create("Email", "Ya está en uso");
ErrorCollection? erroresModelo = null;
var combinada = erroresModelo.Combine(erroresServidor);
// Prefijar claves para validación de objetos anidados
var erroresDireccion = ErrorCollection.Create("Calle", "La calle es obligatoria")
.WithPrefix("Direccion"); // la clave pasa a ser "Direccion.Calle"
Nota: Las lambdas están implementadas como métodos de extensión para mantener
ErrorCollectionligera. El rendimiento es comparable a las sobrecargas basadas en cadenas para uso MVVM típico.
Notificaciones de Eventos
Puedes suscribirte a los cambios en la colección de errores:
var errors = new ErrorCollection();
errors.ErrorsChanged += (sender, e) =>
{
Console.WriteLine($"La propiedad '{e.PropertyName}' ha sido actualizada.");
};
errors.Add("Usuario", "Ya existe.");
Manejo de Excepciones
Convierte fácilmente excepciones en colecciones de errores:
try
{
// Código que lanza una excepción
}
catch (Exception ex)
{
// El comportamiento por defecto es superficial
var errors = ErrorCollection.Create(ex);
// Si quieres capturar toda la cadena, indícalo explícitamente
var deepErrors = ErrorCollection.Create(ex, deep: true);
// O usando el método de extensión
var errorsExt = ex.ToErrorCollection(deep: true);
// O usando el operador implícito (también superficial por defecto)
ErrorCollection errorsImplicit = ex;
}
Licencia
Este proyecto está bajo la licencia especificada en el archivo LICENSE.txt.
Benchmarks
El proyecto está altamente optimizado. Los siguientes números provienen de BenchmarkDotNet ShortRun sobre Apple M5, midiendo el comportamiento actual de la librería en .NET 8.0, .NET 9.0 y .NET 10.0:
| Método | .NET 8.0 | .NET 9.0 | .NET 10.0 | Memoria Asignada |
|---|---|---|---|---|
AnyErrorProperty |
4.40 ns |
4.79 ns |
3.42 ns |
0 B |
GetProperties |
22.58 ns |
22.85 ns |
17.52 ns |
96 B |
GetProperties_ExceptNullFilter |
24.46 ns |
23.85 ns |
18.43 ns |
96 B |
AddError |
35.52 ns |
38.15 ns |
27.51 ns |
312 B |
AddMultipleErrors |
76.79 ns |
61.48 ns |
57.39 ns |
392-472 B |
AddMultipleErrors_WithSanitization |
62.20 ns |
61.96 ns |
60.24 ns |
384 B |
GetErrors_SingleProperty |
55.26 ns |
44.06 ns |
34.37 ns |
232 B |
GetErrors |
132.85 ns |
137.84 ns |
114.05 ns |
360-832 B |
GetErrors_ExceptNullFilter |
127.64 ns |
136.32 ns |
113.87 ns |
360-832 B |
GetEntries_ExceptNullFilter |
169.20 ns |
185.23 ns |
119.78 ns |
872 B |
CreateFromException_Shallow |
35.52 ns |
38.53 ns |
25.85 ns |
312 B |
CreateFromException_Deep |
69.23 ns |
81.48 ns |
55.45 ns |
408 B |
DeserializeJson_WithSanitization |
859.33 ns |
785.90 ns |
629.56 ns |
1912-1944 B |
Notas:
.NET 10.0es la runtime más rápida en casi todos los escenarios medidos.- Los nuevos comportamientos defensivos (
nullen filtros, saneado, tratamiento superficial/profundo de excepciones) se mantienen en la misma liga de rendimiento que la API previa. AnyErrores tan rápido que conShortRunaparece como0.000 ns; interprétalo como coste subnanosegundo, no como cero literal.
Autor
Sebastián Martínez Pérez
| 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 is compatible. 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 is compatible. 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Pitasoft.Error:
| Package | Downloads |
|---|---|
|
Pitasoft.Result
.NET library designed to standardize responses from REST services and internal application layers. It provides a robust set of classes to wrap data, status codes, and error collections, facilitating a unified communication contract between APIs, services, and clients. |
|
|
Pitasoft.Validation
A flexible and high-performance C# validation library designed to validate objects using multiple strategies, including Data Annotations, custom rules, and a fluent API. |
|
|
Pitasoft.Web
Librerias basicas de aplicaciones web |
|
|
Pitasoft.AspNetCore
Librerias basicas de aplicaciones web |
|
|
Pitasoft.Blazor.Validation
Blazor components for data validation in forms. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.3.14 | 679 | 3/23/2026 |
| 5.3.13 | 119 | 3/23/2026 |
| 5.3.12 | 109 | 3/23/2026 |
| 5.3.11 | 136 | 3/22/2026 |
| 5.3.10 | 108 | 3/22/2026 |
| 5.3.9 | 112 | 3/22/2026 |
| 5.3.8 | 188 | 3/20/2026 |
| 5.3.7 | 228 | 3/13/2026 |
| 5.3.6 | 181 | 3/10/2026 |
| 5.3.5 | 120 | 3/10/2026 |
| 5.3.4 | 109 | 3/10/2026 |
| 5.3.3 | 112 | 3/9/2026 |
| 5.3.2 | 310 | 3/2/2026 |
| 5.3.1 | 255 | 2/24/2026 |
| 5.2.2 | 184 | 2/23/2026 |
| 5.2.1 | 115 | 2/22/2026 |
| 5.1.1 | 122 | 2/17/2026 |
| 5.0.3 | 174 | 2/4/2026 |
| 5.0.2 | 188 | 1/26/2026 |
| 5.0.1 | 128 | 1/26/2026 |