我已经使用Telerik MVC Grid已有很长时间了,这是一个很好的控件,但是,与将网格与Ajax绑定结合到从实体框架创建和返回的对象一起使用时,令人讨厌的事情不断出现。实体对象具有循环引用,当您从Ajax回调返回IEnumerable时,如果存在循环引用,它将从JavascriptSerializer生成异常。发生这种情况是因为MVC网格使用了JsonResult,而后者又使用了不支持序列化循环引用的JavaScriptSerializer。
我对这个问题的解决方案是使用LINQ创建不具有相关实体的视图对象。这适用于所有情况,但是需要创建新对象以及将数据复制到实体对象/从实体对象复制到这些视图对象。不是很多工作,而是工作。
我终于弄清楚了如何使网格不序列化循环引用(忽略它们),并且我想与公众共享我的解决方案,因为我认为它是通用的,并且很好地插入了环境。
该解决方案包括两个部分
将默认的网格序列化程序与自定义序列化程序交换
安装可从Newtonsoft获得的Json.Net插件(这是一个很棒的库)
使用Json.Net实现网格序列化器
修改Model.tt文件以在导航属性的前面插入[JsonIgnore]属性
重写Json.Net的DefaultContractResolver并查找_entityWrapper属性名称,以确保也将其忽略(由poco类或实体框架注入的包装器)
所有这些步骤本身都很容易,但是没有所有这些步骤,您将无法利用此技术。
一旦正确实现,我现在可以轻松地将任何实体框架对象直接发送到客户端,而无需创建新的View对象。我不建议每个对象都使用此功能,但有时它是最佳选择。同样重要的是要注意,客户端上没有任何相关的整体,因此请不要使用它们。
这是所需的步骤
在您的应用程序中的某处创建以下类。此类是网格用于获取json结果的工厂对象。不久它将被添加到global.asax文件中的telerik库中。
public class CustomGridActionResultFactory : IGridActionResultFactory
{
public System.Web.Mvc.ActionResult Create(object model)
{
//return a custom JSON result which will use the Json.Net library
return new CustomJsonResult
{
Data = model
};
}
}
实现自定义ActionResult。这段代码大部分都是样板。唯一有趣的部分是在底部调用JsonConvert.SerilaizeObject并将其传递给ContractResolver的底部。 ContactResolver按名称查找称为_entityWrapper的属性,并将其设置为忽略。我不确定谁注入了此属性,但是它是实体包装对象的一部分,并且具有循环引用。
public class CustomJsonResult : ActionResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public string ContentType { get; set; }
public System.Text.Encoding ContentEncoding { get; set; }
public object Data { get; set; }
public JsonRequestBehavior JsonRequestBehavior { get; set; }
public int MaxJsonLength { get; set; }
public CustomJsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.DenyGet;
MaxJsonLength = int.MaxValue; // by default limit is set to int.maxValue
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
response.Write(JsonConvert.SerializeObject(Data, Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
}));
}
}
}
将工厂对象添加到telerik网格中。我在global.asax Application_Start()方法中执行此操作,但实际上可以在任何有意义的地方进行。
DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory());
创建DefaultContractResolver类,该类检查_entityWrapper并忽略该属性。解析器在步骤2中传递到SerializeObject()调用中。
public class PropertyNameIgnoreContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.Name == "_entityWrapper")
property.Ignored = true;
return property;
}
}
修改Model1.tt文件以注入忽略POCO对象的相关实体属性的属性。必须注入的属性是[JsonIgnore]。这是添加到本文中最难的部分,但在Model1.tt(或项目中的任何文件名)中也不难完成。同样,如果您首先使用代码,则可以将[JsonIgnore]属性手动放置在任何创建循环引用的属性的前面。
在.tt文件中搜索region.Begin(“ Navigation Properties”)。这是所有导航属性生成代码的地方。在两种情况下,必须处理许多XXX和Singular裁判。有一个if语句,可以检查属性是否为
RelationshipMultiplicity.Many
在该代码块之后,您需要在该行之前插入[JasonIgnore]属性
<#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
将属性名称注入到生成的代码文件中。
现在查找处理Relationship.One和Relationship.ZeroOrOne关系的这一行。
<#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>
在此行之前添加[JsonIgnore]属性。
现在剩下的唯一事情就是确保在每个生成的文件的顶部,NewtonSoft.Json库都为“ Used”。在Model.tt文件中搜索对WriteHeader()的调用。此方法采用一个字符串数组参数,该参数增加了额外的使用(extraUsings)。而不是传递null,而是构造一个字符串数组,并发送“ Newtonsoft.Json”字符串作为该数组的第一个元素。调用现在应如下所示:
WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
这就是每个对象要做的一切,一切都开始起作用。
现在免责声明
我从未使用过Json.Net,所以我的实现可能不是
最佳。
我已经进行了大约两天的测试,还没有发现此技术失败的任何情况。
我也没有发现JavascriptSerializer和JSon.Net序列化器之间有任何不兼容,但这并不意味着
没有任何
唯一需要注意的是,我正在测试名称为“ _entityWrapper”的属性,以将其忽略的属性设置为true。这显然不是最佳的。
我欢迎任何有关如何改进此解决方案的反馈。我希望它可以帮助其他人。
最佳答案
第一种解决方案适用于网格编辑模式,但是对于已经具有带有循环引用的对象行的网格,我们面临同样的问题,要解决此问题,我们需要创建一个新的IClientSideObjectWriterFactory和一个新的IClientSideObjectWriter。
这是我的工作:
1-创建一个新的IClientSideObjectWriterFactory:
public class JsonClientSideObjectWriterFactory : IClientSideObjectWriterFactory
{
public IClientSideObjectWriter Create(string id, string type, TextWriter textWriter)
{
return new JsonClientSideObjectWriter(id, type, textWriter);
}
}
2-创建一个新的IClientSideObjectWriter,这一次我不实现接口,我继承了ClientSideObjectWriter并覆盖了AppendObject和AppendCollection方法:
public class JsonClientSideObjectWriter : ClientSideObjectWriter
{
public JsonClientSideObjectWriter(string id, string type, TextWriter textWriter)
: base(id, type, textWriter)
{
}
public override IClientSideObjectWriter AppendObject(string name, object value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
return Append("{0}:{1}".FormatWith(name, data));
}
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
data = data.Replace("<", @"\u003c").Replace(">", @"\u003e");
return Append("{0}:{1}".FormatWith((object)name, (object)data));
}
}
注意:替换它是因为网格在编辑模式下为客户端模板呈现html标签,如果我们不进行编码,则浏览器将呈现标签。如果不使用从字符串替换对象,我还没有找到工作环境。
3-在Global.asax.cs的Application_Start上,我像这样注册了新工厂:
DI.Current.Register<IClientSideObjectWriterFactory>(() => new JsonClientSideObjectWriterFactory());
它适用于Telerik拥有的所有组件。我唯一没有更改的是与EntityFramework类相同的PropertyNameIgnoreContractResolver。
关于c# - 使用EntityObjects与Ajax绑定(bind)的Telerik MVC网格获取“循环引用”异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7888770/