你喜欢把MVC视图模型数据转换逻辑放在哪里?

Jam*_*mes 7 asp.net-mvc transform code-organization viewmodel

这是场景,我需要从多个Web服务服务调用返回的多个域​​对象中加载视图模型对象.将域模型对象转换为可消化视图模型对象的代码是一些相当复杂的代码.我想到的三个地方是:

  1. 控制器内部的内部方法,用于加载实例视图模型.
  2. 视图模型类本身上的静态get方法或属性,它返回已加载的视图模型实例.
  3. 一个完全独立的构建器或辅助类,它具有静态get方法,属性或重载构造函数,用于返回已加载的视图模型实例.

要清楚,我不想使用AutoMapper或类似的工具.从最佳实践的角度来看,我想知道这个逻辑应该去哪里,以及为什么.

编辑

所以这就是我到目前为止所做的,这确实给了我"瘦"的控制器逻辑和关注点的分离.我怎么能让它更好呢?

    // ** Controller **
    public ActionResult Default()
    {

        var viewModel = MyViewModelBuilder.BuildViewModel(MarketType.Spot);

        return View(SpotViewUrl, viewModel);
    }

    // ** Builder **
    // Lives in MVC project under ViewModelBuilders folder
    public class MyViewModelBuilder
    {
        public static ChartsModel BuildViewModel(MarketType rateMarket)
        {
            var result = new ChartsModel
            {
                RateMarket = rateMarket,
                DateRange = new DateRange()
            };
            LoadGroupedRateLists(result, rateMarket);
            LoadCoorespondingRates(result);
            return result;
        }

        private static void LoadGroupedRateLists(ChartsModel model, RateMarket rateMarket)
        {
            var rateHistSvc = new RateHistoryService(RatesPrincipal.Current.Session.Token);
            var serviceResult = (rateMarket == RateMarket.Spot)
                                                                    ? rateHistSvc.GetSpotNationalRateHistory()
                                                                    : rateHistSvc.GetContractNationalRateHistory();

            // Break lists apart by category, and re-sort and trim.
            model.Cat1Rates = CategorizeTrimAndSort("cat1", false, serviceResult);
            model.Cat2Rates = CategorizeTrimAndSort("cat2", true, serviceResult);
            model.Cat3Rates = CategorizeTrimAndSort("cat3", false, serviceResult);
            model.Cat4Rates = CategorizeTrimAndSort("cat4", true, serviceResult);
            model.Cat5Rates = CategorizeTrimAndSort("cat5", false, serviceResult);
            model.Cat6Rates = CategorizeTrimAndSort("cat6", true, serviceResult);


            // Get Date range from results.
            var sortedRateMonths = serviceResultNational
                                    .Select(rate => rate.YearMonth)
                                    .OrderBy(ym => ym.Year)
                                    .ThenBy(ym => ym.Month);

            model.DateRange.Start = sortedRateMonths.First();
            model.DateRange.End = sortedRateMonths.Last();
        }   

        ...
    }
Run Code Online (Sandbox Code Playgroud)

dan*_*wig 4

1 或 3,而不是 2。如果您执行 #3,则实际上并不让静态方法执行 Web 服务调用,而只是让它执行映射。域对象输入,视图模型输出。优先选择扩展方法而不是重载构造函数,如果对象不需要跟踪状态,则使其成为非静态没有任何好处。

为什么?

一旦您在模型上添加逻辑方法,它就不再是 POCO。最佳实践是尽可能将视图模型视为无聊的数据桶。有些人还尝试在视图模型构造函数中进行映射,一旦您遇到映射中的任何复杂性,这不是一个好主意。

如果您只需要在一个控制器中进行映射,则可以将其放在子例程中。请记住,如果您想单独测试子组件并将其保留在内部,则您的项目必须具有InternalsVisibleTo您的测试项目。

更新

查看您的代码后,我有点同意@C Sharper 的观点,即这既不属于控制器、视图模型,也不属于辅助类/方法。编写这个 ChartsModel 是非常有趣的代码,并且包含很多业务逻辑。它确实应该位于一个单独的层中。您的控制器应该向下调用该层并将所有这些有趣且重要的代码委托给另一个抽象。然后,该抽象应该返回一个域对象,正如 @C Sharper 所说。无论您使用该域对象作为视图模型,还是将其 DTO 到不同的视图模型中,都取决于您。类似的事情可能是这样的:

public class MyController : Controller
{
    private readonly IComposeChartData _chartDataComposer;

    public MyController(IComposeChartData chartDataComposer)
    {
        _chartDataComposer = chartDataComposer;
    }

    public ActionResult Default()
    {
        var chartComposition = new ChartCompositionSettings
        {
            MarketType = MarketType.Spot,
            Token = RatesPrincipal.Current.Session.Token,
        };
        var chartData = _chartDataComposer.ComposeChartData(chartComposition);
        var chartModel = Mapper.Map<ChartsModel>(chartData);
        return View(SpotViewUrl, chartModel);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个很好的精益控制器主体。抽象可能看起来像这样:

public class ChartDataComposer : IComposeChartData
{
    public ChartData ComposeChartData(ChartCompositionSettings settings)
    {
        // all of the interesting code goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

这样,您的视图模型不需要移动到单独的层中,但您确实需要在该层中创建一个类似的对象(ChartData)。该接口将您的控制器与其所需的数据解耦,并且它返回的对象与您的演示数据(视图模型)具有凝聚力。

我想我并不真正将此代码视为业务逻辑,而更多地视为表示逻辑。为什么您将其视为业务逻辑?

将您的RateHistoryService班级视为供应商。您从中获得原材料,并将这些原材料转化为不同的东西,在此过程中创造价值。这就是企业所做的。

在这种情况下,图表可视化就是您提供的价值。否则,您的客户必须自己筛选原始数据、对其进行修剪、分类、排序、分组等,然后才能创建类似的图表。

我可能应该早点解释一下,但是服务调用已经是我们自己的业务层,并返回域层业务对象。对我来说,拥有多个业务层似乎很奇怪。

您的业​​务层可以有自己的内部分层。在这种情况下,您可以创建一个RateChartingService使用RateHistoryService返回RateChartingResult业务对象的 。然后将其映射到 a ChartsModel(或者像我之前所说的那样,直接将其用作您的视图模型)。