7

ASP.NET MVC4 - Basically I used to have all my business logic in my controllers (which I'm trying to put into the domain models instead). However I don't quite know if ALL my business logic should be put into the domain models or if some should remain in the controllers?

For instance I got a controller action as shown below:

[HttpPost]
    public ActionResult Payout(PayoutViewModel model)
    {
        if (ModelState.IsValid)
        {
            UserProfile user = PublicUtility.GetAccount(User.Identity.Name);
            if (model.WithdrawAmount <= user.Balance)
            {
                user.Balance -= model.WithdrawAmount;
                db.Entry(user).State = EntityState.Modified;
                db.SaveChanges();

                ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount;
                model.Balance = user.Balance;
                model.WithdrawAmount = 0;
                return View(model);
            }
            else
            {
                ViewBag.Message = "Not enough funds on your account";
                return View(model);
            }
        }
        else
        {
            return View(model);
        }
    }

Now should all the logic be put into a method in a domain model so the action method looks like this?

[HttpPost]
    public ActionResult Payout(PayoutViewModel model)
    {
        var model = GetModel(model);
        return View(model);
    }

Or how would you go around doing it?

4

5 に答える 5

12

アプリケーションとビジネス ロジックを別のレイヤー (csproj ファイル)、ビジネス ロジック用のドメイン レイヤー、およびアプリケーション ロジック用のサービス レイヤーに配置します。これにより、MVC プロジェクトから完全に抽象化されます。これには 2 つの大きなメリットがあります。1 つ目は、ビジネス ロジックが変化する可能性のあるパターンに関連付けられていないことです。数年前には、今日の MVC の人気を想像する人は誰もいませんでした。数年後には、MVC に取って代わる新しいものが登場するかどうかもわかりません。 MVC に「結び付けられていない」ことは、MVC を他の何かのために放棄したい場合に役立ちます。

2 つ目の利点は、さまざまなプレゼンテーション レイヤーの実装が非常に簡単になることです。したがって、ビジネス ロジックを WCF サービスとして提示したい場合は、新しい WCF プロジェクトを作成し、それをサービスおよびドメイン レイヤーのファサードにすることで、非常に簡単に実現できます。MVC プロジェクトと WCF サービスの両方が同じビジネス ロジック クラスを使用するため、メンテナンスが非常に簡単になります。

以下は、私が行うコードの例です。これは迅速かつ汚いものであり、ユーザーがデータベースに保存しない場合などにログを追加するなど、さらに多くのことが必要です...

public enum PayoutResult
{
    UserNotFound,
    Success,
    FundsUnavailable,
    DBError
}

public class UserProfile
{
    public float Balance { get; set; }

    public string Username { get; set; }

    // other properties and domain logic you may have

    public bool Withdraw(PayoutModel model)
    {
        if (this.Balance >= model.Amount)
        {
            this.Balance -= model.Amount;
            return true;
        }

        return false;
    }
}


public class PayoutService
{
    IUserRepository userRepository;

    public PayoutService()
    {
        this.userRepository = new UserRepository();
    }

    public PayoutResult Payout(string userName, PayoutModel model)
    {
        var user = this.userRepository.GetAll().SingleOrDefault(u => u.Username == userName);
        if (user == null)
        {
            return PayoutResult.UserNotFound;
        }

        // don't set the model properties until we're ok on the db
        bool hasWithdrawn = user.Withdraw(model);
        if (hasWithdrawn && this.userRepository.SaveUser(user))
        {
            model.Balance = user.Balance;
            model.Amount = 0;

            return PayoutResult.Success;
        }
        else if (hasWithdrawn)
        {
            return PayoutResult.DBError;
        }

        return PayoutResult.FundsUnavailable;
    }
}

コントローラーは次のようになります

[HttpPost]
public ActionResult Payout(PayoutModel model)
{
    if (ModelState.IsValid)
    {
        var result = service.Payout(User.Identity.Name, model);
        // This part should only be in the MVC project since it deals with 
        // how things should be presented to the user
        switch (result)
        {
            case PayoutResult.UserNotFound:
                ViewBag.Message = "User not found";
                break;
            case PayoutResult.Success:
                ViewBag.Message = string.Format("Successfully withdraw {0:c}", model.Balance);
                break;
            case PayoutResult.FundsUnavailable:
                ViewBag.Message = "Insufficient funds";
                break;
            default:
                break;
        }               
    }

    return View(model);
}

また、支払いを Web サービスで公開する必要がある場合 (私はエンタープライズ環境で働いているため、これは頻繁に発生します)、次の手順を実行します...

public class MyWCFService : IMyWCFService
{
    private PayoutService service = new PayoutService();

    public PayoutResult Payout(string username, PayoutModel model)
    {
        return this.service.Payout(username, model);
    }
}
于 2013-06-17T08:44:21.573 に答える
6

私にとって、関心の分離は、これらの決定の最も重要な指針となる原則です。そのため、ドメインの複雑さと、コードを複雑にすることで得られるメリットによって異なります。

とにかく、原則として、私はコントローラーに次の懸念を与える傾向があります。

  1. ビューモデルのインスタンス化とマッピング (かなりのマッピングがある場合を除く)
  2. モデルの検証を表示

また、アプリケーション固有ではないドメイン知識のモデル (またはサービス) を参照する傾向があります。

  1. 出金できる
  2. 出金する

だから、これは私がコードを分割する方法です:

    [HttpPost]
    public ActionResult Payout(PayoutViewModel model)
    {
        if (ModelState.IsValid)
        {
            var account = accountRepository.FindAccountFor(User.Identity.Name);

            if (account.CanWithdrawMoney(model.WithdrawAmount))
            {
                account.MakeWithdrawal(model.WithdrawAmount);

                ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount;
                model.Balance = account.Balance;
                model.WithdrawAmount = 0;
                return View(model);
            }

            ViewBag.Message = "Not enough funds on your account";
            return View(model); 
        }
        else
        {
            return View(model);
        }
    }

アプリケーション状態の保存は、通常、インターセプターにまとめます。そうすれば、リクエスト全体を作業単位のトランザクションでラップできます。

于 2013-06-17T08:52:39.600 に答える
2

すべてのロジックをドメイン モデルに配置し、ドメインに対して 2 つの呼び出しを行います。1 つは検証用、もう 1 つはユース ケースの実行用です。

したがって、エンティティは次のようになります。

public class User 
{
    public double Balance { get;set; }

    public ValidationMessageCollection ValidatePayout(double withdrawAmount)
    {
        var messages = new ValidationMessageCollection();

        if (withdrawAmount > Balance)
        {
            messages.AddError("Not enough funds on your account");
        }

        return messages;
     }

     public void Payout(double withdrawAmount)
     {
         balance -= withdrawAmount;
     }
 }

コントローラーは次のようになります。

[HttpPost]
public ActionResult Payout(PayoutViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    var user = PublicUtility.GetAccount(User.Identity.Name);
    var validationMessages = user.ValidatePayout(model.WithdrawAmount)

    if(validationMessages.Any())
    {
        ViewBag.Message = validationMessages.ToSummary();
        return View(model);
    }

    ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount;
    model.Balance = user.Balance;
    model.WithdrawAmount = 0;
    return View(model);
}

アプリケーション/サービス層を挿入したり、viewModels を使用したり、ViewModelBuilder/Mapper などで ViewModel のすべてのリセットを行ったりするなど、私が行うことは他にもありますが、これは基本的な考え方を示しています。

于 2013-06-17T08:45:10.800 に答える
0

私たちが従うアプローチには、ViewModel (あなたのケース: PayoutViewModel) 内に含まれるビジネス ケースが必要であり、メソッドを介して公開され、これらのメソッドはコントローラー アクション内で消費されます。さらに、ビューモデルがその中のモデルを参照するモデルとビューモデルを明確に分離しています。

于 2013-06-17T08:33:45.673 に答える
-2

controllers に薄いコードを作成することをお勧めします。以前に使用した serviceLayer などの別のレイヤーでビジネスロジックを処理することをお勧めします。これにより、ビュー/コントローラーに返したいもののビューモデルが返されます。サービス層クラス内で ajax メソッドを定義することもできます。これにより、コードの複雑さと保守性の問題が軽減されます。さらに読みやすくなります ..

Controller では、DI を使用して serviceLayer クラスを注入するか、インスタンス化することができます。

  ServiceLayer test = new ServiceLayer() ; 

次に、コントローラーで

  test.registerCustomer(model); // or
  test.registerCutomer(CustomerViewModel);
于 2013-06-17T08:33:36.040 に答える