C# 所有业务逻辑应该在域模型中还是在域模型中?

C# 所有业务逻辑应该在域模型中还是在域模型中?,c#,.net,asp.net-mvc,asp.net-mvc-4,C#,.net,Asp.net Mvc,Asp.net Mvc 4,ASP.NETMVC4-基本上,我以前将所有业务逻辑都放在控制器中(我尝试将其放在域模型中)。 然而,我不太清楚我的所有业务逻辑是应该放在域模型中,还是应该保留在控制器中 例如,我得到一个控制器操作,如下所示: [HttpPost] public ActionResult Payout(PayoutViewModel model) { if (ModelState.IsValid) { UserProfile user =

ASP.NETMVC4-基本上,我以前将所有业务逻辑都放在控制器中(我尝试将其放在域模型中)。 然而,我不太清楚我的所有业务逻辑是应该放在域模型中,还是应该保留在控制器中

例如,我得到一个控制器操作,如下所示:

[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);
        }
    }

或者您将如何执行此操作?

建议您在控制器上使用精简代码,最好在我之前使用过的其他层(如serviceLayer)中处理业务逻辑,该层将返回您希望返回到视图/控制器的视图模型。甚至可以在服务层类中定义ajax方法。这样可以降低代码复杂性和可维护性问题。甚至更具可读性

在控制器中,可以使用DI注入serviceLayer类或将其实例化为

  ServiceLayer test = new ServiceLayer() ; 
然后在你的控制器

  test.registerCustomer(model); // or
  test.registerCutomer(CustomerViewModel);

我们遵循的方法要求业务案例包含在ViewModel中(您的案例:PayoutViewModel),并通过方法公开,这些方法将在控制器操作中使用。此外,我们在模型和视图模型上有一个明确的分离,其中视图模型引用其中的模型。

我们将应用程序和业务逻辑分为不同的层(csproj文件),一个用于业务逻辑的域层和一个用于应用程序逻辑的服务层。这将它们完全从MVC项目中抽象出来。这对我们有两大好处。首先,业务逻辑与可能发生变化的模式无关。几年前,我们中没有人会想到MVC今天会如此流行,几年后,我们不知道是否会有新的东西出现并取代MVC,因此,如果你想放弃MVC而去做其他事情,那么让你的绝大多数代码“脱离”MVC会有所帮助

第二个好处是它使得拥有不同的表示层非常容易实现。因此,如果您想将您的业务逻辑表示为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);
    }
}

我将把所有逻辑放在域模型中,并对域进行两次调用,一次用于验证,一次用于执行用例

所以实体看起来像这样:

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);
}
您的控制器如下所示:

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或simmilar中重置ViewModel,但这显示了基本思想。

对我来说,关注点分离是这些决策最重要的指导原则。因此,这取决于您的域有多复杂,以及使代码复杂化会给您带来什么好处

总之,作为一般规则,我倾向于给控制器以下关注点:

  • 视图模型的实例化和映射(除非存在大量映射)
  • 视图模型验证
  • 而且,我倾向于参考非应用程序特定领域知识的模型(或服务):

  • 我可以取款
  • 撤回
  • 这就是我如何分割代码的方法:

        [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);
            }
        }
    

    在保存应用程序状态时,我通常使用拦截器。这样,您就可以对整个请求进行包装。

    我建议您将所有代码放入域模型中。它使控制器更干净。@DarrenDavies因此即使ModelState.IsValid也应该放在域模型中吗?不,
    ModelState.IsValid
    是MVC的东西,我会将
    if
    条件部分放在域中,返回模型或引发异常。下面是一篇关于FATController业务逻辑应该在域中的重要文章。请不要说它应该在服务层而让人困惑。为什么它在域中,我不明白,那么你是说我们根本不需要serviceLayer?域是业务的模型,它的类代表业务中的实体,其中的代码代表业务逻辑,与我的方法相似,但我必须说,我认为最好有一个验证方法,否则由于对退出有其他限制,您的控制器中可能会出现大量的if语句。@Davintroon那么您在帐户模型中有了诸如Can取款等方法吗?我想将实体从实际业务逻辑中分离出来,因此也许我应该在account domain实体(模型)中使用partial?@JohnMayer Yes将它们连接起来。为什么要将实体和业务逻辑分开?这是一种称为“贫血”域实体的反模式。域模型实体应该同时具有数据和行为。再次,更多关于将业务逻辑置于服务层的讨论???业务逻辑属于该域吗?你是说从MVC的角度来看,它是在服务层,因为MVC只看到了服务层?而实际上,它在域中的服务层之下?