c# - 在返回给客户端之前修改 JSON 的通用方法

标签 c# asp.net-web-api json.net

我正在寻找一种通用方法,该方法允许我修改返回给客户端的对象的 JSON,特别是删除返回对象中的某些属性。类似于建议here .

修改是不确定的,因为它们是根据与用户关联的规则根据请求确定的。所以这不适合缓存的方法。

我已经回顾了几种方法。最明显的选择是 JsonConverter,但是这也有问题,如所列 here , herehere .

这种方式的主要问题是在WriteJson中调用JToken.FromObject获取特定值的JSON,递归调用同一个JsonConverter,导致循环.

我已经尝试了 here 中列出的解决方案的一个变体它提供了一种暂时禁用 CanWrite 以防止循环问题的方法。但是,它似乎不适用于多个并发请求。 JsonConverter 的单个实例在多个线程之间共享,这些线程在不同时间更改和读取 CanWrite 属性的状态,从而导致不一致的结果。

我也试过在 WriteJson 中使用不同的序列化器(即除了提供给方法的序列化器)但是这不支持递归(因为那个序列化器不使用我的 JsonConverter)所以我的 JsonConverter 不会处理任何嵌套项目。从默认序列化程序的转换器集合中删除我的 JsonConverter 有同样的问题。

基本上,如果我想能够递归地处理我的模型对象,我就会遇到自引用循环问题。

理想情况下,JToken.FromObject 可以通过某种方式有选择地不对对象本身调用 JsonConverter,但仍会在序列化期间将其应用于任何子对象。我通过修改 CanConvertCanWrite 设置为 true 来解决这个问题,前提是传递给 CanConvert 的对象与传递给 WriteJson 的最后一个对象。

然而,为了让它工作,我需要一个按请求范围的 JsonConverter(出于上述相同的线程原因),但我看不到如何获得它。

这是我所拥有的示例:-

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test
{
    public class TestConverter : JsonConverter
    {
        bool CannotWrite { get; set; }

        public override bool CanWrite { get { return !CannotWrite; } }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JToken token;

            //----------------------------------------

            // this works; but because it's (i think) creating a new
            // serializer inside the FromObject method
            // which means any nested objects won't get processed

            //token = JToken.FromObject(value);

            //----------------------------------------

            // this creates loop because calling FromObject will cause this
            // same JsonConverter to get called on the same object again

            //token = JToken.FromObject(value, serializer);

            //----------------------------------------

            // this gets around the loop issue, but the JsonConverter will
            // not apply to any nested objects

            //serializer.Converters.Remove(this);
            //token = JToken.FromObject(value, serializer);

            //----------------------------------------

            // see https://stackoverflow.com/a/29720068/1196867
            //
            // this works as it allows us to use the same serializer, but
            // temporarily sets CanWrite to false so the invocation of
            // FromObject doesn't cause a loop
            //
            // this also means we can't process nested objects, however
            // see below in CanConvert for a potential workaround.

            using (new PushValue<bool>(true, () => CannotWrite, (cantWrite) => CannotWrite = cantWrite))
            {
                token = JToken.FromObject(value, serializer);
            }

            // store the type of this value so we can check it in CanConvert when called for any nested objects
            this.currentType = value.GetType();

            //----------------------------------------

            // in practice this would be obtained dynamically
            string[] omit = new string[] { "Name" };

            JObject jObject = token as JObject;

            foreach (JProperty property in jObject.Properties().Where(p => omit.Contains(p.Name, StringComparer.OrdinalIgnoreCase)).ToList())
            {
                property.Remove();
            }

            token.WriteTo(writer);
        }

        private Type currentType;

        public override bool CanConvert(Type objectType)
        {
            if (typeof(Inua.WebApi.Authentication.IUser).IsAssignableFrom(objectType))
            {
                // if objectType is different to the type which is currently being processed,
                // then set CanWrite to true, so this JsonConverter will apply to any nested
                // objects that we want to process
                if (this.currentType != null && this.currentType != objectType)
                {
                    this.CannotWrite = false;
                }

                return true;
            }

            return false;
        }

        public override bool CanRead { get { return false; } }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
}

我考虑过的选项:-

  1. 使用自定义 JsonConverter,但手动构建 JSON 而不是 利用 JToken.FromObject (增加了很多复杂性)
  2. 使用 ActionFilterAttribute 并从 序列化之前的模型(我需要为每个 请求修改模型对象)
  3. 在执行查找的模型中使用 ShouldSerialzeX() 方法(不容易维护)
  4. 使用自定义的 ContractResolver (这会受到相同缓存的影响 问题,即使我在 将“shareCache”设置为 false 的 DefaultContractResolver)

任何人都可以建议:-

  • 一种根据请求制作 JsonConverter 的方法
  • 假设它不能按请求进行,这是一种解决 JsonConverter 线程问题的方法
  • JsonConverter 的替代方案,它允许我在 JSON 对象返回到客户端之前全局检查和修改它们,而不依赖于大量反射开销
  • 还有别的吗?

提前感谢您花时间阅读本文。

最佳答案

为多线程、多类型场景修复 TestConverter 的一种可能性是创建一个 [ThreadStatic] 正在序列化的类型堆栈。然后,在 CanConvert , 如果候选类型与堆栈顶部的类型相同,则返回 false

请注意,这在转换器包含在JsonSerializerSettings.Converters 中时有效。 .如果转换器直接应用于类或属性,例如,

    [JsonConverter(typeof(TestConverter<Inua.WebApi.Authentication.IUser>))]

那么无限递归仍然会发生,因为 CanConvert 不会被直接应用的转换器调用。

因此:

public class TestConverter<TBaseType> : JsonConverter
{
    [ThreadStatic]
    static Stack<Type> typeStack;

    static Stack<Type> TypeStack { get { return typeStack = (typeStack ?? new Stack<Type>()); } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JToken token;

        using (TypeStack.PushUsing(value.GetType()))
        {
            token = JToken.FromObject(value, serializer);
        }

        // in practice this would be obtained dynamically
        string[] omit = new string[] { "Name" };

        JObject jObject = token as JObject;

        foreach (JProperty property in jObject.Properties().Where(p => omit.Contains(p.Name, StringComparer.OrdinalIgnoreCase)).ToList())
        {
            property.Remove();
        }

        token.WriteTo(writer);
    }

    public override bool CanConvert(Type objectType)
    {
        if (typeof(TBaseType).IsAssignableFrom(objectType))
        {
            return TypeStack.PeekOrDefault() != objectType;
        }

        return false;
    }

    public override bool CanRead { get { return false; } }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public static class StackExtensions
{
    public struct PushValue<T> : IDisposable
    {
        readonly Stack<T> stack;

        public PushValue(T value, Stack<T> stack)
        {
            this.stack = stack;
            stack.Push(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 (stack != null)
                stack.Pop();
        }

        #endregion
    }

    public static T PeekOrDefault<T>(this Stack<T> stack)
    {
        if (stack == null)
            throw new ArgumentNullException();
        if (stack.Count == 0)
            return default(T);
        return stack.Peek();
    }

    public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
    {
        if (stack == null)
            throw new ArgumentNullException();
        return new PushValue<T>(value, stack);
    }
}

在您的情况下,TBaseType 将是 Inua.WebApi.Authentication.IUser

原型(prototype) fiddle .

关于c# - 在返回给客户端之前修改 JSON 的通用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35532466/

相关文章:

c# - 如何使用 C# 在 xamarin android 中将字节数组转换为 pdf?

c# - 从 Azure 机器人调用 Web API

javascript - 如何将json反序列化为C#列表对象

c# - 什么会导致 PostMessage 发送的鼠标点击被忽略?

c# - Web.config 转换更改 appsettings 的路径

c# - 如何使用委托(delegate)在线程包装器类中传递方法?

c# - ASP.NET WebAPI 序列化问题

c# - 简单注入(inject)器 Web Api Controller 构造函数注入(inject)失败

c# - 使用 JSchema 进行模式验证的时间问题

c# - 可以将 decimal.MinValue 转换为空字符串并返回的自定义 JsonConverter