1

私は次の作業システムを持っており、それを乾かす方法を探しています:

public class EMailMetaData
{
  [Display(Prompt="myemail@mydomain.com"])
  public string Data;
}
public class PhoneMetaData
{
  [Display(Prompt="+1 (123) 456-7890"])
  public string Data;
}
public class AddressMetaData
{
  [Display(Prompt="Central st. W., St Francisco, USA"])
  public string Data;
}
// 7 more metadata templates

public class ContactVM
{
  [Required]
  public string DataLabel { get; set; }

  [Required(ErrorMessage="Please fill in the data field")]
  public string Data { get; set; }
}

[MetadataType(typeof(EmailMetaData))]
EmailVM : ContactVM
{
}
[MetadataType(typeof(PhoneMetaData))]
PhoneVM : ContactVM
{
}
[MetadataType(typeof(AddressMetaData))]
AddressVM : ContactVM
{
}
// 7 more contact view models

Controllerは明らかに適切なコンテンツでそれらを初期化し、ビューでは、EmailVM.cshtml、PhoneVM.cshtml、AddressVM.cshtmlUrlVM.cshtmlなどのすべての連絡先に対してTemplateEditorを持つContactVMのforeachループを実行します。

メインビューは次のようになります(次のようなすべての設定と詳細は省略されています:

@model ContactsVM
foreach (var contact in Model.Contacts)
{
  @Html.EditorFor(m => contact)
}

およびEditorTemplatesの下

@model EmailVM
@Html.EditorFor(model => model.DataLabel)
@Html.EditorFor(model => model.Data)
<br />
@Html.ValidationMessageFor(model => model.DataLabel)
@Html.ValidationMessageFor(model => model.Data)

...そして明らかに私が定義したすべてのビューモデル用のエディタテンプレートはもう少しあります。

つまり、簡単に言えば、透かし、命名、検証にわずかな違いがある非常に類似した連絡先タイプですが、基本的にすべての文字列とすべてが同じフィールドを持っています(アドレスは構造体ではなく1つの長い文字列であり、すべて同じです)。

私の質問は透かしに固有のものではなく、名前、説明、プロンプトなどの任意のプロパティにすることができます。

[Display(Name="name", Description="description", Prompt="prompt")]

すべてがほぼ機能し、それぞれに適切なラベルと透かしが表示されますが、モデルタイプを除いてすべてのテンプレートエディタがまったく同じであるため、DRY違反が非常に大きいようです。ここで示しているのは、目前の問題に集中するための簡略化です。メインビューとエディターテンプレートは、ここに表示されているものよりもはるかに複雑であるため、重複は非常に大きくなります。

それほど多くのコードを複製しないようにするためのより良い方法を提案できる人はいますか?

ありがとう!

4

4 に答える 4

2

(ここでの問題に対するアプローチはまったく異なるため、別の回答を追加します)

ビューモデルとビューの両方の構造は、私が嫌いになっているパターンであるメタメタメタデータを見ていることを示唆しています。申し訳ありません。

最初に簡単なアプローチを取り、私たちのモデルが何であるかを調べましょう。私はそれがこのようなものだと思います:

public class ContactsVM
{
    [Required]
    public string EmailLabel {get;set;}

    [Display(Prompt="myemail@mydomain.com"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Email {get;set;}

    [Required]
    public string PhoneLabel {get;set;}

    [Display(Prompt="+1 (123) 456-7890"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Phone {get;set;}

    [Required]
    public string AddressLabel {get;set;}

    [Display(Prompt="Central st. W., St Francisco, USA"])
    [Required(ErrorMessage="Please fill in the data field")]
    public string Address {get;set;}

    // 7 more property pairs
}

わかりやすく、シンプルで、わかりやすく、わかりやすい。はい、ビューでコードの重複が必要になります(つまり、2つEditorForの後にValidationMessage)が続きますが、私の経験では、ほとんどの場合、1つの(そして1つだけの)プロパティに対してこのコードを何らかの方法で微調整する必要があるため、問題ではありません。それが気に入らない場合は、もう1つの解決策があります。

@* let's assume that props is a string[] holding meaningful property names, e.g "Email", "Phone", "Address". You can even get it dynamically from reflection
@foreach (var property in props)
{
    @Html.Editor(property + "Label") @Html.Editor(property)
    <br />
    @Html.Validation(property + "Label") @Html.Validation(property)
}

動的モデルの更新

さて、モデルに可変数のデータ項目がある場合(これを日常的に使用するとさらに嫌いになります)、上記のすべてが機能しないため、これにも対処する必要があります。実際に達成しようとしているのは、上記と同じビューコードですが、モデルにはこれらのプロパティが含まれていません。すべての魔法は2つのことにあります。

  1. モデルをDictionary、文字列データを指す文字列キーを含むようなものに変換します。どのようにデータを入力するかは重要ではありません。
  2. ModelMetadataProviderモデルクラスのデフォルトの動作をオーバーライド する独自の動作を次のように提供します。
    1. 与えられたモデルインスタンスは、GetProperties前述の辞書を列挙しModelMetadata、それらの文字列から作成することで応答します
    2. 作成されたそれぞれModelMetadataには、辞書の値に対応するModelValueが含まれている必要があります
    3. の他のすべてのプロパティは、ModelMetadata必要に応じて入力されます(DataAnnotationsは不要になり、プロバイダーにすべての値を挿入します)

それには多くの作業と少しの試行が必要ですが、最終的にはうまくいきます。私の経験から言えば。しかし、必要なため、この全体的な設計アプローチはお勧めしません...すでに多くの作業を確認しており、これはほんの始まりにすぎません。

于 2012-08-03T07:01:25.420 に答える
1

(あなたの問題は、ビューモデルが特定されていないという事実から生じていますが、一種の「そこにあるすべての問題に対する1つの一般的な解決策」です。)

しかし、それでも、非常に簡単な解決策があります。モデルタイプとして、すべてのタイプに対して1つの名前付きエディターテンプレートを作成し、を使用します。ContactVM@Html.EditorFor(m => contact, "YourTemplateName")

于 2012-08-02T13:34:14.657 に答える
1

私自身の質問に答えるには:

これは鈍い解決策ですが、提案されたものよりもはるかに単純であり、最も重要なのは、単純な解決策と比較して重複が最小限で済みます(ただし、オブジェクト指向の世界にいることを考えると、あまりエレガントではありません)。

  1. モデルとして基本クラス(またはインターフェイス、ただし定義する)を使用して1つのビューを定義します。
  2. ビューで、モデル変数を適切なクラスにキャストします
  3. ビューでは、モデルではなくキャスト変数を使用します

したがって、Contact.cshtmlは次のようになります。

@model ContactVM
@* Do tons of stuff that is the same between views (not depending on data annotation) *@
@Html.Partial("_ContactDataAnnotation", Model)
@* Continue doing lots of stuff that is the same between all those classes

Contact.cshtmlエディターテンプレートを呼び出すと、次のようになります(Sergに感謝)。

@foreach (var c in Model.Contacts)
{
  @Html.EditorFor(m => c, "Contact")
}

適切なデータ注釈のみを表示するための部分ビュー_ContactDataAnnotation.cshtmlは、次のようになります。

@using <My Model Namespace>
@model ContactVM
@switch (Model.GetType().Name)
{
  case "EmailVM":
    EmailVM e = Model as EmailVM;
    @Html.EditorFor(model => e.DataLabel)
    @Html.EditorFor(model => e.Data)
    <br />
    @Html.ValidationMessageFor(model => e.DataLabel)
    @Html.ValidationMessageFor(model => e.Data)
    break;
  case "PhoneVM":
    PhoneVM p = Model as PhoneVM;
    @Html.EditorFor(model => p.DataLabel)
    @Html.EditorFor(model => p.Data)
    <br />
    @Html.ValidationMessageFor(model => p.DataLabel)
    @Html.ValidationMessageFor(model => p.Data)
    break;
  // same thing 7 more times for all children of ContactVM, since MVC does not applying polymorphism to data annotations UNFORTUNATELY
  default:
    @Html.EditorFor(model => model.DataLabel)
    @Html.EditorFor(model => model.Data)
    <br />
    @Html.ValidationMessageFor(model => model.DataLabel)
    @Html.ValidationMessageFor(model => model.Data)
  }

これにより、同じエディターテンプレートを異なる名前とモデルクラスで10回複製するのではなく、必要な場所にのみ複製が存在します。

「ベストプラクティス」という質問のタイトルと矛盾することは知っていますが、残念ながら、これが最も簡単で最小限の方法です。Sergが指摘したように、他のソリューションは非常に複雑で、MVCインフラストラクチャの深い介入が必要です。これには時間を費やすのは好きではありません。また、標準のように見えるデータアノテーションで定義するのではなく、プロンプトとツールチップを挿入する必要があります。モデルを装飾および検証する方法。

私の解決策は、データ注釈のポリモーフィズムがないというMVCの回避策であると考えています。

于 2012-08-03T14:10:48.397 に答える
0

次のようなインターフェースを作成できるはずです。

public interface IContactData
{
string Data{get; set;};
.
.
.
}

そのインターフェースをクラスに実装する

public class EMailMetaData : IContactData
{
  [Display(Prompt="myemail@mydomain.com"])
  public string Data{get; set;};
...
}
public class PhoneMetaData  : IContactData
{
  [Display(Prompt="+1 (123) 456-7890"])
  public string Data{get; set;};
....
}

そして、エディタテンプレート(すべてに1つ)で使用します

@model IContactData
于 2012-08-02T15:50:38.200 に答える