2016-11-21 2 views
0

Ich verwende eine DataContractJsonSerializer, um ein Objektdiagramm zu serialisieren. Wenn ich die Objekte konstruiere, erhält jeder einen Verweis auf eine Instanz eines Utility-Objekts (es ist eine Factory, um Instanzen von Unterklassen einer abstrakten Vertragsklasse zu erzeugen) - das funktioniert großartig, bis der Graph serialisiert und dann wieder deserialisiert wird, woraufhin die Objekte Sie haben keinen Bezug mehr auf das Dienstprogrammobjekt. Ich brauche diese Referenz. Wie würden Sie empfehlen, dies zu implementieren (Singletons funktionieren nicht, weil separate Graphen ihre eigene Instanz des Objekts benötigen)?DataContractJsonSerializer - teilen Sie eine Objektinstanz für das gesamte Diagramm?

Antwort

0

Eine Möglichkeit, dies zu erreichen, ist mit einer data contract surrogate. Mithilfe von Surrogate können Sie Ihre "echte" Factory während der Serialisierung durch einen Dummy-Stub ersetzen. Ersetzen Sie dann während der Deserialisierung den Dummy durch die gewünschte Fabrik.

Wenn also Ihre Klassen wie etwas aussehen:

public abstract class FactoryBase 
{ 
} 

public class Factory : FactoryBase 
{ 
} 

public interface IHasFactory 
{ 
    FactoryBase Factory { get; } 
} 

[DataContract] 
public abstract class HasFactoryBase : IHasFactory 
{ 
    [DataMember(IsRequired = true)] 
    FactoryBase factory; 

    public FactoryBase Factory { get { return factory; } } 

    public HasFactoryBase(FactoryBase factory) 
    { 
     this.factory = factory; 
    } 
} 

[DataContract] 
public class Foo : HasFactoryBase 
{ 
    public Foo(FactoryBase factory) 
     : base(factory) 
    { 
     this.Bars = new List<Bar>(); 
    } 

    [DataMember] 
    public List<Bar> Bars { get; set; } 
} 

[DataContract] 
public class Bar : HasFactoryBase 
{ 
    public Bar(FactoryBase factory) : base(factory) { } 
} 

Sie eine IDataContractSurrogate definieren alle Vorkommen von FactoryBase mit einem Surrogat zu ersetzen, wie folgt:

public class FactorySurrogateSelector : IDataContractSurrogate 
{ 
    [DataContract] 
    class FactorySurrogate 
    { 
    } 

    readonly FactoryBase factory; 

    public FactorySurrogateSelector(FactoryBase factory) 
    { 
     this.factory = factory; 
    } 

    #region IDataContractSurrogate Members 

    public object GetCustomDataToExport(Type clrType, Type dataContractType) 
    { 
     throw new NotImplementedException(); 
    } 

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType) 
    { 
     throw new NotImplementedException(); 
    } 

    public Type GetDataContractType(Type type) 
    { 
     if (typeof(FactoryBase).IsAssignableFrom(type)) 
      return typeof(FactorySurrogate); 
     return type; 
    } 

    public object GetDeserializedObject(object obj, Type targetType) 
    { 
     if (obj is FactorySurrogate) 
      return factory; 
     return obj; 
    } 

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes) 
    { 
     throw new NotImplementedException(); 
    } 

    public object GetObjectToSerialize(object obj, Type targetType) 
    { 
     if (obj is FactoryBase) 
     { 
      return new FactorySurrogate(); 
     } 
     return obj; 
    } 

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) 
    { 
     throw new NotImplementedException(); 
    } 

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) 
    { 
     throw new NotImplementedException(); 
    } 

    #endregion 
} 

Dann serialisiert und deserialisiert wie folgt :

var factory = new Factory(); 
var test = new Foo(factory) 
{ 
    Bars = { new Bar(factory) }, 
}; 

var surrogate = new FactorySurrogateSelector(factory); 
var serializer = new DataContractJsonSerializer(test.GetType(), Enumerable.Empty<Type>(), int.MaxValue, false, surrogate, false); 

byte[] json; 
using (var stream = new MemoryStream()) 
{ 
    serializer.WriteObject(stream, test); 
    json = stream.ToArray(); 
} 

Foo test2; 
using (var stream = new MemoryStream(json)) 
{ 
    test2 = (Foo)serializer.ReadObject(stream); 
} 

if (test2.Factory != test.Factory) 
    throw new InvalidOperationException(); 

Hinweis tha t die gewünschte Fabrik wurde direkt in den Konstruktor der FactorySurrogateSelector übergeben und dann innerhalb jedes Typs gesetzt, der Instanzen des Werkstyps enthält.

Die resultierende JSON wird wie folgt aussehen:

{ 
    "factory": {}, 
    "Bars": [ 
    { 
     "factory": {} 
    } 
    ] 
} 

Einige Qualifikationen:

  • Ihre Fabrik von einer gemeinsamen Basisklasse erben müssen, hier FactoryBase. Die Datenvertragserialisierer werden niemals serialisieren ein Schnittstellenelement, z. IFactory factory wobei IFactory Ihre Factory-Schnittstelle ist, auch wenn ein entsprechender Ersatz vorhanden ist.

  • Die leeren Objekte "factory": {} müssen im JSON erscheinen, damit das Ersatzgerät während der Deserialisierung den korrekten "echten" Fabrikwert eingibt. Daher die [DataMember(IsRequired = true)].

0

Ein anderer Weg ist zu erreichen, ein thread static oder thread local Factory-Objekt einführen, dann Klassen füllen damit ein [OnDeserializing] callback verwenden.

Wenn Sie also Ihre Typen wie folgt definieren:

public interface IFactory 
{ 
} 

public class Factory : IFactory 
{ 
} 

public interface IHasFactory 
{ 
    IFactory Factory { get; } 
} 

[DataContract] 
public abstract class HasFactoryBase : IHasFactory 
{ 
    [ThreadStatic] 
    static IFactory deserializedFactory; 

    static IFactory DeserializedFactory 
    { 
     get 
     { 
      return deserializedFactory; 
     } 
     set 
     { 
      deserializedFactory = value; 
     } 
    } 

    public static IDisposable SetDeserializedFactory(IFactory factory) 
    { 
     return new PushValue<IFactory>(factory,() => DeserializedFactory, val => DeserializedFactory = val); 
    } 

    IFactory factory; 

    public IFactory Factory { get { return factory; } } 

    public HasFactoryBase(IFactory factory) 
    { 
     this.factory = factory; 
    } 

    [OnDeserializing] 
    void OnDeserializing(StreamingContext context) 
    { 
     this.factory = DeserializedFactory; 
    } 
} 

public struct PushValue<T> : IDisposable 
{ 
    Action<T> setValue; 
    T oldValue; 

    public PushValue(T value, Func<T> getValue, Action<T> setValue) 
    { 
     if (getValue == null || setValue == null) 
      throw new ArgumentNullException(); 
     this.setValue = setValue; 
     this.oldValue = getValue(); 
     setValue(value); 
    } 

    #region IDisposable Members 

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class. 
    public void Dispose() 
    { 
     if (setValue != null) 
      setValue(oldValue); 
    } 

    #endregion 
} 

[DataContract] 
public class Foo : HasFactoryBase 
{ 
    public Foo(IFactory factory) 
     : base(factory) 
    { 
     this.Bars = new List<Bar>(); 
    } 

    [DataMember] 
    public List<Bar> Bars { get; set; } 
} 

[DataContract] 
public class Bar : HasFactoryBase 
{ 
    public Bar(IFactory factory) : base(factory) { } 
} 

können Sie serialisieren und deserialisieren wie folgt:

var factory = new Factory(); 
var test = new Foo(factory) 
{ 
    Bars = { new Bar(factory) }, 
}; 

var serializer = new DataContractJsonSerializer(test.GetType()); 

byte [] json; 
using (var stream = new MemoryStream()) 
{ 
    serializer.WriteObject(stream, test); 
    json = stream.ToArray(); 
} 

Foo test2; 
using (HasFactoryBase.SetDeserializedFactory(factory)) 
using (var stream = new MemoryStream(json)) 
{ 
    test2 = (Foo)serializer.ReadObject(stream); 
} 

if (test2.Factory != test.Factory) 
    throw new InvalidOperationException(); 

Und die JSON wird wie folgt aussehen:

{ 
    "Bars": [ 
    {} 
    ] 
} 

Einige Anmerkungen:

  • Das Factory-Objekt wird im JSON überhaupt nicht angezeigt.

  • Die Factory-Objekte müssen nicht mehr von einer abstrakten Basisklasse erben, sie können einfach eine gemeinsame IFactory-Schnittstelle implementieren.

Verwandte Themen