4

For example I have these three classes:

public abstract class AbstractBase 
{
    public int A { set; get; }
}

public class Derived1 : AbstractBase
{
    public int B { set; get; }
    public int C { set; get; }
}

public class Derived2 : AbstractBase
{
    public int D { set; get; }
}

And a service that returns with a list of AbstractBase

public class AbstractBaseService
{
    public IEnumerable<AbstractBase> GetInstances()
    { 
        var results = new List<AbstractBase>();

        // TODO: Get data

        return results;
    }
}

And I want to display these classes in a strongly typed ASP.NET MVC View in a table (or a GridView in WF). So what is the best way? And what if I want to add edit option?

enter image description here

Ahmed KRAIEM
  • 10,267
  • 4
  • 30
  • 33
dvjanm
  • 2,351
  • 1
  • 28
  • 42
  • You may be asking for your cake and eating it too in this particular instance. The abstract base can only expose `A` to your view. If you *need* `B`, `C`, or `D` then you'll have to downcast with your current design: `if (result is Derived1){ // downcast }`. Otherwise, I'd rethink your design from class hierarchies. – Adam Kewley Oct 01 '13 at 07:46
  • But my domain model requires this structure. And I want to avoid to check in views what is the dynamic type – dvjanm Oct 01 '13 at 07:49
  • Then, provided your domain model service is also only capable of producing `AbstractBase`, you're stuck with downcasting. Another 'trick' is to remove *as much* type checking by grouping the response with an enum that indicates *which* class you're actually working with. Again, this would require you to have control over your service at the very least. – Adam Kewley Oct 01 '13 at 07:54
  • Do you use the abstract type or derived types in your views? – Henk Mollema Oct 01 '13 at 07:58

1 Answers1

3

You can use the Display Templates in that scenario.

The idea is to create a display template per each concrete derived type, and leave to MVC the task of figuring out the specific type of each item in a list at runtime and then pick up the appropriated template .

Imagine that we have the following controller action method, that returns a view for displaying a list of AbstractBase objects:

public ActionResult Foo()
{
    var list = new List<AbstractBase>()
    {
        new Derived1{ A = 1, B = 2, C = 3},
        new Derived2{ A = 1, D = 4},
    };
    return View(list);
}

You can create the following templates:

~/Views/Shared/DisplayTemplates/Derived1.cshtml

@model MvcApplication1.Models.Derived1
<td>
    @Html.DisplayFor(m => m.A)
</td>
<td>
    @Html.DisplayFor(m => m.B)
</td>
<td>
    @Html.DisplayFor(m => m.C)
</td>
<td></td>

~/Views/Shared/DisplayTemplates/Derived2.cshtml

@model MvcApplication1.Models.Derived2
<td>
    @Html.DisplayFor(m => m.A)
</td>
<td></td>
<td></td>
<td>
    @Html.DisplayFor(m => m.D)
</td>

Unfortunately as you want to display the items in a table, you will need to provide empty tr elements for columns that are not relevant to each concrete type.

And the main view ~/Views/Home/Foo.cshtml will look like :

@model IEnumerable<MvcApplication1.Models.AbstractBase>           
<table>
    <thead>
        <tr>
            <th>Col A</th>
            <th>Col B</th>
            <th>Col C</th>
            <th>Col D</th>
        </tr>
    </thead>    
    <tbody>
        @foreach(var item in Model)
        {
            <tr>
                @Html.DisplayFor(m => item)
            </tr>
        }
    </tbody>
</table>

It takes a list of AbstractBase objects and builds the body of the table iterating over the list and calling DisplayFor for each item. MVC will check at runtime the concrete type of each item and will then search for an appropriated view in the DisplayTemplates folder.

That's the most simple approach, where the fields in the common base class are rendered by each concrete derived class template. However, it makes sense to have a common template for fields in the base class.

You can create another display template for the fields in the base class, ~/Views/Shared/DisplayTemplates/AbstractBase.cshtml

@model MvcApplication1.Models.AbstractBase
<td>
    @Html.DisplayFor(m => m.A)
</td>

How this template for fields in the common base class will be called depends if you are on MVC 4 or not. If you are on MVC 4, you can call the base template from each of the templates for the derived classes. For example, the template for Derived1 will look like this:

@model MvcApplication1.Models.Derived1
@Html.DisplayFor(m => m, "AbstractBase")
<td>
    @Html.DisplayFor(m => m.B)
</td>
<td>
    @Html.DisplayFor(m => m.C)
</td>
<td></td>

If you are not on MVC 4, sadly nested display editors don't work, so the line @Html.DisplayFor(m => m, "AbstractBase") above won't do anything.

Your first and best option outside of MVC4 is to manually render the template for the base class as a partial view. That line would be replaced by @Html.Partial("~/Views/Shared/DisplayTemplates/AbstractBase.cshtml", Model)

Another option that I would recommend you to avoid is to move the call to the abstract class template into the main view, instead of being called from each of the derived classes templates. This might not even be a valid option if the template for the base class needs to be embedded inside some element generated by each of the derived classes. For example it won't be an option if the columns for the base class were not all at the beginning (or end) of each row

So the main view that renders the view will do:

@foreach(var item in Model)
{
    <tr>
        @Html.DisplayFor(m => item, "AbstractBase")
        @Html.DisplayFor(m => item)
    </tr>
}

I would always stick with calling the base template from each of the derived templates, and I would say you should be very careful if you ever find yourself using the last approach. Otherwise you would need to remember calling @Html.DisplayFor(m => item, "AbstractBase") every time you want to render a derived template and some html designs may not even be possible to achieve using that approach.

Hope it helps!

Daniel J.G.
  • 34,266
  • 9
  • 112
  • 112