ASP.NET MVCのリンクテーブルのデータを使用するか、Lookupコンポーネントを開発する

ビジネスアプリケーションの開発は、何らかの方法で、一定量のデータの処理、これらのデータ間の関係の構築、および便利なプレゼンテーションに関連しています。 この記事では、ASP.net MVCでのテーブル間相互作用、およびこの相互作用を視覚化する機能、独自のコンポーネントの開発を検討します。これにより、一方で必要なデータを簡単に選択でき、他方で簡単に構成できます。 JqG​​ridを使用して、関連データの検索、ソート、選択を実装します。 動的な述語の形成に触れ、htmlヘルパーでメタデータを使用する方法を確認し、最後にこのクラスの既存のコンポーネントを検討します。



すべての読者が確実に認識している最も単純な例では、通常のDropDownListを使用して関連テーブルのデータを表示できますが、その使用は非常に限られており、常に効果的ではありません。 私たちのケースでは、コンポーネントを記述する明確な要件があり、組み込みのリスト、並べ替え、および関連データによる検索があり、このタイプのフィールドが多数あったため、対応するコンポーネントを開発することが決定されました。



「ユーザー」と「グループ」という2つの関連テーブルの例を考えてみましょう



public class UserProfile { [Key] public int UserId { get; set; } public string UserName { get; set; } public int? UserGroupId { get; set; } public virtual UserGroup UserGroup { get; set; } } public class UserGroup { [Key] public int UserGroupId { get; set; } [DisplayName("Group Name")] public string GroupName { get; set; } [DisplayName("Group Description")] public string Description { get; set; } public virtual ICollection<UserProfile> Users { get; set; } }
      
      





グループにはN番目のユーザーを含めることができ、ユーザーは特定のグループに対応できることがわかります。 それでは、このデータを受け取り、視覚化できるコードを見てみましょう。 エントリのリストがあるページの場合、これは非常に簡単です。



  public ActionResult Index() { var userProfiles = _db.UserProfiles.Include(c => c.UserGroup); return View(userProfiles.ToList()); }
      
      





実際、上記のコントローラーコードでは、ユーザープロファイルデータに加えて、このプロファイルに関連付けられたグループを要求します。 次に、DisplayNameForを使用してビューに表示します。



  @Html.DisplayNameFor(model => model.UserGroup.GroupName)
      
      





関連付けられたデータをユーザーに表示するだけであれば、これで十分です。 既に述べたように、編集にはDropDownListを使用できますが、この場合、上記のリンクテーブルへのクエリなど、より柔軟なコントロールを作成し、構成をできる限り簡単にする必要があります。 最初に行うことは、Htmlヘルパーの開発です。これにより、ビュー内でのコンポーネントの使用を簡単に記述し、その機能を確保できます。



1. LookupコンポーネントのHtmlヘルパーの開発



ASP.net MVCのHtmlヘルパーとは何ですか? ほとんどの場合、これらは一般的な拡張メソッドであり、親がクラスにアクセスしてHTMLコンテンツを作成できるようにします。 コンポーネントを表示するには、標準のルックアップコントロールビュー、つまりテキストフィールドとボタンを使用します。 IDレコードは非表示フィールドに保存されます。

htmlコンテンツに加えて、htmlヘルパーを使用すると、使用されているモデルとフィールドのメタデータにアクセスすることもできるため、最初に行うことは、モデル内のフィールドを強調表示できる属性を作成し、コンポーネントが正しく機能するために必要な追加情報を提供することです。



そのため、LookupAttributeコードを以下に示します



  [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public sealed class LookupAttribute : Attribute { public Type Model { get; set; } public string NameField { get; set; } }
      
      





ここではすべてが簡単です。関連レコードのテキスト説明として使用するフィールドと、参照するモデルのタイプを保存します。 したがって、モデルのコードはわずかに変換できます。



 public class UserProfile { [Key] public int UserId { get; set; } public string UserName { get; set; } [Lookup(Model = typeof(UserGroup), NameField = "GroupName")] public int? UserGroupId { get; set; } public virtual UserGroup UserGroup { get; set; } }
      
      





これで、GroupNameのテキスト表現のフィールドであるUserGroupモデルを参照することがわかります。 ただし、この属性をHTMLヘルパーで使用するには、ビューのメタデータのコレクションに追加する必要があります。 これを行うには、DataAnnotationsModelMetadataProviderから派生したクラスを実装し、それに応じて登録する必要があります。



  public class LookupMetadataExtension : DataAnnotationsModelMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); var additionalValues = attributes.OfType<LookupAttribute>().FirstOrDefault(); if (additionalValues != null) { metadata.AdditionalValues.Add(LookupConsts.LookupMetadata, additionalValues); } return metadata; } }
      
      





フィールドメタデータを展開できるようにするには、DataAnnotationsModelMetadataProviderクラスから継承し、CreateMetadataメソッドをオーバーライドする必要があります。 DataAnnotationsModelMetadataProviderクラスは、ASP.NET MVCの既定のメタデータモデルプロバイダーを実装します。

すべてが非常に簡単です。 転送された属性のコレクションに存在する場合、AdditionalValuesメタデータコレクションに追加する必要があります。その後、変更されたコレクションを返します。 このクラスが正しく機能するには、登録する必要があります。 Global.asax.csに移動して、次の行を追加します。



 ModelMetadataProviders.Current = new LookupMetadataExtension();
      
      





これで、HTMLヘルパーの開発を続ける準備が整いました。 一般的に、HTMLヘルパー関数は次のようになります



  public static MvcHtmlString LookupFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string filterAction, Type modelType, String nameField, IDictionary<string, object> htmlAttributes) { var fieldName = ExpressionHelper.GetExpressionText(expression); var commonMetadata = PrepareLookupCommonMetadata( ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData), htmlHelper.ViewData.ModelMetadata, modelType, nameField); var lookupAttribute = commonMetadata.AdditionalValues[LookupConsts.LookupMetadata] as LookupAttribute; return LookupHtmlInternal(htmlHelper, commonMetadata, lookupAttribute, fieldName, filterAction, htmlAttributes); }
      
      





また、ビューからモデルのタイプを直接指定する機会もユーザーに与えることに注意してください。 最初の行でフィールドの名前を取得し、PrepareLookupCommonMetadata関数を呼び出します。 この関数は後で検討されます。メタデータを処理し、このメタデータを介してリンクテーブルのデータにアクセスするために使用されるだけです。 式式を使用するModelMetadata.FromLambdaExpression(式、htmlHelper.ViewData)行は、現在のフィールド(実際にはAdditionalValues)のメタデータを取得します。 次に、返されたcommonMetadataオブジェクトから、lookupAttributeを取得し、HTMLコードを生成するための関数を呼び出します。



それでは、PrepareLookupCommonMetadataメタデータ処理関数を見てみましょう。



  private static ModelMetadata PrepareLookupCommonMetadata(ModelMetadata fieldMetadata, ModelMetadata modelMetadata , Type modelType, String nameField) { LookupAttribute lookupMetadata; if (modelType != null && nameField != null) { lookupMetadata = new LookupAttribute { Model = modelType, NameField = nameField }; if (fieldMetadata.AdditionalValues.ContainsKey(LookupConsts.LookupMetadata)) fieldMetadata.AdditionalValues.Remove(LookupConsts.LookupMetadata); fieldMetadata.AdditionalValues.Add(LookupConsts.LookupMetadata, lookupMetadata); }
      
      





まず、ユーザーがビューでタイプとモデルを指定したかどうかを確認し、指定した場合は、AdditionalValuesのデータを更新します。 進む



  if (fieldMetadata.AdditionalValues != null && fieldMetadata.AdditionalValues.ContainsKey(LookupConsts.LookupMetadata)) { lookupMetadata = fieldMetadata.AdditionalValues[LookupConsts.LookupMetadata] as LookupAttribute; if (lookupMetadata != null) { var prop = lookupMetadata.Model.GetPropertyWithAttribute("KeyAttribute"); var releatedTableKey = prop != null ? prop.Name : String.Format("{0}Id", lookupMetadata.Model.Name); fieldMetadata.AdditionalValues.Add("idField", releatedTableKey); var releatedTableMetadata = modelMetadata.Properties.FirstOrDefault(proper => proper.PropertyName == lookupMetadata.Model.Name); if (releatedTableMetadata != null) { UpdateLookupColumnsInfo(releatedTableMetadata, fieldMetadata); UpdateNameFieldInfo(lookupMetadata.NameField, releatedTableMetadata, fieldMetadata); } else { throw new ModelValidationException(String.Format( "Couldn't find data from releated table. Lookup failed for model {0}", lookupMetadata.Model.Name)); } } } else { throw new ModelValidationException(String.Format("Couldn't find releated model type. Lookup field")); } return fieldMetadata; }
      
      





AdditionalValuesに場所があることを確認し、メタデータコレクションから取得します。 次に、GetPropertyWithAttribute Type拡張メソッドを使用して、関連付けられたモデルからキー属性を持つフィールドを取得します。 このフィールドを使用して、リレーションシップを識別します。つまり、このフィールドはリンクテーブルの主キーです。 見つからない場合は、モデル名+ Id =主キーというルールを使用して自分自身を形成しようとします。 この値をidValueとしてAdditionalValuesに追加します。 次に、リンクテーブルのメタデータを名前で取得しようとします。

受信すると、リンクされたテーブルの列情報とテキスト定義を取得します。

ここで、列に関する情報の取得について説明します。 このフィールドのリストは、JqGridでレコードを表示するために使用されます。 このリストを構成するには、別の属性を作成します。



  [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class LookupGridColumnsAttribute : Attribute { public string[] LookupColumns { get; set; } public LookupGridColumnsAttribute(params string[] values) { LookupColumns = values; } }
      
      





次に、リンクテーブルの変更されたビューを見てください。 LookupGridColumnsAttributeを登録する必要はありません。モデルのタイプを記述するModelフィールドを使用して、LookupAttributeを介してこのタイプにアクセスできます。



  [LookupGridColumns(new[] { "Description" })] public class UserGroup { [Key] public int UserGroupId { get; set; } [DisplayName("Group Name")] public string GroupName { get; set; } [DisplayName("Group Description")] public string Description { get; set; } public virtual ICollection<UserProfile> Users { get; set; } }
      
      





列リストには、すでに存在するデフォルトのGroupNameに加えて、Descriptionを追加します。 ここで、列ごとにメタデータを準備する関数の検討に戻ります。



  private static void UpdateLookupColumnsInfo(ModelMetadata releatedTableMetadata, ModelMetadata metadata) { IDictionary<string, string> columns = new Dictionary<string, string>(); var gridColumns = releatedTableMetadata.ModelType.GetCustomAttributeByType<LookupGridColumnsAttribute>(); if (gridColumns != null) { foreach (var column in gridColumns.LookupColumns) { var metadataField = releatedTableMetadata.Properties.FirstOrDefault( propt => propt.PropertyName == column); if (metadataField != null) { columns.Add(column, metadataField.DisplayName); } else { throw new ModelValidationException( String.Format("Couldn't find column in releated table {0}", releatedTableMetadata.GetDisplayName())); } } metadata.AdditionalValues.Add("lookupColumns", columns); } }
      
      





この関数は引数として、リンクテーブルのメタデータとフィールドのメタデータを受け取ります。 リンクテーブルのメタデータで、指定されたLookupGridColumnsAttribute属性を見つけようとします。 nullではないように見え、メタデータを要求する途中で列のリストを調べて、DisplayNameを表示するための対応する列を取得します。 メタデータが見つからない場合は例外をスローし、見つからない場合は受信したデータを列コレクションに追加します。 列のコレクションが形成されたら、AdditionalValuesの形式でフィールドメタデータに追加します。これらはさらに役立ちます。



今こそ、PrepareLookupCommonMetadata関数に戻り、最後の呼び出し、つまりUpdateNameFieldInfoを検討するときです。



 private static void UpdateNameFieldInfo(string nameField, ModelMetadata releatedTableMetadata, ModelMetadata commonMetadata) { var nameFieldMetedata = releatedTableMetadata.Properties.FirstOrDefault(propt => propt.PropertyName == nameField); if (nameFieldMetedata != null) { commonMetadata.AdditionalValues.Add("lookupFieldValue", nameFieldMetedata.SimpleDisplayText); commonMetadata.AdditionalValues.Add("lookupFieldDisplayValue", nameFieldMetedata.DisplayName); } else { throw new ModelValidationException(String.Format("Couldn't find name field in releated table {0}", releatedTableMetadata.GetDisplayName())); } }
      
      





この関数は、接続のテキスト表現に関するすべての情報、つまりLookup属性で“ NameField =“ GroupName“”として指定した同じフィールドに関する情報を受け取り、この情報をフィールドのAdditionalValuesメタデータに追加します。 nameFieldMetedata.SimpleDisplayText-リンクテーブルのGroupNameフィールドの値。 nameFieldMetedata.DisplayName-リンクテーブルのGroupNameフィールドの名前。



これは、適切なHTMLコードを作成するために必要なすべての情報を持っていると言えます。 それがどのように機能し、LookupHtmlInternal関数が受け入れるものを見てみましょう。 HtmlHelperセクションの最初にあるLookupFor関数からの呼び出しであることを思い出してください。



  private static MvcHtmlString LookupHtmlInternal(HtmlHelper htmlHelper, ModelMetadata metadata, LookupAttribute lookupMetadata, string name, string action, IDictionary<string, object> htmlAttributes) { if (string.IsNullOrEmpty(name)) { throw new ArgumentException("Error", "htmlHelper"); } var divBuilder = new TagBuilder("div"); divBuilder.MergeAttribute("id", String.Format("{0}_{1}", name, "div")); divBuilder.MergeAttribute("class", "form-wrapper cf"); divBuilder.MergeAttribute("type", lookupMetadata.Model.FullName); divBuilder.MergeAttribute("nameField", lookupMetadata.NameField); divBuilder.MergeAttribute("idField", metadata.AdditionalValues["idField"] as string); divBuilder.MergeAttribute("nameFieldDisplay", metadata.AdditionalValues["lookupFieldDisplayValue"] as string); divBuilder.MergeAttribute("action", action);
      
      





以下の引数を受け入れます。 1. htmlHelper-htmlコードを生成できます; 2.メタデータ-実際、これはすべてのextを含むフィールドのメタデータです。 情報収集の段階で取得されたメタデータ。 3.専用のlookupMetadataを個別に。 4. name-ビューのように、フィールドの名前。 5アクション-データを要求するために使用されるコントローラーとメソッドを指定します。 5 htmlAttributes-内線 プログラマーによって定義されたhtml属性。

次に、フィールド名がnullではないことがわかり、フィールドのメインパラメーターを含むdivを作成します。 主なパラメータについて説明します:type-参照しているモデルのタイプ、nameField-関係を識別するリンクテーブルのテキストフィールドの名前(この場合はグループの名前)、idField-リンクテーブルの主キー、nameFieldDisplay-リンクテーブルのテキストフィールドの値、接続とアクションを識別します-私が言ったように、これはデータを要求するために使用されるコントローラーとメソッドです。



  var columnsDivBuilder = new TagBuilder("div"); columnsDivBuilder.MergeAttribute("id", String.Format("{0}_{1}", name, "columns")); columnsDivBuilder.MergeAttribute("style", "display:none"); if (metadata.AdditionalValues.ContainsKey("lookupColumns")) { var columns = ((IDictionary<string, string>)metadata.AdditionalValues["lookupColumns"]); var columnString = String.Empty; foreach (var column in columns.Keys) { var columnDiv = new TagBuilder("div"); columnDiv.MergeAttribute("colName", column); columnDiv.MergeAttribute("displayName", columns[column]); columnString += columnDiv.ToString(TagRenderMode.SelfClosing); } columnsDivBuilder.InnerHtml = columnString; }
      
      





さらに、同じスキームに従って、JqGridのビューを構築するために使用されるリンクテーブルのすべての列を含むdivを作成します。



  var inputBuilder = new TagBuilder("input"); inputBuilder.MergeAttributes(htmlAttributes); inputBuilder.MergeAttribute("type", "text"); inputBuilder.MergeAttribute("class", "lookup", true); inputBuilder.MergeAttribute("id", String.Format("{0}_{1}", name, "lookup"), true); inputBuilder.MergeAttribute("value", metadata.AdditionalValues["lookupFieldValue"] as string, true); var hiddenInputBuilder = new TagBuilder("input"); hiddenInputBuilder.MergeAttribute("type", "hidden"); hiddenInputBuilder.MergeAttribute("name", name, true); hiddenInputBuilder.MergeAttribute("id", name, true); hiddenInputBuilder.MergeAttribute("value", metadata.SimpleDisplayText, true); var buttonBuilder = new TagBuilder("input"); buttonBuilder.MergeAttribute("type", "button"); buttonBuilder.MergeAttribute("value", "Lookup"); buttonBuilder.MergeAttribute("class", "lookupbutton"); buttonBuilder.MergeAttribute("id", String.Format("{0}_{1}", name, "lookupbtn"), true);
      
      





残りの属性、つまり、接続のテキスト表現を含むフィールド(nameField)、接続のidを含む非表示フィールド、リンクテーブルのデータでJqGridを開くボタンを形成します。

次のmetadata.SimpleDisplayTextの呼び出しを使用して、フィールドメタデータから現在選択されているレコードのIDを取得することに注意してください。



  divBuilder.InnerHtml = String.Format(@"{0}{1}{2}{3}", inputBuilder.ToString(TagRenderMode.SelfClosing), hiddenInputBuilder.ToString(TagRenderMode.SelfClosing), buttonBuilder.ToString(TagRenderMode.SelfClosing), columnsDivBuilder.ToString(TagRenderMode.Normal) ); return new MvcHtmlString(divBuilder.ToString(TagRenderMode.Normal)); }
      
      





生成したすべてをルートdivにパックし、表示するためにHTML文字列をブラウザに返します。



htmlヘルパーを簡単に使用するために、LookupForメソッドオーバーロードも実装します



  public static MvcHtmlString LookupFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) { var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext); return LookupFor(htmlHelper, expression, urlHelper.Action("LookupData"), null, null, null); } public static MvcHtmlString LookupFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string filterAction) { return LookupFor(htmlHelper, expression, filterAction, null, null, null); }
      
      





htmlヘルパーを使用するには、ビューでHtml.LookupFor(model => model.UserGroupId)を呼び出すだけで十分です。

インテリセンスをビューで機能させるには、Htmlヘルパーを実装するクラスがweb.configにある名前空間をsystem.web-> pages-> namespacesセクションに追加するか、このクラスを定義済みの名前空間のいずれかに配置する必要がありますSystem.Web.Helpersで言いましょう。 または、ビューで直接、<@ using your.namespace>を指定します。



これは、HtmlHelperの開発が終わり、第2部に進んでいると言えます。



2.動的な述語の表現と形成。



開発者が「デフォルト」モードでコンポーネントの使用を簡単に開始できる一連の基本クエリを作成するには、アプリケーションの実行中にクエリツリーを作成できる述語を準備する必要があります。 最終的に動的Linqの形成を可能にするいくつかのメソッドを含むLinqExtensionsクラスを検討してください。 Whereメソッドを実装することから始めましょう。



  public static IQueryable<T> Where<T>(this IQueryable<T> source, string fieldName, string searchString, string compareFunction) { if (searchString == null) searchString = String.Empty; var param = Expression.Parameter(typeof(T)); var prop = Expression.Property(param, fieldName); var methodcall = Expression.Call(prop, typeof(String).GetMethod(compareFunction, new[] { typeof(string) }), Expression.Constant(value: searchString)); var lambda = Expression.Lambda<Func<T, bool>>(methodcall, param); var request = source.Where(lambda); return request; }
      
      





そのため、fieldNameは比較するデータフィールド、searchStringは比較する文字列、比較を実装するために使用されるStringクラスの関数です。 次に、すべてを詳細に分析します。 渡された文字列がnullではないようです。 すべてが順調であれば、Expression.Parameterのタイプ(typeof(T))を決定します。 実際、それはモデルのタイプになります。 次の行では、比較に使用するモデルのフィールドであるtypeプロパティを定義します。 次に、先ほど作成したsearchString引数と「プロパティポインター」を使用して、文字列クラスからcompareFunction関数の呼び出しを作成します。 次に、ラムダを作成し、IQueryableコンテキストを使用して、新しく作成された述語でラムダにWhereを適用します。 生成されたIQueryableを返します。



定義済みの文字列比較関数を使用していくつかの関数を実装



  public static IQueryable<T> WhereStartsWith<T>(this IQueryable<T> source, string fieldName, string searchString) { return Where(source, fieldName, searchString, "StartsWith"); } public static IQueryable<T> WhereContains<T>(this IQueryable<T> source, string fieldName, string searchString) { return Where(source, fieldName, searchString, "Contains"); }
      
      





画像と肖像画では、EqualメソッドとNotEqualメソッドを実装します



  public static IQueryable<T> Equal<T>(this IQueryable<T> source, string fieldName, string searchString) { if (searchString == null) searchString = String.Empty; var param = Expression.Parameter(typeof(T)); var prop = Expression.Property(param, fieldName); var methodcall = Expression.Equal(prop, Expression.Constant(searchString)); var lambda = Expression.Lambda<Func<T, bool>>(methodcall, param); var request = source.Where(lambda); return request; } public static IQueryable<T> NotEqual<T>(this IQueryable<T> source, string fieldName, string searchString) { if (searchString == null) searchString = String.Empty; var param = Expression.Parameter(typeof(T)); var prop = Expression.Property(param, fieldName); var methodcall = Expression.NotEqual(prop, Expression.Constant(searchString)); var lambda = Expression.Lambda<Func<T, bool>>(methodcall, param); var request = source.Where(lambda); return request; }
      
      





ここで、類推により、私は詳細に説明しません。



また、動的にソートできる必要があるため、ApplyOrderメソッドを実装します



  static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName) { var type = typeof(T); var param = Expression.Parameter(type); var pr = type.GetProperty(prop); var expr = Expression.Property(param, type.GetProperty(prop)); var ptype = pr.PropertyType; var delegateType = typeof(Func<,>).MakeGenericType(type, ptype); var lambda = Expression.Lambda(delegateType, expr, param); var result = typeof(Queryable).GetMethods().Single( method => method.Name == methodName && method.IsGenericMethodDefinition && method.GetGenericArguments().Length == 2 && method.GetParameters().Length == 2) .MakeGenericMethod(type, ptype) .Invoke(null, new object[] { source, lambda }); return (IOrderedQueryable<T>)result; }
      
      





引数による:1.プロパティ-ソートするフィールド。 2.methodName-ソートに使用するメソッド。 次に、一連のパラメーターを作成します。 この場合のMakeGenericTypeはデリゲートFunc <T、string>を形成し、それを使用してラムダを作成します。ラムダはmethodNameとして定義されたメソッドに引数として渡し、すべてリフレクションで呼び出します。



したがって、Queryableから並べ替えメソッドへの動的呼び出しを定義できるようになりました。



  public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, bool desc , string property) { return ApplyOrder(source, property, desc ? "OrderByDescending" : "OrderBy"); } public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property) { return ApplyOrder(source, property, "OrderBy"); } public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property) { return ApplyOrder(source, property, "OrderByDescending"); } public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property) { return ApplyOrder(source, property, "ThenBy"); } public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property) { return ApplyOrder(source, property, "ThenByDescending"); }
      
      





これで、Linqヘルパークラスの実装が完了し、次のステップに進みます。



3. ModelBinderとコンポーネントの構成。



私たちに送信される構成データの量は非常に多いため、それらを構造化し、任意の設定への簡単で理解しやすいアクセスを提供するオブジェクトに配置するとよいでしょう。jqgridを使用することを思い出してください。jqgridは、並べ替え、検索、ページネーション、および追加パラメーターに関するデータを提供し、必要に応じて個別に決定します。それでは、モデルに移りましょう。



  public enum SearchOperator { Equal, NotEqual, Contains } public class FilterSettings { public string SearchString; public string SearchField; public SearchOperator Operator; } public class GridSettings { public bool IsSearch { get; set; } public int PageSize { get; set; } public int PageIndex { get; set; } public string SortColumn { get; set; } public bool Asc { get; set; } } public class LookupSettings { public Type Model { get; set; } public FilterSettings Filter { get; set; } public GridSettings GridSettings { get; set; } public string IdField { get; set; } public string NameField { get; set; } }
      
      





クラスの説明については詳しく説明しません。次に、jqGridまたはルックアップから受信したデータをクラスの対応するインスタンスに変換できるようにするコードを検討します。

  public class LookupModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { HttpRequestBase request = controllerContext.HttpContext.Request; var lookupSettings = new LookupSettings { Model = Type.GetType(request["modelType"]), IdField = request["IdField"], NameField = request["NameField"], Filter = new FilterSettings { SearchString = request["searchString"] ?? String.Empty, SearchField = request["searchField"] } }; if(request["searchOper"] != null) { switch (request["searchOper"]) { case "eq": lookupSettings.Filter.Operator = SearchOperator.Equal; break; case "ne": lookupSettings.Filter.Operator = SearchOperator.NotEqual; break; case "cn": lookupSettings.Filter.Operator = SearchOperator.Contains; break; } } lookupSettings.GridSettings = new GridSettings {Asc = request["sord"] == "asc"}; if (request["_search"] != null) lookupSettings.GridSettings.IsSearch = Convert.ToBoolean(request["_search"]); if (request["page"] != null) lookupSettings.GridSettings.PageIndex = Convert.ToInt32(request["page"]); if (request["rows"] != null) lookupSettings.GridSettings.PageSize = Convert.ToInt32(request["rows"]); lookupSettings.GridSettings.SortColumn = request["sidx"]; if (lookupSettings.Filter.SearchField == null) { lookupSettings.Filter.SearchField = request["NameField"]; lookupSettings.Filter.Operator = SearchOperator.Contains; } return lookupSettings; } }
      
      





バインディングを実装するには、IModelBinderクラスから継承し、BindModel関数を実装する必要があります。ここで、controllerContextはコントローラーが動作するコンテキストです。コンテキスト情報には、コントローラー、HTTPコンテンツ、要求コンテキスト、およびルートデータに関する情報が含まれます。bindingContext-モデルがバインドされているコンテキスト。コンテキストには、モデルオブジェクト、モデル名、モデルタイプ、プロパティフィルター、値プロバイダーなどの情報が含まれます。HttpRequestBaseを取得し、このオブジェクトを使用してリクエストで渡されたデータを取得します。次に、設定モデルの構造を形成し、結果のクラスを返します。バインディングを機能させるには、登録する必要があるため、Global.asax.csに移動して、対応する呼び出しを追加しましょう。



  ModelBinders.Binders.Add(typeof(LookupSettings), new LookupModelBinder());
      
      







その結果、すべての登録後、Global.asax.csは次のようになります。



  protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelMetadataProviders.Current = new LookupMetadataExtension(); ModelBinders.Binders.Add(typeof(LookupSettings), new LookupModelBinder()); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); }
      
      





コントローラーで、次のエントリを使用して、ルックアップから受け取った引数を参照できます。



 public virtual ActionResult LookupData([ModelBinder(typeof(LookupModelBinder))] LookupSettings settings)
      
      







これで、設定オブジェクトの作業を終了し、次の段階に進みます。



4. Lookupコントロール用の一般的なMVCコントローラーの実装。



アプリケーションで使用するほとんどのルックアップでは、複雑な構成、フィルタリング、並べ替えは必要ないため、コンポーネントから派生したコンポーネントの種類に関係なく、基本的な並べ替えと検索を実装するオブジェクトを開発し、コントローラーはこのオブジェクトを使用して整理します「デフォルト」モードでのデータへのアクセス。 LookupDataResolverクラスから始めましょう。このクラスは検索操作を担当し、「デフォルト」モードでソートします。コンポーネントは、グリッドから要素を選択することに加えて、対応するフィールドに入力されたテキスト値に従って要素の解像度を提供する必要があることに注意してください。



型は実行時にのみ決定されるため、モデルを一般的な引数として典型化する関数を実装し、リクエストに対応する関数を呼び出します。したがって、次のコードを使用できますdbContext.Set()。AsQueryable();基本的なリクエストを作成します。



LookupMethodCall関数を検討してください。



  private static ActionResult LookupMethodCall(string methodName, LookupSettings settings, DbContext dbContext, OnAfterQueryPrepared onAfterQueryPrepared) { var methodLookupCall = typeof(LookupDataResolver). GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); methodLookupCall = methodLookupCall.MakeGenericMethod(settings.Model); var lookupSettings = Expression.Parameter(typeof(LookupSettings), "settings"); var dbCtx = Expression.Parameter(typeof(DbContext), "dbContext"); var funct = Expression.Parameter(typeof(OnAfterQueryPrepared), "onAfterQueryPrepared"); var lookupSearch = Expression.Lambda( Expression.Call( null, methodLookupCall, lookupSettings, dbCtx, funct), lookupSettings, dbCtx, funct); var lookupSearchDelegate = (Func<LookupSettings, DbContext, OnAfterQueryPrepared, JsonResult>) lookupSearch.Compile(); return lookupSearchDelegate(settings, dbContext, onAfterQueryPrepared); }
      
      





まず、methodNameメソッドの現在の型を調べます。その後、MakeGenericMethod関数を使用して、ジェネリック引数として使用するモデルを準備します。パラメーターを形成します:settings(ルックアップから取得した設定の本質)、dbContext(データベースにアクセスするためのコンテキスト)、onAfterQueryPrepared(データベースへの基本的なリクエストの形成直後に呼び出されるデリゲート。必要に応じて追加のフィルターを追加する必要があります)次に、メソッドを呼び出す対応するラムダを作成し、その後、それをコンパイルして呼び出します。



LookupMethodCall関数を使用して、リクエストに対応するメソッドを呼び出す関数を実装します。BasicLookupは、ルックアップでユーザーが入力したテキストを解決するために、汎用LookupSearch関数にアクセスします。BasicGridは、グリッドでの並べ替えと検索を提供し、汎用関数LookupDataForGridを呼び出します。



  public static ActionResult BasicLookup(LookupSettings settings, DbContext dbContext, OnAfterQueryPrepared onAfterQueryPrepared) { return LookupMethodCall("LookupSearch", settings, dbContext, onAfterQueryPrepared); } public static ActionResult BasicGrid(LookupSettings settings, DbContext dbContext, OnAfterQueryPrepared onAfterQueryPrepared) { return LookupMethodCall("LookupDataForGrid", settings, dbContext, onAfterQueryPrepared); }
      
      







データベースで操作を実行し、結果のデータセットを形成する機能を実現します。これらは、呼び出しが上記で説明されている2つの汎用関数です。



  private static JsonResult LookupSearch<T>(LookupSettings settings, DbContext dbContext, OnAfterQueryPrepared onAfterQueryPrepared) where T : class { var modelType = typeof(T); var request = dbContext.Set<T>().AsQueryable(); if (onAfterQueryPrepared != null) { var query = onAfterQueryPrepared(request, settings); if (query != null) request = query.Cast<T>(); } request = request.WhereStartsWith(settings.Filter.SearchField, settings.Filter.SearchString); return new JsonResult { Data = request.ToList().Select(t => new { label = modelType.GetProperty(settings.NameField).GetValue(t).ToString(), id = modelType.GetProperty(settings.IdField).GetValue(t).ToString() }).ToList(), ContentType = null, ContentEncoding = null, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; }
      
      





そのため、対応するモデルのdbContextから型指定されたQueryableを取得し、デリゲートが定義されているかどうかを確認し、定義されている場合は呼び出して、クエリによってさらに返されるクエリを使用します。その後、すべてが簡単です。WhereStartsWithを使用してリクエストを作成します。設定のsettings.Filter.SearchField、settings.Filter.SearchStringエンティティの値をそれぞれ使用して、フィルタリングを実行するフィールドと行を決定します。結論として、リフレクションを使用して結果の配列を形成し、モデルmodelTypeのタイプによってインスタンスtのフィールドからデータを取得します。

2つの列のみを返します:label-関連レコードのテキスト表現とid-主キー。

複数の値がある場合、コントロール内のテキストは灰色になります。これは、記録許可が失敗したことを示し、より詳細なビューに切り替える必要があります。



次に、関連データのフィルタリングおよび検索機能を提供するLookupDataForGrid関数の実装に進みます。



  private static JsonResult LookupDataForGrid<T>(LookupSettings settings, DbContext dbContext, OnAfterQueryPrepared onAfterQueryPrepared) where T : class { var modelType = typeof(T); var pageIndex = settings.GridSettings.PageIndex - 1; var pageSize = settings.GridSettings.PageSize; var request = dbContext.Set<T>().AsQueryable(); if (onAfterQueryPrepared != null) { var query = onAfterQueryPrepared(request, settings); if (query != null) request = query.Cast<T>(); } if (settings.GridSettings.IsSearch) { switch (settings.Filter.Operator) { case SearchOperator.Equal: request = request.Equal(settings.Filter.SearchField, settings.Filter.SearchString); break; case SearchOperator.NotEqual: request = request.NotEqual(settings.Filter.SearchField, settings.Filter.SearchString); break; case SearchOperator.Contains: request = request.WhereContains(settings.Filter.SearchField, settings.Filter.SearchString); break; } } var totalRecords = request.Count(); var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var userGroups = request .OrderBy(!settings.GridSettings.Asc, settings.GridSettings.SortColumn) .Skip(pageIndex * pageSize) .Take(pageSize); return new JsonResult { Data = new { total = totalPages, settings.GridSettings.PageIndex, records = totalRecords, rows = ( userGroups.AsEnumerable().Select(t => new { id = modelType.GetProperty(settings.IdField).GetValue(t).ToString(), cell = GetDataFromColumns(modelType, settings, t) }).ToList()) }, ContentType = null, ContentEncoding = null, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; }
      
      





この関数は、LookupSearchと同様に実装されます。ここでは、ページネーション、基本的な並べ替え、検索の処理を追加します。列値のリストは、GetDataFromColumns関数を使用して取得されます。この関数はLookupGridColumnsAttribute属性を使用して、グリッドが期待する列のリストを決定します。そのコードは次のとおりです。



  private static IEnumerable<string> GetDataFromColumns(Type model, LookupSettings settings, object instance) { var dataArray = new List<string> { model.GetProperty(settings.IdField).GetValue(instance).ToString(), model.GetProperty(settings.NameField).GetValue(instance).ToString() }; var gridColumns = model.GetCustomAttributeByType<LookupGridColumnsAttribute>(); if (gridColumns != null) { dataArray.AddRange(from column in gridColumns.LookupColumns select model.GetProperty(column).GetValue(instance) into val where val != null select val.ToString()); } return dataArray; }
      
      





結果の配列には、デフォルトで、主キーと、接続のテキスト記述の値を含むフィールドが含まれます。次に、モデルのタイプからLookupGridColumnsAttribute属性を取得し、インスタンスを使用して、リフレクションを使用して、列の値を引き出します。



ここで、基本コントローラーを実装します。これにより、「デフォルト」モードでフォーム上のすべてのルックアップコントロールが機能するようになります。



  public class LookupBasicController : Controller { protected virtual DbContext GetDbContext { get { throw new NotImplementedException("You have to implement this method to return correct db context"); } } protected virtual IQueryable LookupBaseQuery(IQueryable query, LookupSettings settings) { return null; } public virtual ActionResult LookupData([ModelBinder(typeof(LookupModelBinder))] LookupSettings settings) { return LookupDataResolver.BasicLookup(settings, GetDbContext, LookupBaseQuery); } public virtual ActionResult LookupDataGrid([ModelBinder(typeof(LookupModelBinder))] LookupSettings settings) { return LookupDataResolver.BasicGrid(settings, GetDbContext, LookupBaseQuery); }
      
      





後継クラスで正しく機能するには、データベースコンテキストを再定義する必要があります。デフォルトクエリを拡張する場合は、LookupBaseQuery関数を使用します。この関数は、基本クエリを作成するときにLookupSearchおよびLookupDataForGridから呼び出すために使用されます。また、JSがデータを取得するためにアクセスするコントローラーの関数の名前は、htmlヘルパーの構成中に決定できることに注意してください。ただし、jqGridのデータ取得を実行する関数の名前は、次のパターンに従って形成されます。htmlヘルパー+グリッドの構成中に指定された名前。デフォルトでは、JSはLookupDataおよびLookupDataGrid関数にアクセスします。



これで、コンポーネントの基本要素の開発が完了したと言えます。ソースでは、コンポーネントの作業のクライアント部分を担当するファイルlookup.jsを見つけることができますが、あまり興味がないのでここでは考慮しませんでした。



5.使用例



記事の冒頭で説明したモデルを検討してください。コンポーネントを通信に適用しましょう。



  [Table("UserProfile")] public class UserProfile { [Key] [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)] public int UserId { get; set; } public string UserName { get; set; } [Lookup(Model = typeof(UserGroup), NameField = "GroupName")] public int? UserGroupId { get; set; } public virtual UserGroup UserGroup { get; set; } } [LookupGridColumns(new[] { "Description" })] public class UserGroup { [Key] public int UserGroupId { get; set; } [DisplayName("Group Name")] public string GroupName { get; set; } [DisplayName("Group Description")] public string Description { get; set; } public virtual ICollection<UserProfile> Users { get; set; } }
      
      





したがって、UserGroupにLookupリンクを追加し、このレコードのテキスト表現に使用するフィールドを決定するUserProfileがあります。UserGroudテーブルで、追加を指定するLookupGridColumns属性を追加します。ビューに表示する列。実際、これですべてです。今度はコントローラーに行きます。



  public class UserListController : LookupBasicController { private readonly DataBaseContext _db = new DataBaseContext(); protected override DbContext GetDbContext { get { return _db; } }
      
      





LookupBasicControllerから継承し、GetDbContextを再定義して、LookupBasicControllerにdbコンテキストへのアクセスを許可します。



  public ActionResult Edit(int id = 0) { UserProfile userprofile = _db.UserProfiles.Include(c => c.UserGroup) .SingleOrDefault(x => x.UserId == id); if (userprofile == null) { return HttpNotFound(); } return View(userprofile); }
      
      





UserGroupテーブルから関連データにクエリを追加しました。

これでコントローラーのセットアップが完了し、ビューに進みます。



 @using TestApp.Models @model UserProfile @{ ViewBag.Title = "Edit"; } @Styles.Render("~/Content/JqGrid") <h2>Edit</h2> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>UserProfile</legend> @Html.HiddenFor(model => model.UserId) <div class="editor-label"> @Html.LabelFor(model => model.UserName) </div> <div class="editor-field"> @Html.EditorFor(model => model.UserName) @Html.ValidationMessageFor(model => model.UserName) </div> <div class="editor-label"> @Html.LabelFor(model => model.UserGroupId) </div> <div class="editor-field"> @Html.LookupFor(model => model.UserGroupId) @Html.ValidationMessageFor(model => model.UserGroupId ) </div> <p> <input type="submit" value="Save" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/lookup") @Scripts.Render("~/bundles/jqueryval") @Scripts.Render("~/bundles/jqueryui") @Scripts.Render("~/bundles/jqgrid") }
      
      





ここで、追加することを忘れないでください。jqgrid、ルックアップなどのスクリプト 記事に添付されているソースコードを使用して、プレゼンテーションをより詳細に検討できます。





その結果、関連テーブル内のデータを簡単に検索およびソートできるボタンのあるフィールドが得られます。テーブル内の要素を選択するには、目的の要素をダブルクリックします。ある種の完全な制御要素について話すのは時期尚早であり、実現することはまだ多くあります。ただし、コードにはリファクタリングと最適化が必要ですが、全体として機能し、設計段階で定められた基本的な機能を実装します。



すべて次のようになります。





6.結論



結論として、私たちは私たちのニーズを満たすコンポーネントを探すのにしばらく時間を費やし、その結果、製品ASP.net MVC Awesome 3.5に落ち着きました。MVC Awesome Lookupコンポーネントは非常に柔軟であり、さまざまな種類の設定を実行できますが、すべてをゼロから開発することが決定されたため、仕事では使用しなかったためお勧めできません。使用例とコードは、Awe Lookupをご覧くださいまた、複数選択をサポートしています。



この記事で説明したコンポーネントとテストアプリケーションのソースコードは、TestApp.zipからダウンロードできます



素材があなたにとって興味深いものになったことを願っています!






All Articles