3

家のステータスを更新するためのPOSTリクエストを作成したいとしましょう。理想的には、このデータはある種のサービスレイヤーにある必要があります。通常、これには次のものが含まれます。

  1. ユーザーを検証します-それらはまだアクティブですか、それとも管理者によって追い出されていますか?
  2. houseidを確認してください-houseid/レコードは有効ですか?
  3. ユーザーは家の詳細を見ることができますか?
  4. ステータスを「オープン」または「クローズ」に更新

現実の世界/複雑なドメイン-ほとんどのビューは非常に複雑です。おそらく、その地域の家の数、家に関するコメントの数、家の詳細など、家の未解決のタスクの数を破棄する必要があります。 ..。。

つまり、上記のコードはすべてサービスレイヤー内にある可能性がありますが、例外がスローされた場合、ユーザーは家のステータスを更新できません。ビューにデータを入力するには、最初に家の詳細を取得する必要があります。サービスレイヤー内にロードした他のすべてのものをコントローラー内にロードするか、このデータをロードするサービスレイヤーへの別のインコベーション...

同じコードを何度も書き直すことなく、検証とすべての種類を実行して、ドメインモデルが保護されていることを確認するにはどうすればよいですか...

このコードはactionメソッド内にあり、サービスレイヤー内に簡単に含めることができます...

//注:_repoは、linqからsqlへの単純な抽象化です...

    [HttpGet]
    public ActionResult TaskDetail(int houseid, int taskid)
    {
        var loggedonuser = _repo.GetCurrentUser();

        var _house = _repo.Single<House>(x => x.HouseID == houseid && x.Handler == loggedonuser.CompanyID);

        if (_house == null)
            throw new NoAccessException();

        var summary = _house.ToSummaryDTO();

        var companies = _repo.All<Company>();
        var users = _repo.All<User>();

        var task = _repo.Single<HouseTask>
            (x => x.HouseID == _house.HouseID && x.TaskID == taskid && (x.CompanyID == loggedonuser.CompanyID));

        var dto = new TaskDTO
        {
            TaskID = task.TaskID,
            Title = task.Title,
            Description = task.Description,
            DateCreated = task.DateCreated,
            IsClosed = task.IsClosed,
            CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
        };

        if (task.DueDate.HasValue)
            dto.DueDate = task.DueDate.Value;

        var comments = _repo.All<HouseTaskComment>()
            .Where(x => x.TaskID == task.TaskID)
            .OrderByDescending(x => x.Timestamp)
            .Select(x => new TaskCommentDTO
            {
                Comment = x.Comment,
                Timestamp = x.Timestamp,
                CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
                UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login,
                Type = EnumHelper.Convert<TaskCommentType>(x.Type)
            });

        dto.AllComments = comments;

        return View(new TaskViewModel
        {
            Summary = summary,
            TaskDetail = dto,
            NewComment = new TaskCommentDTO()
        });
    }

要するに-要約の家の詳細を取得し、(複数の利用可能なタスクから)タスクの詳細を取得し、タスクのコメントも取得します。それは私見の単純な見方であり、それほど複雑なものではありません。

この時点で、ユーザーは次のことができます。コメントの追加、タスクの閉じる/開く-権限がある場合(簡単にするためにコードは省略)、タスクの期日を設定するか、タスクの期日をクリアすることもできます。

UpdateTaskStatus-ステータスを更新できない場合は、コメントと同様に上記のビューを返す必要があります。コメントできない場合は、詳細ビューを返す必要があります-コメントを閉じることができます。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult TaskDueDate(int houseid, int taskid)
{

    var duedate = Request.Form["duedate"];
    var duetime = Request.Form["duetime"];
    try
    {
        if (ModelState.IsValid)
        {

            var newduedate = DateHelper.GoodDate(duedate, duetime);
            _service.SetTaskDueDate(houseid, taskid, newduedate);

            return RedirectToAction("TaskDetail");
        }
    }
    catch (RulesException ex)
    {
        ex.CopyTo(ModelState);
    }

    var loggedonuser = _repo.GetCurrentUser();

    var _house = _repo.Single<House>(x => x.InstructionID == houseid && x.HandlerID == loggedonuser.CompanyID);

    if (_house == null)
        throw new NoAccessException();

    var summary = _house.ToSummaryDTO();

    var companies = _repo.All<Company>();
    var users = _repo.All<User>();

    var task = _repo.Single<HouseTask>
        (x => x.InstructionID == _house.HouseID && x.CompanyID == loggedonuser.CompanyID && x.TaskID == taskid);

    var dto = new TaskDTO
    {
        TaskID = task.TaskID,
        Title = task.Title,
        Description = task.Description,
        DateCreated = task.DateCreated,
        IsClosed = task.IsClosed,
        CompanyID = companies.Where(y => task.CompanyID == y.CompanyID).SingleOrDefault().Identifier
    };

    if (task.DueDate.HasValue)
        dto.DueDate = task.DueDate.Value;

    var comments = _repo.All<HouseTaskComment>()
        .Where(x => x.TaskID == task.TaskID)
        .OrderByDescending(x => x.Timestamp)
        .Select(x => new TaskCommentDTO
        {
            Comment = x.Comment,
            Timestamp = x.Timestamp,
            CompanyID = companies.Where(y => x.CompanyID == y.CompanyID).SingleOrDefault().Identifier,
            UserID = users.Where(y => x.UserID == y.UserID).SingleOrDefault().Login
        });

    dto.AllComments = comments;

    return View("TaskDetail", new TaskViewModel
    {
        Summary = summary,
        TaskDetail = dto,
        NewComment = new TaskCommentDTO()
    });
}

私は上記のコードがひどく構造化されていることを知っていますが、それを修正する方法についてのアドバイスをいただければ幸いです。

  1. アクション内にすべての読み取り専用コードを残します。各ビューが異なる可能性があるため、ここでサービスレイヤーが干渉することは望ましくありません。
  2. 更新/編集を「保護」し、これをサービスレイヤーまたはコアプロジェクト(個別のc#クラスライブラリ)またはドメインレイヤー内に保持したいのですが、そのコードハンドル検証をどのように記述しますか(これはサービス内で行います)呼び出し)、実際の保存を実行しますか?

CommandHandlerアプローチについて聞いたことがありますが、これは良いアプローチですか?理想的には、コントローラーアクションではなく、ドメイン内の単純なアプローチを使用して、検証と永続性を保持したいと思います。

4

2 に答える 2

8

多分あなたはこれを少し考えすぎています

私があなたのプロセスを理解しているので、これはそれが行われるべき方法です

  1. ユーザーの検証 (まだ有効なユーザーであるかどうか) はユーザーのログイン時に行われ、未登録 (または追い出されたユーザー) はアプリにアクセスできないため、後で処理されません。ユーザーが作業中に追い出される可能性がある場合は、アクション フィルターを作成して次のいずれかに配置することでこれを解決できます。

    • すべてのコントローラー クラス (アクションではない)

    • ベースコントローラークラスを持ち、それにフィルターを置きます

  2. ステップ2、3、および4は実際に関連しています。

    • まず、家のデータを取得する必要があります(許可を含む)

    • ハウスIDが無効な場合、DBから何も返されません

    • データを取り戻した場合は、権限を確認してください

    • 許可されている場合は、それに応じてステータスを設定します

追記

複雑なドメイン モデルでは、通常、複雑なビューを持つ必要はありません。おそらく、それらの数が増え、使いやすさを向上させるために、より複雑なナビゲーションが必要になるでしょう。ユーザーは、複雑なビューよりも単純なビューで作業することにも満足します。

モデルの検証

Asp.net MVC が実装するので、POCO オブジェクト レイヤーでデータ注釈を使用して、静的検証を維持します。これは組み込みであり、コードを削減し (行数が少ない = バグ サーフェスが小さくなる)、より堅牢になるためです。いくつかのものを誤って検証するのを忘れることはありません。

ただし、動的検証(エンティティ インスタンスに対する特定のユーザー/会社のアクセス許可に関連するもの) は、サービス レイヤー内に保持するのが最適です。違反が発生した場合は、カスタム例外をスローし、それらをモデル状態に伝播します (コードの私の理解に基づいて、RulesExceptionクラスで行っていることと同様です)。

try/catch コード ブロックの面倒な (および反復的なコード) を避けるために、値をモデル状態に伝達し、モデル状態エラーを表示するために必要なビューを返すカスタム例外フィルターを作成するだけです。このような例外フィルターの実装は完全にあなた次第です。次に、親コントローラーに配置して(グローバルフィルターを持つMVC 3を使用していないため)、忘れてください。これにより、繰り返しコードも削減されます。

動的モデル検証をサービス層に配置する場合、ユーザーが必要とする詳細に依存しているため、ユーザーが必要とする詳細をそれに渡す必要があります。

ユーザー取得

毎回データ ストアからユーザー データを読み取っていることがわかりますが、これは望ましくありません。ほとんどの場合、必要なのはユーザー ID (そして明らかに会社 ID) だけです。この小さなデータは、別の方法で (通常は安全な Cookie に保存されますが、独自の戦略で決定することもできます) 保持できるため、時間を節約できます。ユーザーはおそらく自分のデータしか変更できないため、その時点で無効にして再読み取りできるため、このキャッシュされたデータを無効にすることはかなり簡単です。頻繁に必要な追加データ (ユーザーの表示名やログイン名など) と一緒にバンドルできます。しかし、それ。ほとんどの場合、コードはユーザー ID を使用します。

モデルの主張/許可の検証

モデルは、モデル エンティティ データが正しいかどうかを検証するだけでなく、特定のユーザーが (会社によっては) エンティティの変更権限を持っているかどうかという要求 (または権限) を検証する必要があります。あなたはそれをフィルターレベルに置くことはできないと言ったが、あなたの問題を見ることができる限り。(前述のように) ユーザー データを永続化すると、必要なすべての情報が得られます。ユーザー データとモデルがあり、アクションに基づいて、そのアクションに関連する静的クレームもわかります。

[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(...) { ... }

アクション フィルターを簡単に実行できない場合は、特定のインターフェイスを実装するカスタム バリデーター クラス (フィルターが任意の呼び出しを実行できるようにするため) と、検証する必要があるパラメーター名を指定することもできます。これにより、ユーザー/会社のデータに対してカスタム クラスを検証できるようになります。また、異なるバリデーターを使用して、複数の権限を検証したり、同じ/異なる権限を検証したりすることもできます。

[RequirePermission(Permission.UpdateTaskDueDate, typeof(TaskValidator), "paramName")]
public ActionResult TaskDueDate(...) { ... }

このバリデーターのValidateメソッドは、パーミッションの列挙値を取得し、何が必要かを検証します。

別の可能性もあります。カスタムバリデータを定義するエンティティクラスにカスタム属性(またはそれ以上)を含めることができるため、フィルターは検証する必要があるアクセス許可タイプのみを取得します。これにより、1 つのアクションで複数のオブジェクトを簡単に検証することもできます。アクセス許可を提供するだけで、フィルターはアクション パラメーター、その型、および宣言されたカスタム バリデーターをチェックします。

フィルターがほとんど呼び出さないカスタム バリデーターを使用すると、プレゼンテーションではなくサービス レイヤーでの検証も保持されます。そのため、基になるコードから UI を独立させるように検証する方法は、ドメイン ロジック次第です。何かが変更される場合は、入力と出力の型のみが一致する必要がありますが、すべての UI レイヤー コードは実質的にそのままにしておく必要があります。

リクエストデータの読み取り

を使用してデータを読み取る代わりに、Request.Form["duedate"]それらをアクションパラメーターとして配置する必要があります。それらは MVC によって設定されます。あなたのコードでは:

try
{
    if (ModelState.IsValid)
    {

        var newduedate = DateHelper.GoodDate(duedate, duetime);
        _service.SetTaskDueDate(houseid, taskid, newduedate);

        return RedirectToAction("TaskDetail");
    }
}
catch (RulesException ex)
{
    ex.CopyTo(ModelState);
}

ifステートメントは完全に冗長です。整数パラメーターは、アクション本体内で常に有効になります (null 許容として設定されません)。他のカップルを静的にチェックするかどうかにかかわらずDateHelper.GoodDate、それらはカスタムクラスに含めることができ(TaskId同様に)、それにデータ注釈を付けることができます。そして、モデルの検証は実際には無効になる可能性があります(したがって、ifステートメントは理にかなっています)。

ただし、アクションで同じコードを繰り返す代わりに、次のTaskDueDateようにすることもできます。

[HttpPost]
[ValidateAntiForgeryToken]
[RequirePermission(Permission.UpdateTaskDueDate)]
public ActionResult TaskDueDate(int houseid, TaskDue taskDueData)
{
    if (!this.ModelState.IsValid)
    {
        // don't repeat code and just call another action within this controller
        return this.TaskDetail(houseid, taskDueData.TaskId);
    }
    _service.SetTaskDueDate(houseid, taskid, newduedate);
    return RedirectToAction("TaskDetail");
}

両方のアクションで同じことをしているためです。アクション内で他のアクションを呼び出せないとは誰も言っていません。彼らはActionResultとにかく戻ってきます。

このアクションがはるかに単純化されていることについては、両者とも同意していると思います。

アクション メソッド コード

_repoサービス ドメイン コードであるため、これらすべての呼び出しをコントローラー アクション内に配置することはしません。ビューが消費するオブジェクトを提供するために、パラメータを提供し、サービス層を呼び出すだけです。

エピローグ

実際の問題が何であるかを解読するのは非常に難しいため、少なくともいくつかの質問/問題/問題に答えていただければ幸いです (または、正確に何を求めているかをよりよく言います)。かなり紛らわしい方法で質問を書いたため、質問に対する回答が得られません。人々はあなたの問題が何であるかを理解していません。

ともかく。コードをリファクタリングして、動的検証を実装する必要がある場合の方法を説明しようとしました。

于 2011-08-04T14:09:37.007 に答える
0

標準の FaultException から継承して、HouseDTO タイプの場合に HouseDetails プロパティを追加できますか?

于 2011-08-04T13:55:34.660 に答える