2011-10-25 14 views
4

Telerik MVC Gridを使用していますが、これは素晴らしいコントロールですが、Ajax Bindingでグリッドを使用することに関連して、 Entity Frameworkから作成され、返されたオブジェクトEntityオブジェクトには循環参照があり、AjaxコールバックからIEnumerableを返すと、循環参照がある場合はJavascriptSerializerから例外が生成されます。これは、MVCグリッドがJsonResultを使用し、循環参照の直列化をサポートしないJavaScriptSerializerを使用するためです。Telerik MVC Grid with Ajax EntityObjectsを使用したバインディングで循環参照例外

この問題の解決方法は、LINQを使用して関連するエンティティを持たないビューオブジェクトを作成することでした。これはすべてのケースで機能しますが、新しいオブジェクトの作成と、これらのビュー・オブジェクトへのエンティティ・オブジェクト間でのデータのコピーが必要です。仕事の多くはありませんが、それは仕事です。

グリッドを一般的にどのようにして循環参照を直列化しないか(無視する)、最終的に私はそれが一般的だと思うので、私の解決策を共有したいと考えました。

ソリューションはJson.Netプラグインで利用できるNewtonsoftからカスタム・シリアライザ

  • と部品のカップル

    1. スワップデフォルトのグリッド・シリアライザをインストールしている(これは偉大なライブラリです)
    2. Json.Netを使用してグリッドシリアライザを実装する
    3. [JsonIgnore]属性をナビゲーションプロパティの前に挿入するようにModel.ttファイルを変更します。
    4. オーバーライド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. カスタムアクションレスを実装します。このコードは大半が定型句です。興味深い部分は、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() 
                        })); 
           } 
          } 
      } 
      
    3. 工場オブジェクトをtelerikグリッドに追加します。私はglobal.asax Application_Start()メソッドでこれを行いますが、現実的にはどこでも理にかなっています。

      DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory()); 
      
    4. _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; 
          } 
      } 
      
    5. に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に設定します。これは明らかに最適ではありません。

    このソリューションを改善する方法についてのご意見をお待ちしております。私はそれが他の人を助けることを望む。

  • 答えて

    0

    私は最初のソリューションは、グリッドの編集モードで動作しますが、私たちはの負荷と同じ問題を抱えている... CustomGridActionResultFactoryを実装しますが呼び出されないメソッドを作成するための私のApplication_Startに

    1

    を新しいコールを置きますグリッドに循環参照を持つオブジェクトの行が既に存在し、これを解決するには、新しいIClientSideObjectWriterFactoryと新しいIClientSideObjectWriterを作成する必要があります。 これは私が何をすべきかです:新しいIClientSideObjectWriterを作成し、私はインターフェイスを実装していないこの時間は、私はClientSideObjectWriterを継承し、オーバーライドしまし

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

    :2-

    1-新しいIClientSideObjectWriterFactoryを作成します。 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タグをレンダリングするので、そのを交換し、私たちはコードしない場合は、ブラウザがタグをレンダリングします。文字列オブジェクトからの置換を使用していない場合は、まだワークラウンドが見つかりませんでした。 Global.asax.csの私のApplication_Startで

    3 - 私はこのような私の新しい工場登録:

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

    をそして、それはTelerikが持っているすべてのコンポーネントのために働きました。私が変更しなかったのは、EntityFrameworkクラスで同じPropertyNameIgnoreContractResolverだけでした。

    0

    私は実装がやや簡単かもしれないと思う少し異なるアプローチを採用しました。

    私がすべては私のシリアライザSerializing Entity Framework problemsでこれを組み合わせると、あなたが持っている

    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)); 
         } 
        } 
    

    代わりに、通常の[GridAction]属性のグリッドJSONを返すメソッドに拡張[Grid]属性を適用で循環参照を回避する簡単な方法ですが、必要に応じて複数のレベルをシリアル化することもできます(必要なもの)

    注意:(1.3以降わからないが、私は多分考えて)あなたは、最新バージョンをダウンロードする必要がありますので、Telerikは私にとって非常に最近、この仮想CreateActionResultを追加

    0

    もう一つの良いパターンは、単にからViewModelを作成しないようにしないことですモデル。 ViewModelを含めると良いパターンです。これは、直前のUIに関連したモデルの微調整を行う機会を与えます。たとえば、boolを調整して、関連する文字列をYまたはNにすると、UIを見栄え良くすることができます。逆も同様です。 時々、ViewModelはモデルとまったく同じで、プロパティをコピーするコードは不要ですが、パターンは良いものであり、それに固執するのがベストプラクティスです。

    関連する問題