Where do you want to place the MVC model data transformation logic?

Here is the scenario, I need to load a view model object from several domain objects that are returned from several web service calls. The code that converts the domain model objects into a model object of an assimilable form is a rather complex code. The three places that I thought about placing it are as follows:

  • Internal methods inside the controller to load the instance view model.
  • The static get method or property in the view model class itself that returns the loaded instance of the view model.
  • A completely separate building or utility class that has a Static get method, a property, or an overloaded constructor that returns a loaded instance of a view model.

To be clear, I do not want to use AutoMapper or tools like this. In terms of best practices, I would like to know where this logic should go and why.

EDIT

So, here is what I still have, it gives me “skinny” controller logic and separation of problems. How can I do it better?

// ** 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(); } ... } 
+6
source share
2 answers

1 or 3, not 2. Provided that if you do # 3, you actually do not allow the static method to make web service calls, just let it do the mapping. The domain object is in, viewmodel (s). Prefer the extension method for an overloaded constructor, if the object does not need to track the state, there is no benefit in making it non-static.

Why?

As soon as you put the boolean method into the model, it ceases to be POCO. Best practice is to handle view models as much as possible, such as boring data buckets. Some people also try to make a mapping in the viewmodel constructor, which is not a good idea when you get into any kind of display complexity.

If you only need to do the mapping in one controller, you can put it in a subroutine. Keep in mind if you want to test sub in isolation and keep it internal, your project will need to have InternalsVisibleTo your test project.

Update

Looking at my code, I tend to agree with @C Sharper that this applies neither to the controller, nor to the viewmodel, nor to the helper class / method. Compiling this ChartsModel is very interesting code and contains a lot of business logic. It really should be in a separate layer. Your controller should go to this layer and delegate all this interesting and important code to another abstraction. This abstraction should then return the domain object, as @C Sharper said. Regardless of whether you use this domain object as your view model or DTO in another view model, it is up to you. Here's what it looks like:

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

This is a good piece of the controller. Then the abstraction might look something like this:

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

Thus, your view model should not move to a separate layer, but you need to create a similar object (ChartData) in this layer. The interface separates your controller from the data it needs, and the returned object binds the presentation data (viewmodel).

I think that I really do not see this code as business logic, but more like presentation logic. Why do you see it as business logic?

Think of your RateHistoryService class as a provider. You get raw materials from it and turn these raw materials into something else, creating value in this process. This is what companies do.

In this case, the visualization of the chart is the value you provide. Otherwise, your customers will have to sift through the raw data, crop, classify, sort, group, etc., before they can create similar charts.

I probably should have explained this earlier, but the call to the service is already at our own business level and the domain level of business objects is returned. It seems strange to me to have more than one business layer.

Your business level may have its own internal bundle. In this case, you can create a RateChartingService that uses the RateHistoryService to return the RateHistoryService business object. Then map this to ChartsModel (or, as I said earlier, use it directly as your view model).

+2
source

I would say do not do this in your controller. Controllers should be as skinny as possible. I would do it like this.

The Data Layer assigns its properties and values ​​to Domain objects. Then your next layer calls it “Business Layer”, which will transfer your domain object to your ViewModel. And you just pass the view model to your controller if the controller does not handle any of these logic elements.

Separation is very important. Domain objects must remain outside the controller, and controllers should only care about view models.

+2
source

All Articles