我正在尝试创建一个 IContractResolver 来简化我对 WebApi 项目的安全处理.
I am attempting to create a IContractResolver to simplify my security handling on a WebApi Project.
我正在尝试什么:
我想根据一组动态条件(例如调用端点的用户的角色)序列化某些对象/属性.
I want to serialize certain objects/properties based on a set of dynamic conditions ( for examples the Role of the user that called the endpoint).
所以我实现了一个在接口的 CreateProperty 覆盖中检查的自定义属性,并将 ShouldSerialize 函数设置为我自己的逻辑.
So I implemented a custom attribute that is checked in the CreateProperty override of the Interface, and set the ShouldSerialize function to my own logic.
我现在的问题是,是否可以有条件地序列化某个列表中的完整对象?而不是在预处理步骤中过滤列表(如果我更改对象,这很容易出错),我希望它由当前的 ContractResolver 递归处理.
My question now is, is it possible to conditionally serialize full objects that are in a certain list ? Instead of filtering the lists in a preprocessing step (which is error prone, if I change my objects) I would like it to be handled recursively by the current ContractResolver.
在某种程度上,我试图得到这样的东西:
In a way I was trying to get something like this:
override void CreateObject(JSONObject ob){
if ( ob.DeclaringType == MyType)
{
ob.ShouldSerialize = instance => {[...] }; //Custom Logic
}
}
我错过了覆盖,这根本不可能吗?有没有更好的方法来实际执行此操作,而不必预解析"我的所有值?
Am I missing a override, is this not possible at all? Is there a better way to actually do this, without me having to "pre-parse" all my values?
这不是开箱即用的.如果您检查 JsonSerializerInternalWriter.SerializeList()
你会看到没有逻辑可以跳过基于某些过滤器的集合条目.
This is not implemented out of the box. If you inspect the source for JsonSerializerInternalWriter.SerializeList()
you will see there is no logic to skip collection entries based on some filter.
但是,Json.NET 确实具有强大的异常处理.如果在开始序列化对象时抛出异常,然后在 [OnError]
回调:
However, Json.NET does have robust exception handling. If an exception is thrown when beginning to serialize an object then caught and swallowed in an [OnError]
callback:
null
.null
is written.因此,实现所需功能的一种可能性是从添加到 JsonContract.OnSerializingCallbacks
由您的自定义合同解析器,然后使用添加到 JsonContract.OnErrorCallbacks
.当与您已经在做的对属性值进行过滤结合使用时,这种方法的优点是保证即使它是根对象或包含在字典、动态对象或多维数组中的秘密对象也不能被序列化.这种方法不会干扰 PreserveReferencesHandling.Arrays
.
Thus one possibility to achieve your desired functionality would be to throw an exception from an artificial callback added to JsonContract.OnSerializingCallbacks
by your custom contract resolver, then catch and swallow the exception with a handler added to JsonContract.OnErrorCallbacks
. When combined with filtering on property values as you are already doing, this approach has the advantage of guaranteeing a secret object cannot be serialized even when it is the root object or when contained in a dictionary, a dynamic object, or a multidimensional array. This approach will not interfere with PreserveReferencesHandling.Arrays
.
执行此操作的一个合同解析器如下:
One contract resolver that does this is as follows:
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;
}
}
那么一种可能的用途是:
Then one possible use would be:
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("");
}
}
原型小提琴.
请注意,抛出和捕获大量异常可能会对性能产生影响.请参阅 C# 中的异常有多昂贵?.您将需要分析您的 Web 应用程序以确定这是否是一个问题.您还需要决定您的 Web 服务在尝试序列化秘密"根对象时是否应该返回异常,或者执行其他操作.
Note that throwing and catching a large number of exceptions can have performance implications. See How expensive are exceptions in C#?. You will need to profile your web application to determine whether this is a problem. You also will need to decide whether your web service should return an exception when attempting to serialize a "secret" root object, or do something different.
这篇关于JSON.Net 自定义合约序列化和集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!