Skip to content

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:

  1. Ocultar propiedades: Ignora las propiedades EventId, OccurredAt y Metadata por defecto, o las propiedades especificadas en el constructor.
  2. 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 el JsonSerializer para serializar eventos de dominio.

Referencias Adicionales