>我正在尝试创建一个条件的合同解析器,以便我可以根据 Web 请求/控制器操作以不同的方式控制序列化。
例如,在我的用户控制器中,我想序列化用户的所有属性,但对于某些相关对象,我可能只序列化基元类型。 但是,如果我去我的公司控制器,我想序列化公司的所有属性,但可能只序列化用户的原始属性(因此我不想使用数据注释或应该序列化函数。
因此,查看自定义合同解析器页面,我创建了自己的页面。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 项目中。 用户、公司和角色对象是从 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;
}
}
我认为如果您使用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
类型的变量可以从所有其他类型分配。