1

An important view in our application is a big table of entities. For the purposes of this question I'll use an analogy for our entities - so let's say it's vehicles. There's half a dozen different kinds of vehicles, and they share a lot of fields, like the brand and whether it is currently available. However, there's also vehicle type-specific fields - that's what makes them different of course. An example is the mileage, which makes sense for cars but not for bikes.

There is a generalised vehicle view model that contains the most important fields that they all have in common. That way you can put them all in one list, ordered in some manner, and have the most important data clearly displayed. Clicking on one of them takes you to a different view, specific per kind of vehicle, where you can see everything and interact with the vehicle in various ways.

However, there are also overview pages for specific kinds of vehicles. Those are also tables, with the exact same layout and ordering logic and whatnot - but they may have one or two extra fields displayed in their own columns. A bicycle overview will include the size, to help people find their vehicle quickly.

Here's the thing. You can only have one view model per view. Keeping a single overview table (as a partial to be used by the different web pages) means using the same view model for every kind of vehicle - which means that you will have most fields in that view model be empty at any given time. You would have a class called VehicleViewModel that contains the generic fields like Brand, IsAvailable, but also the specific fields like Mileage, Size - which would usually be null. Then one attribute to designate the specific type of vehicle, and have the same view look at that type to determine which fields to try displaying.

I think that such a solution is "dirty". It is folding polymorphic tree of classes into one bucket and making a class have properties that do not exist depending on the kind of class it really is.

One alternative is making multiple view models and views, and thus copy-pasting html. We are using partials so the redundancy is minimal, but still significant.

Finally, here's my proposed solution. It is simplified but I would appreciate any feedback to make it a more viable candidate:

public abstract class VehicleFieldsToDisplayViewModel
{
    public abstract string RenderTableHeader();
    public abstract string RenderTableRow();

    protected Dictionary<string, object> FieldsToDisplay;
}

public class CarFieldsToDisplayViewModel : VehicleFieldsToDisplayViewModel
{
    public override string RenderTableHeader() => "<tr>" +
                                         $" <th style=\"position: relative; width: 40%;\">{FieldsToDisplay["name"]}</th>" +
                                         $" <th style=\"position: relative; width: 20%;\">{FieldsToDisplay["brand"]}</th>" +
                                         $" <th style=\"position: relative; width: 20%;\">{FieldsToDisplay["mileage"]}</th>" +
                                         $" <th style=\"position: relative; width: 20%;\">{FieldsToDisplay["isAvailable"]}</th>" +
                                         "</tr>";
    public override string RenderTableRow() => "<tr>" +
                                               $" <td>{FieldsToDisplay["name"]}</td>" +
                                               $" <td>{FieldsToDisplay["brand"]}</td>" +
                                               $" <td>{FieldsToDisplay["mileage"]}</td>" +
                                               $" <td>{FieldsToDisplay["isAvailable"]}</td>" +
                                               "</tr>";

    public CarFieldsToDisplayViewModel(string name, string brand, int mileage, bool isAvailable)
    {
        FieldsToDisplay = new Dictionary<string, object>();
        FieldsToDisplay.Add("name", name);
        FieldsToDisplay.Add("brand", brand);
        FieldsToDisplay.Add("mileage", mileage);
        FieldsToDisplay.Add("isAvailable", isAvailable);
    }
}

The view can use VehicleFieldsToDisplayViewModel by just using the rendering methods, so you can put the relevant layout in a single view. There is some html in the view model implementations, but that redundancy can be reduced by having helper methods in the abstract class like string DisplayAsWideHeaderCell(string value) or string DisplayBoolWithFancyTick(string boolAsString).

So in summary:

Option 1: every field in the same view model.

  • Pros: less immediate work, can keep the same view.
  • Cons: a single model is used for very different things, some fields will be null depending on its 'actual' type. The view itself will get increasingly complex as more types of entities are added, and thus more checks will be needed to figure out what to display.

Option 2: different views and view models for different kinds of vehicles.

  • Pros: each entity has its own model, making it clear what it represents.
  • Cons: there will be many views that differ only slightly, resulting in lots of code and script being copied making maintainability harder.

Option 3: abstract view model with different implementations that have render methods for the table headers and rows.

  • Pros: each entity has its own view model, which decides for itself what should be displayed. A single view can be used.
  • Cons: having html/css in the view model makes maintainability harder when its layout needs to be changed. That can be alleviated by gathering all the html in helper methods in the superclass, which increases complexity slightly.

What's the better solution? What else should I take into account to come to a decision?

5
  • Could you clarify: is it about MVC (suggested by your title) or MVVM (suggested by use of view models) ? Also important: opinions ("what do you think") are out of scope here. Could you therefore clarify what you mean with "better" and reformulate your question to encourage objective answers (e.g. pros & cons, or suitability in view of requirements, etc...) ?
    – Christophe
    Aug 4 at 10:18
  • @Christophe It is MVVM, my apologies. Regarding opinions; honestly I came looking for (well-supported) opinions. There's hardly any measurable technical requirements that would notice whether this is one view or sixteen different ones. This question is about what is the better practise - I think that it will be bad in the long run to ignore polymorphism and just flatten a tree of domain entities into a single view model, but the only even vaguely measurable concern is maintainability.
    – KeizerHarm
    Aug 4 at 10:29
  • Thanks for the feed back. Since you have intuitively looked at a more robust, polymorphic & maintainable solution, that is open for extension (OCP?), and you now look for third party feedback, I suspect that you are looking at objective arguments ("use this because <elements that matters to you>") more than opinions ("I prefer this because <personal taste and beliefs>"). It is an interesting question and iI think it's worth to fine-tune the concluding questions (not everybody will read the comments). Maybe just think of what would make an answer useful/valuable for you (and others) :-)
    – Christophe
    Aug 4 at 10:45
  • @Christophe And thank you for your comments :-) I have now summarised the problem and the possible solutions below, and tried to refine the question to "What other factors should I consider to decide on the better solution?" But I still wish to be open to feedback on the third, "newly proposed" option since it is the one I cobbled together myself and it can probably stand a refactoring or two to be an even more viable candidate solution.
    – KeizerHarm
    Aug 4 at 10:53
  • Looking at your code example, it still looks MVC to me - server-side rendered. a ViewModel should not include HTML tags, only data. Aug 14 at 13:26
2
+100

First of all, the purpose of Model View in the MVVM pattern is to hold and maintain the state of the view. It shouldn't know about the view, and it shouldn't have a view code.

Secondly, regardless of the technology that you are using, this problem should be a view problem and it should be resolved there. One solution that is supported by many technologies is view aggregation/inheritance where you separate your view into multiple parts (partial view) and aggregate them based on the case.

For example, you can create an abstract view of the vehicle which has all the common fields. For each specific type, you create a new view that inherits or includes the vehicle view and extend it with the extra fields. As such, there is no duplication in the view code and each type has its own view.

6
  • So this interests me. Firstly, I thought that a view model did hold some information about data presentation. After all, there's all these tags to designate the labels, and you can even specify the display view to use all from the view model. A rendering method on a view model does not seem out of place to me, and it is also not something I came up with by myself.
    – KeizerHarm
    Aug 9 at 7:27
  • I know that there are ways to do view inheritance but I need to be sure it's the best thing to do before I tell my team that our overbloated application needs yet another external library ^^;
    – KeizerHarm
    Aug 9 at 7:28
  • I am not sure that I am able to follow. You need to be more specific about the technology stack. Usually, VM is POCO used to hold view status that can be reused and unit tested. Those labels could be generic data annotations being exploited by the view. Also, rendering shouldn't be done in the VM. Some MVVM implementations combine the controller and the VM in one class. In that case, you may see the rendering happens in the VM. However, rendering is compiling the view and model to generate the response. it doesn't imply that the view code should be part of the model nor the controller.
    – AlHassan
    Aug 9 at 17:29
  • Fair, I kept the specific technology out of it because this is more of a general principles question, but at least for C#/html the default system library supports display names for fields , with which they are tagged in the view model, thus putting presentation logic in the view model.
    – KeizerHarm
    Aug 10 at 7:04
  • There's a similar tag called UIHint to make the cshtml file (which contains both languages) pick automatically a specific view when a larger view is told to just render that model.
    – KeizerHarm
    Aug 10 at 7:12
1

Think "composition", not "inheritance".

Neither option you provide is ideal, and I can think of a better option:

  • Have a ViewModel object that is "composed" instead of using Object-Oriented Ideas
  • Have common base-fields on the root object, have fields that hold specialised data. Some fields might have sub-fields of specialised data.

For example:

  • VehicleType - enum
  • VehicleId - int (global shared sequence)
  • VehicleWeight - decimal
  • VehicleSurfaceTopSpeed - decimal
  • BikeAttributes - sub-object
  • TruckAttributes - sub-object
  • CarAttributes - sub-object
  • BoatAttributes - sub-object
  • MaintenanceAttributes - sub-object

The way you find the design for this is to start with the smallest elements in a long list, the cluster as required. You might optimise to reduce the average amount of fields that end up null. As you can see, I also included an example of "MaintenanceAttributes" - don't just cluster by vehicle type because half of the types might record Mileage, and the other half don't.

This is better for you, because:

  • you seem to want to minimise the amount of work/LOC that would be otherwise required for implementing multiple endpoints (with different ViewModels)
  • you seem to need to be able to load all of the data in a sequence from a single endpoint (single round-trip)

You can also go further with your architecture to remove a lot of unnecessary work here: the use of GraphQL, or something similar.

You aren't really getting much value having custom code in between your database and your consuming application. With GraphQL you can define a View, then consume it in your application - all from a single endpoint.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.