【问题标题】:Is it good Practice Call Business Logic from ViewModel从 ViewModel 调用业务逻辑是否很好?
【发布时间】:2014-05-23 15:42:56
【问题描述】:

我正在处理一个大型 ASP.NET MVC 项目(大约 15 个单独的项目)。我们正在使用外观设计模式来调用业务逻辑以及其他项目。

问题:在 MVC 应用程序中,从 ViewModel 调用 Facade 是最佳实践吗?

我使用单个外观实例来调用所有函数。我为每个 Action 创建一个 ViewModel,并使用 ViewModel 中的数据填充它。这些结果使 ViewModel 变得更大,但 Controller Action 变得更薄,因为我们现在正在 ViewModel 中进行工作。在 ViewModel 构造函数中,我传递外观实例并从业务逻辑层获取所需的内容。

public class MyViewModel
{
    private Facade _Facade;
    public IEnumerable<SomeModel> Collection { get; set; }
    public IEnumerable<SelectListItem> Years { get; set; }
    public IEnumerable<SelectListItem> Quarters { get; set; }
    public int SelectedYear { get; set; }
    public int SelectedQuarter { get; set; }


     public BottomUpForecastViewModel(EXFacade facade)
    {
        this._Facade = facade;
        this.Years = GetFinancialYears();
        this.Quarters = GetFinancialQuarters();
        this.SelectedYear = DateTime.Now.Year;
        this.SelectedQuarter = TimePeriods.GetQuarterNoForDate(DateTime.Now);
        Collection = GetMonthlyCollection(SelectedYear, SelectedQuarter);// Take data     from the _Facade(call facade)

    }

}

  public class MyController : Controller
  {


    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult BottomUpForecast()
    {

        return View(new MyViewModel(facade));
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult BottomUpForecast(MyViewModel model)
    {

        return View();

    }

}

这是好的做法吗?

考虑到我们不需要担心依赖关系,您有什么更好的方法建议吗?

更新:我发现了一篇关于如何让控制器精益的有趣文章“让他们节食”: http://lostechies.com/jimmybogard/2013/12/19/put-your-controllers-on-a-diet-posts-and-commands/**

【问题讨论】:

  • 你可以参考我对这个帖子的回答。我不会在控制器中填充您的 ViewModel。这增加了控制器中的代码量,我认为这通常很糟糕。相反,只需从控制器调用业务层并让业务层填充您的虚拟机。我的控制器代码会这样...MyViewModel vm = new BusinessLayer().BottomForecastVM(); return view(vm);stackoverflow.com/questions/21597180/…

标签: c# asp.net asp.net-mvc asp.net-mvc-4 viewmodel


【解决方案1】:

你的想法是正确的。从 View Model 调用业务逻辑是完全可以接受的。我一直都这样做。

不幸的是,您当前的实现与具体类型紧密耦合。您可以使用一点抽象重构:

相反,在您的业务层中,创建一个接口,IEXFacade 以传递绑定您的对象并传递给您的 ViewModel:

public interface IEXFacade
{
   public IEnumerable<SomeModel> GetMonthlyCollection(int SelectedYear, int SelectedQuarter);
   public IEnumerable<SelectListItem> GetFinancialYears();
   public IEnumerable<SelectListItem> GetFinancialQuarters();
   public int getSelectedYear();
   public int getSelectedQuarter(DateTime dateTime);
}

您的 EXFacade 定义如下所示:

public class EXFacade : IEXFacade
{
   private TimePeriods _timePeriods = new TimePeriods();

   public int getSelectedYear()
   {
       return DateTime.Now.Year;
   }

   public int getSelectedQuarter (DateTime dateTime)
   {
       return _timePeriods.GetQuarterNoForDate(dateTime);
   }


   public IEnumerable<SomeModel> GetMonthlyCollection()
   {
           ....
           return MonthlyCollection;
   }

   public IEnumerable<SelectListItem> GetFinancialYears();
   {
           ....
           return MonthlyCollection;
   }

   public IEnumerable<SelectListItem> GetFinancialQuarters();
   {
           ....
           return MonthlyCollection;
   }

}

现在您的 View Model 将采用 IEXFacade 并且更能容忍变化

public class MyViewModel
{
     MyViewModel(IEXFacade facade)
     {
        Years = facade.GetFinancialYears();
        Quarters = facade.GetFinancialQuarters();
        SelectedYear = facade.getSelectedYear();
        SelectedQuarter = facade.getSelectedQuarter (DateTime.Now);
        Collection = facade.GetMonthlyCollection(SelectedYear, SelectedQuarter);
    }


    //Keeping the Facade Object seems extraneous (unless I'm missing something)
    //private Facade _Facade;
    public IEnumerable<SomeModel> Collection { get; set; }
    public IEnumerable<SelectListItem> Years { get; set; }
    public IEnumerable<SelectListItem> Quarters { get; set; }
    public int SelectedYear { get; set; }
    public int SelectedQuarter { get; set; }
}

我们的目标是通过传递一个接口来解耦对您的 EXFacade 类的特定实现的依赖。现在您的 EXFacade 方法逻辑可以在不破坏您的视图模型的情况下进行更改。只要接口(属性和签名)保持不变。


结论:

我不喜欢直接从我的 ViewModel 调用逻辑,而不是我的 Controller。但是,它通常更方便,因为它节省了一个步骤。相反,直接注入模型的逻辑不如将其整合到控制器中那么明显。但是关于“胖控制器”与“胖模型”的争论是相当平等的,我不认为任何一方更正确。

更重要的是要了解外观模式是“聊天”逻辑层和表示层之间的接口。为了抽象和解耦,该模式需要一个接口。一旦你用一个接口抽象了你的 Facade,你就可以通过使用像 NInject 这样的 IOC 容器将你的 Facade 注入到你的控制器或模型中来进一步解耦。

我强烈建议在这样的大型项目中使用依赖注入模式。

【讨论】:

  • @unique,希望对我有所帮助。我更新了我的答案。我相信你走在正确的轨道上,但使用 IOC 容器来实现依赖注入会有所帮助;)
  • 另外需要注意的是,在视图模型中实现逻辑的这种方式,也是将默认构造函数添加到视图模型中。如果您不添加默认构造函数,您会在 POST 上遇到 ASP.NET MVC 绑定器的问题。
  • 您好@DaveAlperovich,感谢您的回复。你提到了使用 DI,我完全可以看到这对视图模型有什么用处,但当然,控制器现在初始化 VM 的方式意味着给定当前示例,我们必须将外观注入控制器然后传递它下到虚拟机。你认为它可以在虚拟机的构造函数期间注入属性吗?您推荐的将 DI 用于 VM 的具体方法是什么?
  • 具体来说,我在考虑我们经常使用Html.Partial 的情况,似乎我们只能将单个对象传递给部分,这意味着我们不能再传递门面下。因此,如果虚拟机可以自己实例化他们需要的服务,那么将所需的服务属性注入到虚拟机的构造函数中是否有意义?
  • @Worthy7,我没想到将 DI 用于 VM。我在考虑 VM 对服务的调用。这些服务将由 DI 管理。看看我的例子:VM 有一个实现 Facade 接口的服务属性。不知道我解释得好不好。
【解决方案2】:

如果您将业务逻辑放在 ViewModel 中,您将打破 MVC 模式。构造视图是控制器的工作,而不是视图通过接收依赖来构造自身。

ViewModel 应该忽略其他层(View 和 Controller),从而促进松散耦合架构。

如果您的 ViewModel 变得太大,您可以创建仅用于构建 ViewModel 的辅助方法或类。

public class MyController : Controller
{
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult BottomUpForecast()
    {
        return View(this.GetMyViewModel());
    }


    private MyViewModel GetMyViewModel()
    {
        var viewModel = new MyViewModel()
        {
            Years = this.facade.GetFinancialYears();
            Quarters = this.facade.GetFinancialQuarters();
            SelectedYear = DateTime.Now.Year;
            SelectedQuarter = this.facade.TimePeriods.GetQuarterNoForDate(DateTime.Now);
            Collection = this.facade.GetMonthlyCollection(SelectedYear, SelectedQuarter);
        }

        return viewModel;
    }
}

// Thin ViewModel
public class MyViewModel
{
    public IEnumerable<SomeModel> Collection { get; set; }
    public IEnumerable<SelectListItem> Years { get; set; }
    public IEnumerable<SelectListItem> Quarters { get; set; }
    public int SelectedYear { get; set; }
    public int SelectedQuarter { get; set; }
}

关于这个话题的有趣讨论在这里:https://stackoverflow.com/a/1464030/1027250

【讨论】:

  • 非常感谢您的评论。那么模型在 ASP.NET MVC 应用中的用途是什么?
  • @unique - 大多数时候,它们是相同的。关键区别在于模型表示域实体,ViewModel 是为包含下拉列表等的特定视图量身定制的。IE:PersonModel 与 EditPersonViewModel
  • 继续 Yorro 的回答。你有 3 层权利,数据业务和演示。数据层将调用数据库并填充“域实体”,业务将简单地将域实体转移到 ViewModel 中。业务层将简单地将 ViewModel 传递给控制器​​,而无需控制器处理任何人口
【解决方案3】:

视图模型是视图的模型。它应该包含数据(模型)以及将该数据移入和移出视图所需的任何逻辑。它不应该知道任何其他层。它甚至不应该依赖于控制器,更不用说低于它的任何东西。填充该视图模型是控制器的工作,因此控制器的工作是调用业务逻辑。

【讨论】:

  • 但是在这种情况下,从控制器动作结果视图中填充视图模型不是大型控制器动作吗?
  • 当然,但那是控制器的工作。通过将该逻辑推送到视图模型中,您会创建一个额外的依赖项。
  • ViewModel 是一个横切层,必须充当“数据传输对象”。如果您在 ViewModel 中访问业务逻辑,您将通过向该层添加额外的工作来打破单一责任原则。
  • @Andrey 遵循您在 aspnet/mvc 应用程序中概述的模式。 ViewModel 不是 DTO。回想起来,我宁愿调用 ViewModel.AddPost() 而不是 businessLayer.Posts.Add(post)。向 ViewModel 添加行为(委托给业务层)是否有任何现实的缺点(假设其中没有逻辑)?记住 MVVM 不是 MVC,ViewModel 可能负责许多不相关的 DTO!实际上,这将声明 ViewModel 实际上只是另一个可以稍微清理 Controller 的层。
  • @jwize 从 ViewModel 调用业务层会增加软件的耦合度。我同意 MVVM 不是 MVC,但是将 MVVM 与 ASP.NET MVC 框架一起使用,你没有转义;)。控制器应该负责将数据从业务层转换到视图模型(我建议使用 AutoMapper 来完成这项肮脏的工作)。
【解决方案4】:

添加到@Yorro 帖子中,MVVM 实际上使用了这种模式,VM 负责所有此类活动。在 MVC 中,最好使用 Controller 来执行此类操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多