6

MVVM パターンを使用して C# WPF アプリケーションを構築しています。NHibernate を使用してドメイン モデルを永続化するリポジトリ クラスがあります。

私のモデルは、より大きなツリー構造 (Recipeを含む をOperation含む を含むPhase) で構成されています。操作とフェーズの両方に、キーと値のマッピングの動的リストが として含まれていIDictionary<string, string>ます。対応する NHibernate マッピングOperation

<class name="Operation" table="operations">
  <id column="id" type="int" generator="native" />
  <property name="Name" column="name" />

  <map name="Parameters" table="operation_params">
    <key column="operation" />
    <index column="param" type="string" />
    <element column="value" type="string" />
  </map>

  <list name="Phases" cascade="all-delete-orphan">
    <key column="operation" />
    <index column="`index`" />
    <one-to-many class="Phase" />
  </list>
</class>

さて、その部分は簡単で、非常にうまく機能します。クラスは現在、Operation内部にロジックがほとんどない単純なデータ コンテナーである POCO です。


私の問題は、アプリケーションが .xml ファイルから読み取る外部スキーマに対してパラメーターを検証する必要があることです。スキーマには、単一のパラメーター (範囲、有効な値など) の制限と、複数のパラメーター間の依存関係 (つまり、有効な値は別のパラメーターの値によって異なります) が含まれています。

検証ロジックを統合する最良の方法は何ですか? 私はここ数日たくさん読んで、今まで、次の代替案に出くわしました。

  1. 検証ロジックをモデル クラス自体に追加します。

    このため、NHibernate によって作成されたオブジェクトに検証スキーマを適切に挿入する方法がわかりません。ユーザーがパラメーターを編集している場合、または操作をインポートしている場合 (バックアップなどから) のみ、常に検証機能が必要なわけではありません。では、モデル クラスに実際の検証ロジックを実装し、検証が本当に必要なときはいつでも、プロパティを使用して検証ルールを挿入することができるでしょうか? NHibernate を使用して格納するモデル クラスにその機能を追加することは良い習慣と考えられますか、それともモデル クラスを「ダム」のままにする必要がありますか?

  2. オブジェクトをラップする検証ロジックのデコレータ クラスを作成しOperationます。

    このようにして、検証が必要なときはいつでもラッパーを使用し、表示のみが必要な場合は裸のモデル クラスを使用します。ここでの問題は、ViewModel クラスが既にラッパーであるため、ここでラッピングの別のレイヤーを取得することです。また、操作クラスはより大きなツリー構造 (レシピ/操作/フェーズ) の一部であるため、コレクションのラッパーを作成し、コレクションの変更を基になるコレクションに戻す必要がありますが、これは複雑な作業になる可能性があります。

  3. 操作を検証したいときに渡すサービス クラスを作成します。

    ここで見られる問題は、サービスがステートレスであるため、ユーザーが 1 つのパラメーターを変更するたびにパラメーター リスト全体を再検証する必要があることです。これは、特にパラメーターの検証ステータスが変更されたときに UI の何らかの変更イベントを発生させたい場合に、最善のアプローチではないようです。

私の問題に対する一般的なアプローチは何ですか? まだ見つかっていない、完全に必要なパターンはありますか? つまり、検証のために外部スキーマ定義に依存する実装がたくさんあります (XML/XSD および同様のドキュメント構造を参照してください)。私の問題に対する完全な解決策を既に見つけた天才がいるに違いありません ;-)助けて、そう!

4

1 に答える 1

6
  1. 検証ロジックをモデル クラス自体に追加します。
  2. - POCO をロジックで爆破し、ActiveRecord パターンを使用することになるため、最適なアプローチではありません。
  3. Operation オブジェクトをラップする検証ロジックのデコレータ クラスを作成します。
  4. -既存のラッパーをラップする必要があるという違いがあり、抽象化のレベルを吹き飛ばすことになるという違いがあるため、これも推奨されないより良いアプローチだと思います。
  5. 操作を検証したいときに渡すサービス クラスを作成します。
  6. -おそらく、そのようなことを達成するのはあなたの場合ではありません(Webサービスまたは別のタイプのリモートサービスについて言っていることを正しく理解している場合)。たとえば、これらの検証ルールに制限がある場合、このソリューションはより適しています複数のクライアント向けに集中化されており、具体的なアプリケーションに厳密ではありません。

私は次の解決策を支持します

以下を含むバリデータ プロジェクトをソリューションに追加します。

  • アプリケーションが .xml ファイルから読み取る外部スキーマに対してパラメーターを検証するロジック。

  • プロジェクトで使用する各 POCO オブジェクトの検証ルールであり、検証が必要です (または、これらのルールをより高いレベルで適用することもできます。つまり、POCO ではなく、POCO のいくつかのラッパーを既に実装している場合、ただし、ベスト プラクティスは、規則を POCO に直接適用することです – よりクリーンで正しいアプローチ)

だから

1- POCO には、プロパティと単純な検証 SelfValidate() が含まれます。


namespace Core.Domain {
    public class Operation : ValidatableDomainObject {
        #region Properties
        public virtual String Name { get; set; }
        public virtual ISet Phases { get; set; }     
        #endregion Properties

        #region Validation
        public override ValidationResult SelfValidate() {
            return ValidationHelper.Validate(this);
        }
        #endregion Validation       
    }
}

2- POCO Validator には、XML ファイルに基づいて POCO の検証に適用する必要があるルールが含まれます。


#region Usings

using System.Linq;
using FluentValidation;
using FluentValidation.Results;

#endregion Usings

namespace Core.Validation {

    public class OperationValidator : AbstractValidator {
        #region .Ctors
        /// 
        /// .Ctor used for operation purpose
        /// 
        public OperationValidator() {
            Validate();
        }
        #endregion .Ctors

        /// 
        /// Validation rules for Operation
        /// 
        private void Validate() {
            //here you may get validations rules from you xml file and structure the following code after your requirements
            //Name
            RuleFor(x => x.Name).Length(2, 20).WithMessage("Operation name should have length between 2 and 20 symbols");
            //ApplicationFormsWrapper
            Custom(entity => {
                foreach (var item in entity.Phases)
                    if (item.PhaseState == null)
                        return new ValidationFailure("Phases", "First Phase is missing");
                return null;
            });
        }
    }
}

3- ValidatableDomainObject クラスを追加し、System.ComponentModel.IDataErrorInfo を実装します (ユーザー インターフェイスがバインドできるカスタム エラー情報を提供する機能を提供します)。


#region Usings

using System.ComponentModel;
using System.Linq;
using FluentValidation.Results;
using Core.Validation.Helpers;

#endregion Usings

namespace Core.Domain.Base {
    public abstract class ValidatableDomainObject : DomainObject, IDataErrorInfo {

        public abstract ValidationResult SelfValidate();

        public bool IsValid {
            get { return SelfValidate().IsValid; }
        }

        public string Error {
            get { return ValidationHelper.GetError(SelfValidate()); }
        }

        public string this[string columnName] {
            get {
                var validationResults = SelfValidate();
                if (validationResults == null) return string.Empty;
                var columnResults = validationResults.Errors.FirstOrDefault(x => string.Compare(x.PropertyName, columnName, true) == 0);
                return columnResults != null ? columnResults.ErrorMessage : string.Empty;
            }
        }
    }
}

4-次の ValidationHelper を追加します。


#region Usings

using System;
using System.Text;
using FluentValidation;
using FluentValidation.Results;

#endregion Usings

namespace Core.Validation.Helpers {
    public class ValidationHelper {
        public static ValidationResult Validate(TK entity)
            where T : IValidator, new()
            where TK : class {
            IValidator validator = new T();
            return validator.Validate(entity);
        }

        public static string GetError(ValidationResult result) {
            var validationErrors = new StringBuilder();
            foreach (var validationFailure in result.Errors) {
                validationErrors.Append(validationFailure.ErrorMessage);
                validationErrors.Append(Environment.NewLine);
            }
            return validationErrors.ToString();
        }
    }
}

アプリケーションコードで次のことを実行できます

  1. サービスまたはビューモデル レベルで、検証エラーを取得するためにこれを行うことができます。

var operation = new Operation(){Name="A"};
var validationResults = operation.SelfValidate();

  1. ビュー レベルでは、次のように記述できます (この場合、検証エラーが表示された場合、それらは OperationValidator クラスから直接取得されます)。
<TextBox Text="{Binding CurrentOperation.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

: 実装は FluentValidation (流暢なインターフェイスとラムダ式を使用する .NET 用の小さな検証ライブラリ) に基づいています。http://fluentvalidation.codeplex.com/を参照してください。ドメイン オブジェクトから検証を分離するメカニズムを説明します。

于 2012-10-02T08:08:57.697 に答える