Telerik MVC Gridを使用していますが、これは素晴らしいコントロールですが、Ajax Bindingでグリッドを使用することに関連して、 Entity Frameworkから作成され、返されたオブジェクトEntityオブジェクトには循環参照があり、AjaxコールバックからIEnumerableを返すと、循環参照がある場合はJavascriptSerializerから例外が生成されます。これは、MVCグリッドがJsonResultを使用し、循環参照の直列化をサポートしないJavaScriptSerializerを使用するためです。Telerik MVC Grid with Ajax EntityObjectsを使用したバインディングで循環参照例外
この問題の解決方法は、LINQを使用して関連するエンティティを持たないビューオブジェクトを作成することでした。これはすべてのケースで機能しますが、新しいオブジェクトの作成と、これらのビュー・オブジェクトへのエンティティ・オブジェクト間でのデータのコピーが必要です。仕事の多くはありませんが、それは仕事です。
グリッドを一般的にどのようにして循環参照を直列化しないか(無視する)、最終的に私はそれが一般的だと思うので、私の解決策を共有したいと考えました。
ソリューションはJson.Netプラグインで利用できるNewtonsoftからカスタム・シリアライザ
- スワップデフォルトのグリッド・シリアライザをインストールしている(これは偉大なライブラリです)
- Json.Netを使用してグリッドシリアライザを実装する
- [JsonIgnore]属性をナビゲーションプロパティの前に挿入するようにModel.ttファイルを変更します。
- オーバーライド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 }; } }
カスタムアクションレスを実装します。このコードは大半が定型句です。興味深い部分は、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());
_entityWrapperをチェックし、その属性を無視DefaultContractResolverクラスを作成します。リゾルバはPOCOオブジェクトの関連エンティティプロパティを無視属性を注入するModel1.ttファイルを修正手順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; } }
にSerializeObject()呼び出しに渡されます。注入されなければならない属性は[JsonIgnore]です。これは、このポストに追加するのが最も難しい部分ですが、Model1.tt(またはプロジェクト内のファイル名)にするのは難しくありません。また、最初にコードを使用している場合、[JsonIgnore]属性を循環参照を作成する属性の前に手動で配置することもできます。
.ttファイル内のregion.Begin( "Navigation Properties")を検索します。これは、すべてのナビゲーションプロパティがコード生成された場所です。多くの人をXXXに配慮しなければならないケースが2つあり、Singular参照があります。あるプロパティは、挿入する必要が
RelationshipMultiplicity.Many
ちょうどそのコードブロックの後の文のtahtをチェックしている場合場合は、[JasonIgnore]属性にproprty名を注入ライン
<#=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ライブラリが生成された各ファイルの先頭に "使用"されていることを確認することです。 Model.ttファイルのWriteHeader()への呼び出しを検索します。このメソッドは、余分な使用法(extraUsings)を追加する文字列配列パラメータを取ります。 nullを渡す代わりに、文字列の配列をconnstructし、配列の最初の要素として "Newtonsoft.Json"文字列を送ります。コールは、今のようになります。すべての
WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
ザッツが行うことです、とeverthingは、オブジェクトごとに、作業を開始します。
は今免責事項
- のために私はそれの私の実装は 最適ではないかもしれないJson.Netを使用したことがありません。
- 私は現在約2日間試験しており、この技術が失敗した場合は見つかりませんでした。私もJavascriptSerializerとJSon.Netのシリアライザとの間の非互換性を発見していないが、それdoesntの意味 そこには、任意の
- をアレントのみ、他の注意点は、私はに名前で「_entityWrapper」と呼ばれるプロパティをテストしていますということです
- そのignoredプロパティをtrueに設定します。これは明らかに最適ではありません。
このソリューションを改善する方法についてのご意見をお待ちしております。私はそれが他の人を助けることを望む。