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?