以下是该场景,我需要从多个Web服务服务调用返回的多个域对象中加载视图模型对象。将域模型对象转换为可消化视图模型对象的代码是一些相当复杂的代码。我想到的三个地方是:
要清楚,我不想使用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();
}
...
}
答案 0 :(得分:3)
1或3,而不是2.如果你做#3,你实际上并不让静态方法进行Web服务调用,只是让它进行映射。域对象,viewmodel(s)输出。首选扩展方法来重载构造函数,如果对象不需要跟踪状态,那么将其设置为非静态就没有任何好处。
为什么?
只要在模型上放置逻辑方法,它就不再是POCO。最佳做法是尽可能地将视图模型视为无聊的数据桶。有些人还试图在viewmodel构造函数中进行映射,一旦你在映射中遇到任何复杂的情况,这不是一个好主意。
如果您只需要在一个控制器中进行映射,则可以将其放在子例程中。请记住,如果您想要单独测试sub并将其保持在内部,那么您的项目必须具有InternalsVisibleTo您的测试项目。
<强>更新强>
在查看代码之后,我倾向于agree with @C Sharper,它既不属于控制器,也不属于viewmodel,也不属于helper类/方法。编写这个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);
}
}
这是一个很好的精益控制器体。抽象可能看起来像这样:
public class ChartDataComposer : IComposeChartData
{
public ChartData ComposeChartData(ChartCompositionSettings settings)
{
// all of the interesting code goes here
}
}
这样,您的viewmodel不需要移动到单独的图层中,但您需要在该图层中创建类似的对象(ChartData)。界面将控制器与其所需的数据分离,并且它返回的对象与您的表示数据(viewmodel)紧密相关。
我想我并不认为这段代码是业务逻辑,而是更多 表示逻辑。为什么将其视为业务逻辑?
将您的RateHistoryService
课程视为供应商。您从中接收原材料,并将这些原材料转换为不同的东西,在此过程中创建价值。这就是企业所做的事情。
在这种情况下,图表可视化是您提供的值。否则,您的客户必须先筛选原始数据,修剪,分类,排序,分组等,然后才能创建类似的图表。
我可能应该先解释一下,但服务电话 已经到了我们自己的业务层,并返回域层 业务对象。拥有多个业务对我来说似乎很奇怪 层
您的业务层可以拥有自己的内部分层。在这种情况下,您可以创建一个RateChartingService
,使用RateHistoryService
返回RateChartingResult
商务对象。然后将其映射到ChartsModel
(或者我之前说的,直接将其用作您的视图模型)。
答案 1 :(得分:2)
我想说不要在你的控制器中这样做。控制器应尽可能“瘦”。我会这样做。
您的“数据层”会为Domain对象分配其属性和值。 然后您的后续层称其为“业务层”,将您的域对象转移到ViewModel中。您只需将视图模型传递给控制器,而无需Controller处理任何逻辑。
分离非常重要。域对象应该远离控制器,控制器应该只关心视图模型。