4

我正在尝试创建一个有条件的 ContractResolver,以便我可以根据 Web 请求/控制器操作以不同方式控制序列化。

例如,在我的用户控制器中,我想序列化我的用户的所有属性,但一些相关的对象我可能只序列化原始类型。但是如果我去我的公司控制器,我想序列化公司的所有属性,但可能只是用户的原始属性(因此我不想使用数据注释或序列化函数。

因此,查看我自己创建的自定义 ContractResolver 页面。 http://james.newtonking.com/projects/json/help/index.html?topic=html/ContractResolver.htm

看起来像这样

public class IgnoreListContractResolver : DefaultContractResolver
{
    private readonly Dictionary<string, List<string>> IgnoreList;

    public IgnoreListContractResolver(Dictionary<string, List<string>> i)
    {
        IgnoreList = i;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();

        if(IgnoreList.ContainsKey(type.Name))
        {
            properties.RemoveAll(x => IgnoreList[type.Name].Contains(x.PropertyName));                
        }
        return properties;
    }
}

然后在我的 GetUsers 的 web api 控制器操作中我这样做

public dynamic GetUsers()
{
    List<User> Users = db.Users.ToList();
    List<string> RoleList = new List<string>();
    RoleList.Add("UsersInRole");
    List<string> CompanyList = new List<string>();
    CompanyList.Add("CompanyAccesses");
    CompanyList.Add("ArchivedMemberships");
    CompanyList.Add("AddCodes");

    Dictionary<string, List<string>> IgnoreList = new Dictionary<string, List<string>>();
    IgnoreList.Add("Role", RoleList);
    IgnoreList.Add("Company", CompanyList);
    GlobalConfiguration
            .Configuration
            .Formatters.JsonFormatter
            .SerializerSettings
            .ContractResolver = new IgnoreListContractResolver(IgnoreList);

    return new { List = Users, Status = "Success" };
}

因此,在调试时,我看到我的合同解析器运行并返回正确的属性,但返回到浏览器的 Json 仍然包含我从列表中删除的属性的条目。

我缺少什么或如何进入 webapi 控制器中的 Json 序列化步骤的任何想法。


*更新** 我应该补充一点,这是一个同时具有 MVC 控制器和 webapi 控制器的 MVC4 项目。User、Company 和 Role 对象是从 EF5 加载的对象(首先由代码创建)。有问题的控制器是一个 web api 控制器。不知道为什么这很重要,但我在一个干净的 WebApi 项目(并且没有 EF5)而不是 MVC 项目中尝试了这个,它按预期工作。这是否有助于确定问题可能出在哪里?

谢谢


*更新 2* * 在同一个 MVC4 项目中,我为 Object 类创建了一个名为 ToJson 的扩展方法。它使用 Newtonsoft.Json.JsonSerializer 来序列化我的实体。就这么简单。

public static string ToJson(this object o, Dictionary<string, List<string>> IgnoreList)
{
    JsonSerializer js = JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings()
    {
        Formatting = Formatting.Indented,
        DateTimeZoneHandling = DateTimeZoneHandling.Utc,
        ContractResolver = new IgnoreListContractResolver(IgnoreList),
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore  
    });

    js.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());

    var jw = new StringWriter();
    js.Serialize(jw, o);
    return jw.ToString();

}

然后在 MVC 操作中,我创建一个像这样的 json 字符串。

model.jsonUserList = db.Users.ToList().ToJson(IgnoreList);

忽略列表的创建位置与我之前的帖子完全一样。我再次看到合同解析器运行并正确限制了属性列表,但输出的 json 字符串仍然包含所有内容(包括我从列表中删除的属性)。这有帮助吗?我一定是做错了什么,现在看来它不是 MVC 或 web api 框架。这可能与 EF 交互/代理/等有关吗?任何想法将不胜感激。

谢谢


*更新 3 ***

消除过程和更彻底的调试让我意识到 EF 5 动态代理正在搞乱我的序列化和 ContractResolver 检查类型名称匹配。所以这是我更新的 IgnoreListContractResolver。 在这一点上,我只是在寻找关于更好方法的意见,或者我是否正在做一些可怕的事情。 我知道这只是为了直接使用我的 EF 对象而不是 DTO 而跳过了很多圈,但最后我发现这个解决方案非常灵活。

public class IgnoreListContractResolver : CamelCasePropertyNamesContractResolver
{
    private readonly Dictionary<string, List<string>> IgnoreList;

    public IgnoreListContractResolver(Dictionary<string, List<string>> i)
    {
        IgnoreList = i;
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();

        string typename = type.Name;
        if(type.FullName.Contains("System.Data.Entity.DynamicProxies.")) {
            typename = type.FullName.Replace("System.Data.Entity.DynamicProxies.", "");
            typename = typename.Remove(typename.IndexOf('_'));
        }

        if (IgnoreList.ContainsKey(typename))
        {
            //remove anything in the ignore list and ignore case because we are using camel case for json
            properties.RemoveAll(x => IgnoreList[typename].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
        }
        return properties;
    }
} 
4

1 回答 1

1

我认为如果您使用Type而不是string忽略列表的键类型可能会有所帮助。因此,您可以避免命名问题(不同命名空间中具有相同名称的多个类型),并且可以利用继承。我不熟悉 EF5 和代理,但我猜代理类派生自您的实体类。因此,您可以检查Type.IsAssignableFrom()而不只是检查是否typename是忽略列表中的键。

private readonly Dictionary<Type, List<string>> IgnoreList;

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
    List<JsonProperty> properties = base.CreateProperties(type, memberSerialization).ToList();

    // look for the first dictionary entry whose key is a superclass of "type"
    Type key = IgnoreList.Keys.FirstOrDefault(k => k.IsAssignableFrom(type));

    if (key != null)
    {
        //remove anything in the ignore list and ignore case because we are using camel case for json
        properties.RemoveAll(x => IgnoreList[key].Contains(x.PropertyName, StringComparer.CurrentCultureIgnoreCase));
    }
    return properties;
}

然后必须像这样创建忽略列表(我还使用短语法来创建列表和字典):

var CompanyList = new List<string> {
    "CompanyAccesses",
    "ArchivedMemberships",
    "AddCodes"
};

var IgnoreList = new Dictionary<Type, List<string>> {
    // I just replaced "Company" with typeof(Company) here:
    { typeof(Company), CompanyList }
};

请注意,如果您使用我上面的代码,作为第一个键添加到忽略列表将导致每次都typeof(object)匹配此条目,并且您的其他条目将永远不会被使用!发生这种情况是因为一个类型的变量可以从其他所有类型中赋值。object

于 2013-02-04T08:51:03.600 回答