2016-06-26 7 views
0

Ich versuche, einen IContractResolver zu erstellen, um meine Sicherheitsbehandlung in einem WebApi-Projekt zu vereinfachen.JSON.Net benutzerdefinierte Vertragsserialisierung und Sammlungen

Was ich versuche:

ich auf eine Reihe von dynamischen Bedingungen bestimmte Objekte/Eigenschaften auf Basis serialisiert werden soll (Beispiele, die die Rolle des Benutzers, der Endpunkt genannt).

Also habe ich ein benutzerdefiniertes Attribut implementiert, das in der CreateProperty-Überschreibung der Schnittstelle überprüft wird, und setze die ShouldSerialize-Funktion auf meine eigene Logik.

Meine Frage ist jetzt, ist es möglich, vollständige Objekte in einer bestimmten Liste zu serialisieren? Anstatt die Listen in einem Vorverarbeitungsschritt zu filtern (was fehleranfällig ist, wenn ich meine Objekte ändere) möchte ich, dass sie rekursiv vom aktuellen ContractResolver gehandhabt wird.

In gewisser Weise habe ich versucht, so etwas zu bekommen:

override void CreateObject(JSONObject ob){ 
if (ob.DeclaringType == MyType) 
{ 
    ob.ShouldSerialize = instance => {[...] }; //Custom Logic 
} 
} 

Bin ich eine Überschreibung fehlt, dann ist dies gar nicht möglich? Gibt es einen besseren Weg, dies zu tun, ohne dass ich all meine Werte "vorparsen" muss?

+0

Dies ist nicht im Lieferumfang enthalten. Siehe die Quelle für ['JsonSerializerInternalWriter.SerializeList()'] (https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs#L663). Aber etwas kann möglich sein. Kannst du ein [mcve] deiner Einrichtung geben? Können Sie beim Serialisieren und Deserialisieren verschiedene Vertragsresolver verwenden? Ist Ihr Stammobjekt jemals eine Sammlung, die gefiltert werden muss, oder müssen Sie nur die Objektgruppen filtern? – dbc

Antwort

0

Dies ist nicht im Lieferumfang enthalten. Wenn Sie die Quelle für JsonSerializerInternalWriter.SerializeList() überprüfen, werden Sie sehen, dass keine Logik vorhanden ist, um Auflistungseinträge basierend auf einem Filter zu überspringen.

Allerdings hat Json.NET robust exception handling. Wenn eine Ausnahme ausgelöst wird, wenn ein Objekt zu serialisiert beginnt dann in einem [OnError] Rückruf gefangen und verschlungen:

  • Wenn ein Array-Eintrag schreiben, der Array-Eintrag ist übersprungen (Ihr gewünschtes Verhalten).
  • Wenn das Root-Objekt geschrieben wird, wird die Ausnahme nicht abgefangen (möglicherweise ein Fehler?)
  • Sonst wird null geschrieben.

So ist eine Möglichkeit, Ihre gewünschte Funktionalität zu erreichen, wäre eine Ausnahme von einem künstlichen Rückruf JsonContract.OnSerializingCallbacks durch Ihren benutzerdefinierten Vertrag Resolver hinzugefügt zu werfen, dann fangen und die Ausnahme schluckte mit einem Handler JsonContract.OnErrorCallbacks hinzugefügt. In Kombination mit der Filterung von Eigenschaftswerten, wie Sie es bereits tun, hat dieser Ansatz den Vorteil, dass ein geheimes Objekt nicht serialisiert werden kann, selbst wenn es das Stammobjekt ist oder in einem Wörterbuch, einem dynamischen Objekt oder einem mehrdimensionalen Array enthalten ist. Dieser Ansatz stört PreserveReferencesHandling.Arrays nicht.

Ein Vertrag Resolver, der dies tut, ist wie folgt:

sealed class JsonSkipObjectException : JsonException 
{ 
} 

public class ShouldSerializeContractResolver : DefaultContractResolver 
{ 
    readonly Predicate<object> shouldSerialize; 
    readonly SerializationCallback serializationCallback; 
    readonly SerializationErrorCallback onErrorCallback; 

    public ShouldSerializeContractResolver(Predicate<object> shouldSerialize) 
     : base() 
    { 
     this.shouldSerialize = shouldSerialize; 
     this.serializationCallback = (o, context) => 
      { 
       if (shouldSerialize != null && !this.shouldSerialize(o)) 
        throw new JsonSkipObjectException(); 
      }; 
     this.onErrorCallback = (o, context, errorContext) => 
      { 
       if (errorContext.Error is JsonSkipObjectException) 
       { 
        errorContext.Handled = true; 
       } 
      }; 
    } 

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 
    { 
     var property = base.CreateProperty(member, memberSerialization); 

     if (shouldSerialize != null) 
     { 
      if (property.Readable) 
      { 
       var oldShouldSerialize = property.ShouldSerialize; 
       property.ShouldSerialize = (o) => 
        { 
         if (oldShouldSerialize != null && !oldShouldSerialize(o)) 
          return false; 
         var value = property.ValueProvider.GetValue(o); 
         if (!this.shouldSerialize(value)) 
          return false; 
         return true; 
        }; 
      } 
     } 
     return property; 
    } 

    protected override JsonContract CreateContract(Type objectType) 
    { 
     var contract = base.CreateContract(objectType); 
     contract.OnSerializingCallbacks.Add(serializationCallback); 
     contract.OnErrorCallbacks.Add(onErrorCallback); 
     return contract; 
    } 
} 

Dann eine mögliche Verwendung wäre:

public interface IConditionalSerialization 
{ 
    bool ShouldSerialize(); 
} 

public class ConditionalSerializationObject : IConditionalSerialization 
{ 
    public bool IsSecret { get; set; } 

    public string SecretProperty { get { return "should not see me"; } } 

    // Ensure "normal" conditional property serialization is not broken 
    public bool ShouldSerializeSecretProperty() 
    { 
     return false; 
    } 

    #region IConditionalSerialization Members 

    bool IConditionalSerialization.ShouldSerialize() 
    { 
     return !IsSecret; 
    } 

    #endregion 
} 

public class TestClass 
{ 
    public static void Test() 
    { 
     Predicate<object> filter = (o) => 
      { 
       var conditional = o as IConditionalSerialization; 
       return conditional == null || conditional.ShouldSerialize(); 
      }; 
     var settings = new JsonSerializerSettings 
     { 
      ContractResolver = new ShouldSerializeContractResolver(filter), 
     }; 

     var ok = new ConditionalSerializationObject { IsSecret = false }; 
     var notOk = new ConditionalSerializationObject { IsSecret = true }; 

     Test(ok, settings); 
     Test(new { Public = ok, Private = notOk }, settings); 
     Test(new [] { ok, notOk, ok, notOk }, settings); 
     Test(new[,] {{ ok, notOk, ok, notOk }}, settings); 
     Test(new { Array = new[,] { { ok, notOk, ok, notOk } } }, settings); 
     try 
     { 
      Test(notOk, settings); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("Exception thrown and not caught serializing root object " + notOk.GetType()); 
      Console.WriteLine(ex); 
     } 
    } 

    static void Test<T>(T value, JsonSerializerSettings settings) 
    { 
     Console.WriteLine("Unfiltered object: "); 
     Console.WriteLine(JToken.FromObject(value)); 

     var serializer = JsonSerializer.CreateDefault(settings); 
     var token = JToken.FromObject(value, serializer); 
     Console.WriteLine("Filtered object: "); 
     Console.WriteLine(token); 
     if (!token.SelectTokens("..IsSecret").All(t => JToken.DeepEquals(t, (JValue)false))) 
     { 
      throw new InvalidOperationException("token.SelectTokens(\"..IsSecret\").All(t => JToken.DeepEquals(t, (JValue)true))"); 
     } 
     if (token.SelectTokens("..SecretProperty").Any()) 
     { 
      throw new InvalidOperationException("token.SelectTokens(\"..SecretProperty\").Any()"); 
     } 
     Console.WriteLine("Secret objects and properties were successfully filtered."); 
     Console.WriteLine(""); 
    } 
} 

Prototype fiddle.

Beachten Sie, dass das Werfen und Abfangen einer großen Anzahl von Ausnahmen Auswirkungen auf die Leistung haben kann. Siehe How expensive are exceptions in C#?. Sie müssen Ihre Webanwendung profilieren, um festzustellen, ob dies ein Problem ist.Sie müssen auch entscheiden, ob Ihr Web-Service eine Exception zurückgeben soll, wenn Sie versuchen, ein "geheimes" Root-Objekt zu serialisieren oder etwas anderes zu tun.

+0

Dies ist eine perfekte Antwort, es ist schade, dass Json.Net Collection-Objekte nicht überspringen kann. Aber dein Weg könnte perfekt funktionieren. Ich werde Leistungsprofile profilieren müssen, um zu sehen, ob das für meinen Anwendungsfall funktioniert, aber es ist ziemlich genau das, wonach ich gefragt habe. – Morphex

Verwandte Themen