Telerik的MVC电网与阿贾克斯绑定使用EntityObjects得到循环引用除外 [英] Telerik MVC Grid with Ajax Binding using EntityObjects gets Circular References exception

查看:321
本文介绍了Telerik的MVC电网与阿贾克斯绑定使用EntityObjects得到循环引用除外的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用Telerik的MVC网格相当长的一段时间了,这是一个很大的控制,但是,有烦人的事情不断显示了与使用阿贾克斯绑定到创建的对象,并从实体框架返回电网。实体对象具有循环引用,当你从一个Ajax回调返回一个IEnumerable它来自的JavaScriptSerializer如果有循环引用生成异常。这是因为MVC网格使用又使用的JavaScriptSerializer它不支持序列化循环引用一个JsonResult。

I have been using Telerik MVC Grid for quite a while now and It is a great control, however, one annoying thing keeps showing up related to using the grid with Ajax Binding to objects created and returned from the Entity Framework. Entity objects have circular references, and when you return an IEnumerable from an Ajax callback it generates an exception from the JavascriptSerializer if there are circular references. This happens because the MVC Grid uses a JsonResult which in turn uses JavaScriptSerializer which does not support serializing circular references.

我对这个问题的解决方案是使用LINQ创建不具备相关的entites视图对象。这适用于所有的情况下,但reqires新对象的创建和数据到/从实体反对这些查看对象的复印。不是很多的工作,但它的工作。

My solution to this problem has been to use LINQ to create view objects that do not have the Related Entites. This works for all cases, but reqires the creation of new objects and the copying of data to / from entity objects to these view objects. Not alot of work, but it is work.

我终于想通了如何一般使电网不序列循环referneces(忽略他们),我想分享我为广大市民解决方案,因为我认为这是通用的,可插入环境很好。

I have finally figured out how to generically make the grid not serialize the circular referneces (ignore them) and I wanted to share my solution for the general public, as I think it is generic, and plugs into the environment nicely.

解决方案有几个部分


  1. 使用自定义的串行交换默认电网串行

  2. 安装Json.Net插件可从Newtonsoft(这是一个伟大的图书馆)

  3. 实现使用Json.Net

  4. 修改Model.tt文件中的导航属性前插入[JsonIgnore]属性电网串行

  5. 重写Json.Net的DefaultContractResolver并查找_entityWrapper属性名称,以确保这也被忽略(注入包装的POCO类或实体框架)

  1. Swap the default grid serializer with a custom serializer
  2. Install the Json.Net plug-in available from Newtonsoft (this is a great library)
  3. Implement the grid serializer using Json.Net
  4. Modify the Model.tt files to insert [JsonIgnore] attributes in front of the navigation properties
  5. Override the DefaultContractResolver of Json.Net and look for the _entityWrapper attribute name to ensure this is also ignored (injected wrapper by the poco classes or entity framework)

所有这些步骤是很容易在自己的和,但没有所有的人你可以不采取这种技术的优势。

All of these steps are easy in and of themselves, but without all of them you cannot take advantage of this technique.

一旦实施正确我现在可以轻松地直接发送的任何实体框架对象到客户端,而无需创建新的View对象。我不建议这对于每一个对象,但有时它是最佳的选择。同样重要的是要注意,任何相关entires不availalbe在客户端,因此,不使用它们。

Once implemented correctly i can now easily send any entity framework object directly to the client without creating new View objects. I dont recommend this for every object, but sometimes it is the best option. It is also important to note that any related entires are not availalbe on the client side, so don't use them.

以下是必需的步骤


  1. 创建您的应用程序下面的类的地方。这个类是电网用于获取JSON结果的工厂对象。这将被添加到不久Global.asax文件中Telerik的库

  1. Create the following class in your application somewhere. This class is a factory object that the grid uses to obtain json results. This will be added to the telerik library in the global.asax file shortly.

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,并将它们被忽略。我不完全相信谁注入此属性,但它是实体包装对象的一部分,它有圆形referneces。

  • Implement the Custom ActionResult. This code is boilerplate for the most part. The only interesting part is at the bottom where it calls JsonConvert.SerilaizeObject passing in a ContractResolver. The ContactResolver looks for properties called _entityWrapper by name and sets them to be ignored. I am not exactly sure who injects this property, but it is part of the entity wrapper objects and it has circular referneces.

    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()方法,但实际上它可以在任何地方做是有道理的。

  • Add the factory object to the telerik grid. I do this in the global.asax Application_Start() method, but realistically it can be done anywhere that makes sense.

    DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory());
    


  • 创建DefaultContractResolver类检查_entityWrapper并忽略该属性。其中解析器传递到在步骤2中的SerializeObject()调用

  • Create the DefaultContractResolver class that checks for _entityWrapper and ignores that attribute. The resolver is passed into the SerializeObject() call in step 2.

    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]属性。

  • Modify the Model1.tt file to inject attributes that ignore the related entity properties of the POCO Objects. The attribute that must be injected is [JsonIgnore]. This is the hardest part to add to this post but not hard to do in the Model1.tt (or whatever filename it is in your project). Also if you are using code first then you can manually place the [JsonIgnore] attributes in front of any attribute that creates a circular reference.

    输入搜索region.Begin( 导航属性)在.TT文件。这是所有导航属性是生成的代码。有两种情况有被照顾的许多人希望XXX和奇异refernece。有一个if语句taht检查,如果该属性是

    Search for the region.Begin("Navigation Properties") in the .tt file. This is where all of the navigation properties are code generated. There are two cases that have to be taken care of the many to XXX and the Singular refernece. There is an if statement taht checks if the property is

    RelationshipMultiplicity.Many
    

    只是,代码块之后,你需要插入[JasonIgnore]属性前行

    Just after that code block you need to insert the [JasonIgnore] attribute prior to the line

    <#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
    



    ,一个注入的proprty名称到生成的代码文件。

    Which injects the proprty name into the generated code file.

    现在寻找这一行它处理Relationship.One和Relationship.ZeroOrOne关系。

    Now look for this line which handles the Relationship.One and Relationship.ZeroOrOne relationships.

    <#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>
    

    添加仅此行之前[JsonIgnore]属性。

    Add the [JsonIgnore] attribute just before this line.

    现在唯一剩下的事情就是确保NewtonSoft.Json库使用在每个生成的文件的顶部。搜索调用WriteHeader()在Model.tt文件。该方法采用增加了额外的usings(extraUsings)的字符串数组参数。而不是传递null connstruct字符串数组,并在Newtonsoft.Json字符串数组的第一个元素发送。该呼叫现在应该是这样的:

    Now the only thing left is to make sure the NewtonSoft.Json library is "Used" at the top of each generated file. Search for the call to WriteHeader() in the Model.tt file. This method takes a string array parameter that adds extra usings (extraUsings). Instead of passing null connstruct an array of strings and send in the "Newtonsoft.Json" string as the first element of the array. The call should now look like:

    WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
    


  • 这就是所有有做的,全部的寄托开始工作,每一个对象。

    Thats all there is to do, and everthing starts working, for every object.


    • 我从来没有使用Json.Net所以我实施它可能不是
      最佳。

    • 我一直在测试大约两天,还没有发现哪里该技术失败的案例。

    • 我还没有发现的JavaScriptSerializer和JSon.Net串行但是,这并不意味着
      有任何的arent

    • 之间的任何不兼容性的唯一需要注意的就是我是一个名为_entityWrapper的名字到忽略属性设置为true性能测试。这显然不是最佳的。

    • I have never used Json.Net so my implementation of it might not be optimal.
    • I have been testing for about two days now and havent found any cases where this technique fails.
    • I also have not found any incompatibilities between the JavascriptSerializer and the JSon.Net serializer but that doesnt mean there arent any
    • The only other caveat is that the i am testing for a property called "_entityWrapper" by name to set its ignored property to true. This is obviously not optimal.

    我欢迎就如何改进这一解决方案的任何反馈。我希望它可以帮助别人。

    I would welcome any feedback on how to improve this solution. I hope it helps someone else.

    推荐答案

    第一个解决方案可与电网编辑模式,但我们与同样的问题在已用循环引用对象的行,以解决此我们需要创建一个新IClientSideObjectWriterFactory和一个新IClientSideObjectWriter电网的负载。
    这是我做的:

    The first solution works with the grid editing mode, but we have the same problem with the load of the grid that already has rows of objects with circular reference, and to resolve this we need to create a new IClientSideObjectWriterFactory and a new IClientSideObjectWriter. This is what I do:

    1 - 创建一个新的IClientSideObjectWriterFactory:

    1- Create a new IClientSideObjectWriterFactory:

    public class JsonClientSideObjectWriterFactory : IClientSideObjectWriterFactory
    {
        public IClientSideObjectWriter Create(string id, string type, TextWriter textWriter)
        {
            return new JsonClientSideObjectWriter(id, type, textWriter);
        }
    }
    



    2 - 创建一个新的IClientSideObjectWriter,这次我做的未实现该接口,我继承了ClientSideObjectWriter和overrided的AppendObject和AppendCollection方式:

    2- Create a new IClientSideObjectWriter, this time I do not implement the interface, I've inherited the ClientSideObjectWriter and overrided the AppendObject and AppendCollection methods:

    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标签在编辑模式下客户端模板,如果我们不编码那么浏览器就会呈现标签。如果不使用从字符串对象替换我没有找到一个workarround呢。

    NOTE: The replace its because the grid renders html tags for the client template in edit mode and if we don't encode then the browser will render the tags. I didn't find a workarround yet if not using a Replace from string object.

    3在我的Global.asax.cs中的Application_Start我注册了我的新工厂这样的

    3- On my Application_Start on Global.asax.cs I registered my new factory like this:

    DI.Current.Register<IClientSideObjectWriterFactory>(() => new JsonClientSideObjectWriterFactory());
    

    和它的工作为Telerik的具有所有组件。我不唯一改变的是,这是对的EntityFramework类一样PropertyNameIgnoreContractResolver。

    And it worked for all components that Telerik has. The only thing that I do not changed was the PropertyNameIgnoreContractResolver that was the same for the EntityFramework classes.

    这篇关于Telerik的MVC电网与阿贾克斯绑定使用EntityObjects得到循环引用除外的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆