Event Contract Resolver
El EventContractResolver
es una clase que extiende DefaultContractResolver
de Newtonsoft.Json, diseñada para personalizar la serialización de eventos de dominio. Su propósito principal es controlar qué propiedades de un evento de dominio se serializan a JSON, ofreciendo flexibilidad para omitir propiedades no deseadas y aplicar convenciones de nombres.
La implementación del EventContractResolver
se basa en la herencia de DefaultContractResolver
. La lógica principal reside en los métodos sobrescritos CreateProperty
y ResolvePropertyName
.
CreateProperty
realiza la comprobación para las propiedades a ignorar y establece la lógica para que no se serialicen. También asegura que las propiedades no escritas sean accesibles si tienen un setter público o privado.ResolvePropertyName
convierte el primer carácter de la propiedad a minúscula, proporcionando un formato de nombre de propiedad específico.
namespace CodeDesignPlus.Net.Serializers;
/// <summary>/// Contract resolver for the serialization of domain events./// </summary>public class EventContractResolver : DefaultContractResolver{ /// <summary> /// The property names to ignore during serialization. /// </summary> private readonly string[] propertyNamesToIgnore;
/// <summary> /// Initializes a new instance of the <see cref="EventContractResolver"/> class with default properties to ignore. /// </summary> public EventContractResolver() { this.propertyNamesToIgnore = new[] { nameof(IDomainEvent.EventId), nameof(IDomainEvent.OccurredAt), nameof(IDomainEvent.Metadata) }; }
/// <summary> /// Initializes a new instance of the <see cref="EventContractResolver"/> class with specified properties to ignore. /// </summary> /// <param name="propertyNamesToIgnore">The property names to ignore during serialization.</param> public EventContractResolver(string[] propertyNamesToIgnore) { this.propertyNamesToIgnore = propertyNamesToIgnore; }
/// <summary> /// Creates a <see cref="JsonProperty"/> for the given <see cref="MemberInfo"/>. /// </summary> /// <param name="member">The member to create a <see cref="JsonProperty"/> for.</param> /// <param name="memberSerialization">The member's parent <see cref="MemberSerialization"/>.</param> /// <returns>A created <see cref="JsonProperty"/> for the given <see cref="MemberInfo"/>.</returns> protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization);
if (!property.Writable) { property.Writable = (member as PropertyInfo).GetSetMethod(true) != null; }
if (!typeof(DomainEvent).IsAssignableFrom(member.DeclaringType)) return property;
if (propertyNamesToIgnore.Contains(property.PropertyName, StringComparer.CurrentCultureIgnoreCase)) { property.ShouldSerialize = _ => false; }
return property; }
/// <summary> /// Resolves the name of the property. /// </summary> /// <param name="propertyName">The name of the property.</param> /// <returns>The resolved name of the property.</returns> protected override string ResolvePropertyName(string propertyName) { if (string.IsNullOrEmpty(propertyName)) return propertyName;
return char.ToLowerInvariant(propertyName[0]) + propertyName[1..]; }
/// <summary> /// Resolves the dictionary key. /// </summary> /// <param name="dictionaryKey">The key of the dictionary.</param> /// <returns>The resolved key of the dictionary.</returns> protected override string ResolveDictionaryKey(string dictionaryKey) { return dictionaryKey; }}
¿Cómo Funciona?
El EventContractResolver
funciona interceptando el proceso de creación de propiedades de Newtonsoft.Json durante la serialización. Cuando se serializa un objeto, el ContractResolver
se encarga de crear JsonProperty
para cada miembro del objeto. Este ContractResolver
personalizado revisa cada propiedad, y si cumple ciertas condiciones (como pertenecer a una clase que implementa IDomainEvent
), aplica reglas específicas:
- Ocultar propiedades: Ignora las propiedades
EventId
,OccurredAt
yMetadata
por defecto, o las propiedades especificadas en el constructor. - Cambio de nombres: Convierte la primera letra del nombre de la propiedad a minúscula.
Métodos
EventContractResolver
Type: public EventContractResolver()
Este constructor inicializa una nueva instancia de la clase EventContractResolver
con la configuración predeterminada para ignorar las propiedades comunes en eventos de dominio: EventId
, OccurredAt
, y Metadata
.
EventContractResolver
Type: public EventContractResolver(string[] propertyNamesToIgnore)
Este constructor permite personalizar la lista de propiedades que serán ignoradas durante la serialización, ofreciendo más flexibilidad en casos de uso específicos.
CreateProperty
Type: protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
Este método, sobrescrito de la clase base DefaultContractResolver
, es el núcleo de la personalización del EventContractResolver
. Se encarga de crear una instancia de JsonProperty
para un miembro específico, aplicando reglas personalizadas antes de que Newtonsoft.Json proceda con la serialización.
ResolvePropertyName
Type: protected override string ResolvePropertyName(string propertyName)
Este método, sobrescrito de la clase base DefaultContractResolver
, se encarga de transformar el nombre de la propiedad antes de que se utilice en la cadena JSON.
ResolveDictionaryKey
Type: protected override string ResolveDictionaryKey(string dictionaryKey)
Este método, sobrescrito de la clase base DefaultContractResolver
, define cómo se manejan las claves de los diccionarios durante la serialización.
Ejemplo de Uso
using CodeDesignPlus.Net.Serializers;using Newtonsoft.Json;using System;using System.Collections.Generic;
// Define un evento de dominio (puedes tener tu propia clase base)public interface IDomainEvent{ Guid EventId { get; } DateTime OccurredAt { get; } Dictionary<string, object> Metadata { get; }}
public class DomainEvent : IDomainEvent{ public Guid EventId { get; } public DateTime OccurredAt { get; } public Dictionary<string, object> Metadata { get; } public DomainEvent() { EventId = Guid.NewGuid(); OccurredAt = DateTime.UtcNow; Metadata = new Dictionary<string, object>(); }}
public class UserCreatedEvent : DomainEvent{ public string UserName { get; set; } public string Email { get; set; }}
public class Example{ public static void Main(string[] args) { var userCreatedEvent = new UserCreatedEvent { UserName = "Test User", Email = "test@test.com" };
// Serializar el evento de dominio usando el EventContractResolver var settings = new JsonSerializerSettings { ContractResolver = new EventContractResolver() };
var json = JsonSerializer.Serialize(userCreatedEvent, settings);
Console.WriteLine(json); // Resultado: {"userName":"Test User","email":"test@test.com"}
// Serializar el evento de dominio sin EventContractResolver var settings2 = new JsonSerializerSettings(); var json2 = JsonSerializer.Serialize(userCreatedEvent, settings2); Console.WriteLine(json2); // Resultado: {"EventId":"guid","OccurredAt":"datetime","Metadata":{},"UserName":"Test User","Email":"test@test.com"} }}
Conclusiones
- El
EventContractResolver
proporciona un control detallado sobre qué propiedades se serializan de los eventos de dominio, permitiendo excluir metadatos no necesarios. - La capacidad de modificar los nombres de las propiedades proporciona flexibilidad para seguir convenciones de nombres específicas.
- La clase
EventContractResolver
es reutilizable y puede adaptarse a diferentes tipos de eventos de dominio con la especificación de propiedades adicionales a ignorar en el constructor. - Se recomienda usar el
EventContractResolver
en conjunto con elJsonSerializer
para serializar eventos de dominio.