2011-10-25 45 views
4

我已經使用了Telerik MVC Grid很長一段時間了,它是一個很棒的控件,但是,一個惱人的事情一直在顯示與使用Ajax綁定到從實體框架創建和返回的對象。實體對象具有循環引用,並且當您從Ajax回調中返回一個IEnumerable時,如果有循環引用,它將從JavascriptSerializer中生成一個異常。發生這種情況是因爲MVC Grid使用JsonResult,而JsonResult又使用不支持序列化循環引用的JavaScriptSerializer。使用EntityObjects進行Ajax綁定的Telerik MVC Grid獲取循環引用異常

我對這個問題的解決方法是使用LINQ創建沒有相關實體的視圖對象。這適用於所有情況,但需要創建新對象以及將數據從實體對象複製到這些視圖對象。不是很多的工作,但它是工作。

我終於想出瞭如何一般性地使網格不會序列化循環引用(忽略它們),我想爲大衆分享我的解決方案,因爲我認爲它是通用的,並且很好地插入到環境中。

該解決方案有幾個部分的

  1. 交換的默認網格串行與自定義序列
  2. 安裝Json.Net插件可從Newtonsoft(這是一個偉大的圖書館)
  3. 使用Json.Net實現網格串化器
  4. 修改Model.tt文件以在導航屬性前插入[JsonIgnore]屬性
  5. 覆蓋Json.Net的DefaultContractResolver並尋找_entityWrapper屬性名,以確保這也忽略(注射包裝由POCO類或實體框架)

在自己和所有這些步驟是容易的,但沒有他們全部你不能利用這種技術。

一旦正確實施,我現在可以輕鬆地將任何實體框架對象直接發送到客戶端,而無需創建新的View對象。我不推薦爲每個對象,但有時它是最好的選擇。同樣重要的是要注意,任何相關的條目都不能在客戶端使用,所以不要使用它們。

下面是步驟需要

  1. 創建您的應用程序下面的類的地方。這個類是網格用來獲取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 
         }; 
        } 
    } 
    
  2. 實現自定義操作結果。這個代碼大部分是樣板。唯一有趣的部分是在它調用JConverter.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() 
                      })); 
         } 
        } 
    } 
    
  3. 將工廠對象添加到telerik網格中。我在global.asax Application_Start()方法中這樣做,但實際上它可以在任何有意義的地方完成。

    DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory()); 
    
  4. 創建檢查_entityWrapper並忽略該屬性的DefaultContractResolver類。分解器被傳遞到SerializeObject()調用在步驟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; 
        } 
    } 
    
  5. 修改Model1.tt文件來注入忽略POCO對象的相關實體的屬性的屬性。必須注入的屬性是[JsonIgnore]。這是添加到這篇文章中最難的部分,但在Model1.tt(或者您的項目中的任何文件名)中並不難。另外,如果您先使用代碼,則可以手動將[JsonIgnore]屬性放置在創建循環引用的任何屬性前面。

    搜索.tt文件中的region.Begin(「Navigation Properties」)。這是所有導航屬性都是代碼生成的地方。有兩種情況必須由XXX和單數引用來處理。有一個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庫在每個生成文件的頂部都是「Used」。在Model.tt文件中搜索對WriteHeader()的調用。該方法接受一個字符串數組參數,用於添加額外的使用(extraUsings)。而不是傳遞null來構造一個字符串數組,並將「Newtonsoft.Json」字符串作爲數組的第一個元素髮送。該呼叫現在應該是這樣的:

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

這就是所有有做,和寄託都開始工作,爲每個對象。

現在的免責聲明

  • 我從來沒有使用Json.Net所以我實現它可能不是最優的 。
  • 我已經測試了大約兩天,並沒有發現任何這種技術失敗的情況。
  • 我還沒有找到JavascriptSerializer和JSon.Net串行但那不之間的任何不兼容的意思 有任何的arent
  • 唯一的另外一點是,我正在測試一個名爲「_entityWrapper」按名稱屬性將其忽略的屬性設置爲true。這顯然不是最佳的。

我會歡迎任何有關如何改進此解決方案的反饋意見。我希望它能幫助別人。

回答

0

我把新的呼叫進入我的Application_Start爲貫徹CustomGridActionResultFactory但create方法從來沒有所謂的...

1

第一個解決方案可與電網編輯模式,但我們有一個的負載同樣的問題網格已經有循環引用對象的行,爲了解決這個問題,我們需要創建一個新的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和overrided的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的具有所有組件。唯一不改變的是PropertyNameIgnoreContractResolver,它與EntityFramework類相同。

0

我採取了一種略有不同的方法,我相信這可能會更容易實現。

我所要做的就是應用擴展[Grid]屬性電網JSON返回方法,而不是正常的[GridAction]屬性

public class GridAttribute : GridActionAttribute, IActionFilter 
    {  
    /// <summary> 
    /// Determines the depth that the serializer will traverse 
    /// </summary> 
    public int SerializationDepth { get; set; } 

    /// <summary> 
    /// Initializes a new instance of the <see cref="GridActionAttribute"/> class. 
    /// </summary> 
    public GridAttribute() 
     : base() 
    { 
     ActionParameterName = "command"; 
     SerializationDepth = 1; 
    } 

    protected override ActionResult CreateActionResult(object model) 
    {  
     return new EFJsonResult 
     { 
     Data = model, 
     JsonRequestBehavior = JsonRequestBehavior.AllowGet, 
     MaxSerializationDepth = SerializationDepth 
     }; 
    } 
} 

public class EFJsonResult : JsonResult 
    { 
    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 EFJsonResult() 
    { 
     MaxJsonLength = 1024000000; 
     RecursionLimit = 10; 
     MaxSerializationDepth = 1; 
    } 

    public int MaxJsonLength { get; set; } 
    public int RecursionLimit { get; set; } 
    public int MaxSerializationDepth { get; set; } 

    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) 
     { 
     var serializer = new JavaScriptSerializer 
     { 
      MaxJsonLength = MaxJsonLength, 
      RecursionLimit = RecursionLimit 
     }; 

     serializer.RegisterConverters(new List<JavaScriptConverter> { new EFJsonConverter(MaxSerializationDepth) }); 

     response.Write(serializer.Serialize(Data)); 
     } 
    } 

與我的串行Serializing Entity Framework problems結合這和你有一個避免循環引用的簡單方法,但也可以選擇序列化多個級別(我需要)

注意:Telerik的最近加入這個虛擬CreateActionResult我,所以你可能需要下載最新版本(不知道,但我想,也許1.3+)

0

另一個很好的模式是根本就沒有避免創建從一個ViewModel模型。 這是一個很好的模式,包括ViewModel。它使您有機會對模型進行最後一刻的UI相關調整。例如,您可以調整布爾以使關聯字符串YN有助於使界面看起來更好,反之亦然。 ViewModel有時候和Model完全一樣,複製屬性的代碼看起來沒有必要,但是這個模式是一個很好的模式,堅持這一點是最好的做法。

相關問題