c# – 使用EntityObjects进行Ajax绑定的Telerik MVC Grid获取循环引用异常
作者:互联网
我一直在使用Telerik MVC Grid已经有一段时间了,它是一个很好的控件,然而,一个令人烦恼的事情一直出现与使用Ajax绑定到从Entity Framework创建和返回的对象的网格相关.实体对象具有循环引用,当您从Ajax回调返回IEnumerable时,如果存在循环引用,则会从JavascriptSerializer生成异常.发生这种情况是因为MVC Grid使用了JsonResult,而JsonResult又使用不支持序列化循环引用的JavaScriptSerializer.
我对这个问题的解决方案是使用LINQ来创建没有相关实体的视图对象.这适用于所有情况,但需要创建新对象以及将数据复制到实体对象或从实体对象复制到这些视图对象.没有很多工作,但这是工作.
我终于想出了如何一般地让网格没有序列化循环引用(忽略它们),我想分享我的解决方案,一般公众,因为我认为它是通用的,并很好地插入环境.
该解决方案有几个部分
>使用自定义序列化程序交换默认网格序列化程序
>安装Newtonsoft提供的Json.Net插件(这是一个很棒的库)
>使用Json.Net实现网格序列化器
>修改Model.tt文件以在导航属性前插入[JsonIgnore]属性
>覆盖Json.Net的DefaultContractResolver并查找_entityWrapper属性名称以确保它也被忽略(由poco类或实体框架注入包装器)
所有这些步骤本身都很容易,但如果没有所有这些步骤,你就无法利用这种技术.
一旦正确实现,我现在可以轻松地将任何实体框架对象直接发送到客户端,而无需创建新的View对象.我不推荐每个对象,但有时它是最好的选择.同样重要的是要注意,任何相关的entires都不在客户端,因此不要使用它们.
以下是所需的步骤
>在您的应用程序中创建以下类.此类是网格用于获取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
};
}
}
>实现Custom 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语句taht检查属性是否
RelationshipMultiplicity.Many
在该代码块之后,您需要在行之前插入[JasonIgnore]属性
<#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
这会将proprty名称注入生成的代码文件中.
现在查找处理Relationship.One和Relationship.ZeroOrOne关系的这一行.
<#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>
在此行之前添加[JsonIgnore]属性.
现在唯一剩下的就是确保NewtonSoft.Json库在每个生成的文件的顶部“使用”.在Model.tt文件中搜索对WriteHeader()的调用.此方法采用字符串数组参数来添加额外的使用(extraUsings).而不是传递null connstruct一个字符串数组,并发送“Newtonsoft.Json”字符串作为数组的第一个元素.现在看来应该是这样的:
WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
这就是所有要做的事情,并且每个对象都会开始工作.
现在为免责声明
>我从未使用过Json.Net,所以我的实现可能不是
最佳.
>我现在已经测试了大约两天,并没有发现任何这种技术失败的情况.
>我也没有发现JavascriptSerializer和JSon.Net序列化程序之间存在任何不兼容性,但这并不意味着
没有
>唯一的另一个警告是,我正在通过名称测试名为“_entityWrapper”的属性,将其ignored属性设置为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("<", @"<").Replace(">", @">");
return Append("{0}:{1}".FormatWith((object)name, (object)data));
}
}
注意:替换它,因为网格在编辑模式下为客户端模板呈现html标记,如果我们不编码,则浏览器将呈现标记.如果没有使用Replace from string对象,我还没有找到workarround.
3-在Global.asax.cs上的Application_Start上我注册了我的新工厂:
DI.Current.Register<IClientSideObjectWriterFactory>(() => new JsonClientSideObjectWriterFactory());
它适用于Telerik拥有的所有组件.我唯一没有改变的是PropertyNameIgnoreContractResolver,它与EntityFramework类相同.
标签:c,circular-reference,telerik,telerik-mvc 来源: https://codeday.me/bug/20190709/1417918.html