我已經使用了Telerik MVC Grid很長一段時間了,它是一個很棒的控件,但是,一個惱人的事情一直在顯示與使用Ajax綁定到從實體框架創建和返回的對象。實體對象具有循環引用,並且當您從Ajax回調中返回一個IEnumerable時,如果有循環引用,它將從JavascriptSerializer中生成一個異常。發生這種情況是因爲MVC Grid使用JsonResult,而JsonResult又使用不支持序列化循環引用的JavaScriptSerializer。使用EntityObjects進行Ajax綁定的Telerik MVC Grid獲取循環引用異常
我對這個問題的解決方法是使用LINQ創建沒有相關實體的視圖對象。這適用於所有情況,但需要創建新對象以及將數據從實體對象複製到這些視圖對象。不是很多的工作,但它是工作。
我終於想出瞭如何一般性地使網格不會序列化循環引用(忽略它們),我想爲大衆分享我的解決方案,因爲我認爲它是通用的,並且很好地插入到環境中。
該解決方案有幾個部分的
- 交換的默認網格串行與自定義序列
- 安裝Json.Net插件可從Newtonsoft(這是一個偉大的圖書館)
- 使用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 }; } }
實現自定義操作結果。這個代碼大部分是樣板。唯一有趣的部分是在它調用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() })); } } }
將工廠對象添加到telerik網格中。我在global.asax Application_Start()方法中這樣做,但實際上它可以在任何有意義的地方完成。
DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory());
創建檢查_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; } }
修改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。這顯然不是最佳的。
我會歡迎任何有關如何改進此解決方案的反饋意見。我希望它能幫助別人。