我试图创建一个IContractResolver来简化WebApi项目上的安全处理。
我正在尝试:
我想基于一组动态条件来序列化某些对象/属性(例如,调用端点的用户角色)。
因此,我实现了一个自定义属性,该属性在Interface的CreateProperty重写中进行了检查,并将ShouldSerialize函数设置为我自己的逻辑。
我现在的问题是,是否可以有条件地序列化某个列表中的完整对象?与其在预处理步骤中过滤列表(如果我更改对象,这很容易出错),我希望它由当前的ContractResolver递归处理。
在某种程度上,我试图得到这样的东西:
override void CreateObject(JSONObject ob){ if ( ob.DeclaringType == MyType) { ob.ShouldSerialize = instance => {[...] }; //Custom Logic } }
我是否缺少覆盖,这根本不可能吗?有没有一种更好的方法可以真正做到这一点,而无需我“预先解析”我的所有价值观?
这不是开箱即用的。如果您检查源,JsonSerializerInternalWriter.SerializeList()您将发现没有逻辑可以基于某些过滤器跳过收集条目。
JsonSerializerInternalWriter.SerializeList()
但是,Json.NET确实具有强大的异常处理功能。如果在开始序列化对象时引发了异常,则在[OnError]回调中将其捕获并吞下:
[OnError]
null
因此,实现所需功能的一种可能性是从JsonContract.OnSerializingCallbacks自定义合同解析器添加的人工回调中引发异常,然后使用添加的处理程序捕获并吞下该异常JsonContract.OnErrorCallbacks。与已经进行的对属性值的过滤结合使用时,此方法的优点在于,即使秘密对象是根对象或包含在字典,动态对象或多维数组中,也无法对它进行序列化。这种方法不会干扰PreserveReferencesHandling.Arrays。
JsonContract.OnSerializingCallbacks
JsonContract.OnErrorCallbacks
PreserveReferencesHandling.Arrays
一个执行此操作的合同解析器如下:
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; } }
那么一种可能的用途是:
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(""); } }
原型小提琴。
请注意,引发和捕获大量异常可能会对性能产生影响。您将需要分析您的Web应用程序,以确定是否存在问题。您还需要确定您的Web服务在尝试序列化“秘密”根对象时是应返回异常还是执行其他操作。