0

after using now the Resolver which I've created here with the help of dbc it doesn't work anymore properly.

I get always the following Exception:

Error reading object reference '87'. Path '$values[1].EntityB.Bar', line 1, position 49698.

Inner Exception:

A different Id has already been assigned for value 'EntityB'. This error may be caused by an object being reused multiple times during deserialization and can be fixed with the setting ObjectCreationHandling.Replace.

I also tried the suggested ObjectCreationHandling but it doesn't work.

I've tried to add lock statements around the add and the get Methods but it still does not fix the issue.

Below the Resolver as mentioned in the other post here:

public abstract class EquivalencingReferenceResolver : IReferenceResolver
{
    private readonly IReferenceResolver _defaultResolver;

    public string GetReference(object context, object value)
    {
        var representative = this.GetOrAddRepresentativeObject(value);
        return this._defaultResolver.GetReference(context, representative);
    }

    public bool IsReferenced(object context, object value)
    {
        return this.TryGetRepresentativeObject(value, out var representative) && this._defaultResolver.IsReferenced(context, representative);
    }

    public object ResolveReference(object context, string reference)
    {
        return this._defaultResolver.ResolveReference(context, reference);
    }

    public void AddReference(object context, string reference, object value)
    {
        var representative = this.GetOrAddRepresentativeObject(value);
        this._defaultResolver.AddReference(context, reference, representative);
    }

    protected EquivalencingReferenceResolver(IReferenceResolver defaultResolver)
    {
        this._defaultResolver = defaultResolver ?? throw new ArgumentNullException();
    }

    protected virtual bool TryGetRepresentativeObject(object obj, out object representative)
    {
        representative = obj;
        return true;
    }

    protected virtual object GetOrAddRepresentativeObject(object obj)
    {
        return obj;
    }
}

And the concrete implementation:

public class SelectiveValueEqualityReferenceResolver : EquivalencingReferenceResolver
{
    private readonly Dictionary<Type, Dictionary<object, object>> _representatives;

    public SelectiveValueEqualityReferenceResolver(IReferenceResolver defaultResolver)
        : base(defaultResolver)
    {
        this._representatives = new Dictionary<Type, Dictionary<object, object>>();
    }

    protected override bool TryGetRepresentativeObject(object obj, out object representative)
    {
        var type = obj.GetType();
        if (type.GetTypeInfo().IsClass && this._representatives.TryGetValue(type, out var typedItems))
            return typedItems.TryGetValue(obj, out representative);

        return base.TryGetRepresentativeObject(obj, out representative);
    }

    protected override object GetOrAddRepresentativeObject(object obj)
    {
        var type = obj.GetType();

        if (!type.GetTypeInfo().IsClass)
            return base.GetOrAddRepresentativeObject(obj);

        if (!this._representatives.TryGetValue(type, out var typedItems))
        {
            typedItems = new Dictionary<object, object>();
            this._representatives.Add(type, typedItems);
        }

        if (!typedItems.TryGetValue(obj, out var representative))
            representative = typedItems[obj] = obj;

        return representative;
    }
}

The Serialization class:

public static class Serialization
{
    private static readonly ILogger Log = Serilog.Log.ForContext(typeof(Serialization));

    public static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.All,
        NullValueHandling = NullValueHandling.Ignore,
        FloatParseHandling = FloatParseHandling.Decimal,
        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
        DateTimeZoneHandling = DateTimeZoneHandling.Local,
        Formatting = Formatting.None,
        ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
        ReferenceResolverProvider = () => new CustomReferenceResolver(new JsonSerializer().ReferenceResolver),
        Error = (sender, args) => Log.Error(args.ErrorContext.Error, $"Error while (de)serializing: {args.ErrorContext}; object: {args.CurrentObject}")
    };

    private static readonly JsonSerializerSettings SimpleJsonSerializerSettings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.None,
        NullValueHandling = NullValueHandling.Ignore,
        FloatParseHandling = FloatParseHandling.Decimal,
        Formatting = Formatting.Indented,
        Error = (sender, args) => Log.Error(args.ErrorContext.Error, $"Error while (de)serializing: {args.ErrorContext}; object: {args.CurrentObject}")
    };

    public static string Serialize<T>(T item)
    {
        return Serialize(item, JsonSerializerSettings);
    }

    public static string SerializeSimple<T>(T item)
    {
        return Serialize(item, SimpleJsonSerializerSettings);
    }

    public static string Serialize<T>(T item, JsonSerializerSettings settings)
    {
        var jsonString = JsonConvert.SerializeObject(item, settings);
        return jsonString;
    }

    public static T Deserialize<T>(string data)
    {
        return Deserialize<T>(data, JsonSerializerSettings);
    }

    public static T DeserializeSimple<T>(string data)
    {
        return Deserialize<T>(data, SimpleJsonSerializerSettings);
    }

    public static T Deserialize<T>(string data, JsonSerializerSettings settings)
    {
        try
        {
            return JsonConvert.DeserializeObject<T>(data, settings);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Exception in deserializing data.");
            throw;
        }
    }
}

My Setup where I use this is currently a Webservice. This uses RestSharp and RestSharp.Newtonsoft.Json to resolve the Response from a web service. This Webservice basically uses also Newtonsoft for the responses with the same JSON Settings. (because it is basically the same service, just on other nodes).

For the Host I use the following Configuration:

public void Configuration(IAppBuilder appBuilder)
{
    var config = new HttpConfiguration
    {
        IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
    };
    ConfigureIocContainer(appBuilder, config);
    config.MapHttpAttributeRoutes();
    config.ConfigureDefaultRoutes()
          .ConfigureSwagger(ServiceDescription.DisplayName)
          .Services.Add(typeof(IExceptionLogger), new SerilogExceptionLogger());

    appBuilder.ConfigureAuthenticationSchema(AuthenticationSchemes.Ntlm);

    config.Formatters.XmlFormatter.UseXmlSerializer = true;
    config.Formatters.JsonFormatter.SerializerSettings = Serialization.JsonSerializerSettings;

    appBuilder.UseWebApi(config);
}

So basically the same Settings in here. The Host is a Self-hosted Owin Host with Swagger.

The call to the Service is done by this:

private static string RemoteUrl = "http://localhost/";
private IRestClient GetAvailableClient()
{
    var client = new RestClient(RemoteUrl)
    {
        Authenticator = new NtlmAuthenticator(),
        RemoteCertificateValidationCallback = this.RemoteCertificateValidationCallback,
    };

    client.AddHandler("application/json", MyJsonSerializer);
    client.AddHandler("text/json", MyJsonSerializer);
    client.AddHandler("text/x-json", MyJsonSerializer);
    client.AddHandler("text/javascript", MyJsonSerializer);
    client.AddHandler("*+json", MyJsonSerializer);
    return client;
}

protected TResult ExecuteRequest<TResult>([CallerMemberName] string requestName = null, Method method = Method.GET, object parameter = null) where TResult : new()
{
    IRestResponse<TResult> result = null;
    try
    {

        var client = this.GetAvailableClient();
        var request = BuildRequest(this._serviceName, requestName, method, parameter);
        result =  client.Execute<TResult>(request);           

        if (result.IsSuccessful)
            return result.Data;

        throw GetExceptionFromResponse(result);
    }
    catch (Exception ex)
    {
        this._logger.Error(ex, $"Error while calling Remote Service {this._serviceName} with Request {requestName} and Parameter {parameter} and ResponseContent {result?.Content}");
        throw;
    }
}

When I make a breakpoint at the Exception Handling and try to deserialize it manually with Serialization.Deserialze<TResponse>(result.Content) it throws the same exception.

// Update

It seems that the Resolver isn't Threadsafe. I'm not really sure which one, but I assume, that the Internal Newtonsoft Reference Resolver isn't Threadsafe, because I've also tried it with lock statements, which also didn't solved the issue.

Below my custom implementation

public class CustomReferenceResolver : IReferenceResolver
{
    private readonly Dictionary<object, ReferencedContext> _referencedContexts = new Dictionary<object, ReferencedContext>();
    private readonly object _referencedContextsLock = new object();

    public object ResolveReference(object context, string reference)
    {
        lock (this._referencedContextsLock)
            return this._referencedContexts.ContainsKey(context) ? this._referencedContexts[context].GetValueByKey(reference) : null;
    }

    public string GetReference(object context, object value)
    {
        lock (this._referencedContextsLock)
            return this.IsReferenced(context, value) ? this._referencedContexts[context].GetKeyByValue(value) : this.AddObjectInternal(context, value);
    }

    public bool IsReferenced(object context, object value)
    {
        lock (this._referencedContexts)
            return this._referencedContexts.TryGetValue(context, out var currentContext) && currentContext.IsObjectReferenced(value);
    }

    public void AddReference(object context, string reference, object value)
    {
        this.AddObjectInternal(context, value, reference);
    }

    private string AddObjectInternal(object context, object value, string reference = null)
    {
        lock (this._referencedContextsLock)
        {
            if (this._referencedContexts.TryGetValue(context, out var referenceContext))
                return referenceContext.AddObject(value, reference);

            referenceContext = new ReferencedContext();
            this._referencedContexts.Add(context, referenceContext);

            return referenceContext.AddObject(value, reference);
        }
    }

    private class ReferencedContext
    {
        private int _idCount;
        private readonly Dictionary<string, object> _referencedObjects = new Dictionary<string, object>();
        private readonly object _referenceLock = new object();

        private string GetNextKey()
        {
            return Interlocked.Increment(ref this._idCount).ToString();
        }

        public object GetValueByKey(string key)
        {
            lock (this._referenceLock)
                return this._referencedObjects.ContainsKey(key) ? this._referencedObjects[key] : null;
        }

        public string GetKeyByValue(object value)
        {
            lock (this._referenceLock)
                return this._referencedObjects.SingleOrDefault(x => x.Value.Equals(value)).Key;
        }

        public string AddObject(object value, string key = null)
        {
            lock (this._referenceLock)
            {
                if (this.IsObjectReferenced(value))
                    return this.GetKeyByValue(value);

                if (key == null)
                    key = this.GetNextKey();

                this._referencedObjects.Add(key, value);
                return key;
            }
        }

        public bool IsObjectReferenced(object value)
        {
            lock (this._referenceLock)
                return this._referencedObjects.Any(x => x.Value.Equals(value));
        }
    }
}

Can you please tell me what's wrong with the Reference Resolver?

CodeRain
  • 69
  • 11
  • Can you share a [mcve]? We would need to see instances of the types that, when serialized, throw the exception. – dbc Jan 16 '19 at 19:43
  • Or, at the minimum, can you please [edit] your question to share the full `ToString()` output of the exception, including the exception type, message, traceback, and inner exception(s)? – dbc Jan 16 '19 at 21:19
  • The Exception is mentioned as above. Sorry for forgetting the StackTrace. My Datamodel is as mentioned in the other post. But It seems that I solved it now myself. The Resolver seems not to be threadsafe (really strange, because I make everytime a new one. I've updated my post with my solution. – CodeRain Jan 18 '19 at 07:42

0 Answers0