对于我正在开发的分散项目,我们需要能够使用服务总线异步传递C#应用程序之间的数据,并决定使用JSON(使用Newtonsoft.Json
库)作为消息体格式,以便我们可以将来使用其他语言而不必担心语言之间的类型差异。
我们有一个共享的接口库,这样两端都可以随意处理消息,只要它们实现了所需的接口即可。这需要一个自定义JsonConverter
来处理接口类型的集合,但是每次添加新的接口类型时我们都不想更新它,因此我们编写了一个允许这样做的自定义序列化器。 / p>
这已经有一年多的工作没有问题了。上周我对共享库中的不同类型进行了一些小改动,与序列化器和转换器类型无关,现在我们尝试反序列化的每条消息都会因为方法调用循环导致 StackOverflow 在循环中调用3个方法)。以下是相关代码:
/// <summary>
/// Custom Converter that allows mapping of interface types to concrete
/// types for deserialization.
/// </summary>
/// <remarks>
/// Instead of needing to create multiple JsonConverter classes
/// for each scenario, you can just call the <see cref="AddTypeMapping"/>
/// method to specify a mapping between interface and concrete type.
/// </remarks>
public class CustomJsonConverter : JsonConverter
{
private Dictionary<string, Type> _map;
public CustomJsonConverter()
{
_map = new Dictionary<string, Type>();
}
/// <summary>
/// Adds a new interface to concrete type mapping to the converter.
/// </summary>
/// <param name="interfaceTypeName">Interface type to create a mapping for.</param>
/// <param name="concreteType">Concrete type to use for interface type.</param>
public void AddTypeMapping( string interfaceTypeName, Type concreteType )
{
_map.Add( interfaceTypeName, concreteType );
}
#region JsonConverter Overrides
/// <summary>
/// Determines if we can successfully convert an object of the
/// specified type.
/// </summary>
/// <param name="objectType">Type to convert.</param>
/// <returns>
/// true if a conversion is possible; false otherwise.
/// </returns>
public override bool CanConvert( Type objectType )
{
if ( _map.Count == 0 || _map.ContainsKey( objectType.FullName ) )
{
return true;
}
return false;
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can write JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can write JSON; otherwise, <c>false</c>.</value>
/// <remarks>
/// In this case (inside a custom converter), we need to return <c>false</c>
/// otherwise we end up in a 'self referencing loop error' when handing
/// control back to the default JsonSerializer.
/// </remarks>
public override bool CanWrite
{
get { return false; }
}
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can read JSON; otherwise, <c>false</c>.</value>
public override bool CanRead
{
get { return true; } // THIS SEEMS TO BE THE ERROR, BUT WHY?
}
/// <summary>
/// Parses the supplied JSON data into the proper types, applying conversions where neccessary.
/// </summary>
/// <param name="reader">JSON data reader instance.</param>
/// <param name="objectType">Type to convert the JSON data into.</param>
/// <param name="existingValue"></param>
/// <param name="serializer">Instance of the serializer to use.</param>
/// <returns></returns>
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
{
if ( _map.Count == 0 )
{
return serializer.Deserialize( reader, objectType );
}
if ( _map.ContainsKey( objectType.FullName ) )
{
return serializer.Deserialize( reader, _map[objectType.FullName] );
}
throw new NotSupportedException( string.Format( "No mapping for Type '{0}' found.", objectType.FullName ) );
}
/// <summary>
/// Writes the supplied object into the JSON data stream.
/// </summary>
/// <param name="writer">JSON data writer instance.</param>
/// <param name="value">Object to serialize into a JSON data stream.</param>
/// <param name="serializer">Instance of the serializer to use.</param>
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
serializer.Serialize( writer, value );
}
#endregion
}
/// <summary>
/// Manages the serialization and deserialization of objects to and from JSON.
/// </summary>
/// <typeparam name="T">The object type to be de/serialized.</typeparam>
public class MessageSerializer<T> where T : class
{
/// <summary>Serializer instance.</summary>
private readonly JsonSerializer _serializer;
/// <summary>Custome converter instance.</summary>
private readonly CustomJsonConverter _converter;
public MessageSerializer()
{
_converter = new CustomJsonConverter();
_serializer = new JsonSerializer
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.None,
};
_serializer.Converters.Add( _converter );
_serializer.Converters.Add( new GuidJsonConverter() );
}
#region Converters
/// <summary>
/// Adds a new interface to concrete type mapping in order to allow
/// deserialization of JSON data involving an interface.
/// </summary>
/// <param name="interfaceType">The interface type to map to concrete type.</param>
/// <param name="concreteType">The concrete type to use for the interface.</param>
public void MapType( Type interfaceType, Type concreteType )
{
_converter.AddTypeMapping( interfaceType.FullName, concreteType );
}
#endregion
#region Serialization
/// <summary>
/// Serializes an object to a JSON string.
/// </summary>
/// <param name="message">The type to be serialized.</param>
/// <returns>
/// The JSON equivalent of the supplied type.
/// </returns>
public string Serialize( T message )
{
var result = new StringBuilder();
StringWriter sw = null;
try
{
sw = new StringWriter( result );
using ( var writer = new JsonTextWriter( sw ) )
{
_serializer.Serialize( writer, message );
}
}
finally
{
if ( sw != null )
{
sw.Dispose();
}
}
return result.ToString();
}
/// <summary>
/// Deserializes a JSON string to it's original type.
/// </summary>
/// <param name="message">The JSON string to deserialize.</param>
/// <returns>
/// Instance of Object Type from the supplied JSON string.
/// </returns>
public T Deserialize( string message )
{
T obj;
StringReader sr = null;
try
{
sr = new StringReader( message );
using ( var reader = new JsonTextReader( sr ) )
{
obj = _serializer.Deserialize<T>( reader );
}
}
finally
{
if (sr != null)
{
sr.Dispose();
}
}
return obj;
}
#endregion
}
// SAMPLE USAGE:
var ser = new MessageSerializer<EmailMessage>();
ser.MapType( typeof(IEmailMessage), typeof(EmailMessage) );
var jsonData = ser.Serialize( data ); // works fine
var deserializedData = ser.Deserialize( jsonData ); // StackOverflow occurs during this call
问题似乎集中在CustomJsonConverter.CanRead
属性上。这之前已经为所有事情返回true
并且工作正常。现在它只有在我将其设置为返回false
(仍在测试以完全确认)时才会起作用。
真正奇怪的是,这个属性开始返回false
,直到差不多一年前它开始失败,直到我把它换成当前值true
。现在好像我必须把它改回false
(直到明年)?我错过了什么?
答案 0 :(得分:1)
事实证明这很简单......这就是NuGet的错! (真的是我的。)
用于处理使用服务总线进行消息传递的库是作为内部NuGet包构建的,以便在更新可用时让每个人都可以轻松更新(并且自最初创建以来我们对此库的更改很少)。 / p>
原始邮件没有任何属于接口类型的属性,因此CanRead
属性返回false
工作正常,任何服务处理程序都使用 版本1 的NuGet包。从不需要更新版本的NuGet包,因为服务也没有改变。
除非CanRead
属性返回true
并且处理这些消息的任何新服务处理程序都使用了 版本2 >,否则具有属性接口的较新消息将无效NuGet包的em> 。
我们需要对影响所有服务处理程序的核心消息传递库进行更改,以便在更新到最新版本时使用 版本1 已停止工作。所以,我更新了CanRead
的逻辑,如下所示,一切都在愉快地工作:
/// <summary>
/// Gets a value indicating whether this <see cref="JsonConverter"/> can read JSON.
/// </summary>
/// <value><c>true</c> if this <see cref="JsonConverter"/> can read JSON; otherwise, <c>false</c>.</value>
public override bool CanRead
{
get { return _map.Count > 0; }
}
这总是很简单...