3

I'm having a problem with display templates and dealing with interfaces and objects which implement the interface. In the example I have many objects, which I want to be rendered in a fixed way, I decided to create an interface and reference this in the view which I've decided to put into the shared display templates folder. DisplayFor doesn't seam to work for objects passed to it which implement the interface in the view, does any one know a solution to this.

Its probably easier to explain via code so I've wrote a quick example. The base interface and two classes which inherit from it:

public interface IPet
{
    String Name { get; }
}

public class Dog : IPet
{
    public String Name { get; set; }
}

public class Cat : IPet
{
    public String Name { get; set; }
}

The example display template in shared display templates

@model IPet

<div>@Model.Name</div>

The example view model to be passed to the view

public class VM
{
    public IPet Master { get; set; }
    public IEnumerable<IPet> Minions { get; set; }
}

The controller (in this case to create mock information)

    public ActionResult Index()
    {
        var viewModel = new VM();
        viewModel.Master = new Cat(){Name = "Fluffy"};

        var minions = new List<IPet>();

        minions.Add(new Dog(){Name = "Dave"});
        minions.Add(new Dog(){Name = "Pete"});
        minions.Add(new Cat(){Name = "Alice"});

        viewModel.Minions = minions;

        return View(viewModel);
    }

and finally the view which I would expect DisplayFor to work

@model ViewInheritance.Models.VM

<h2>Master</h2>

@Html.DisplayFor(x => x.Master)

<h2>Minions</h2>

@Html.DisplayFor(x => x.Minions)

Given that all the objects are are defined in the view model as the interfaces, howcome it fails to use the display template?

One solution I have found is to simply use the code

@Html.DisplayFor(x => x.Master, "IPet")

To recap, the question is:

Why does this happen? Is there a way to make DisplayFor correctly work out that a type of Cat which implements IPet should in fact be looking at the common shared view IPet.cshtml?

Thanks

Sam
  • 40,644
  • 36
  • 176
  • 219
Martin
  • 1,355
  • 2
  • 14
  • 21
  • I know the coede is an example, but for the sake of a valid code sample, all your `IPet` implementations contain a `private` property as that is the C# default accessor if `public` is not specified. Also, the `Name` property of `IPet` only has a getter, this will cause your code in your action to fail to compile as you trying to set property which does not have a setter. – Nope Feb 03 '12 at 11:55
  • possible duplicate of [Overriding the default EditorFor template-selecting in ASP.NET MVC 3 RC](http://stackoverflow.com/questions/4161089/overriding-the-default-editorfor-template-selecting-in-asp-net-mvc-3-rc) – Sam Jul 10 '14 at 00:37

2 Answers2

0

Starting a new MVC application and fixing the code to actually compile the view renders fine. It also renders fine when moving the view into the shared folder.

I Added setter to IPet:

public interface IPet
{
    String Name { get; set; }
}

I updated implementation and added public accessors:

public class Dog : IPet
{
    public String Name { get; set; }
}

public class Cat : IPet
{
    public String Name { get; set; }
}

I left your VM alone and also did not change any code in your View. Pressing F5, running the MVC application rendered the results as expected (See image).enter image description here

Nope
  • 22,147
  • 7
  • 47
  • 72
  • 1
    Is it really using the display template? As it seems the minions are on the same line and a `
    ` normally gets placed on it's own row (css `display: block;`).
    – Kristoffer L Jul 03 '12 at 08:32
  • @KristofferL: To borrow from: http://msdn.microsoft.com/en-us/library/system.web.mvc.html.displayextensions.displayfor.aspx. `Html.DisplayFor` returns HTML markup for each property in the object that is represented by the expression. That markup is what ever is rendered in the output. If you view source of the page you see if it is rendering a div or not in a given case. `Html.DisplayFor` is a set of extension methods as far as I know and not a display template but I could be wrong about that. – Nope Jul 03 '12 at 08:39
  • 1
    This answer doesn't actually address the problem of the `IPet.cshtml` display template not being used. In fact, in this answer, the display template is still not used, so this doesn't solve the problem. – Sam Jul 10 '14 at 00:12
-1

Unfortunately, I don't think ASP.NET MVC currently supports automatically selecting templates based on implemented interfaces. I think this makes sense because a class could implement multiple interfaces, so if you had templates for more than one of those interfaces, which one should the framework choose?

You could use a base class instead of an interface if your design can cope with it:

  • Change IPet to a (possibly abstract) class.
  • Change IPet.cshtml to Pet.cshtml.

Otherwise I think you'll just need to explicitly tell the framework which template to use. Here are some options:

  • Decorate the view model properties with [UIHint].
  • Specify the template in your calls to your HtmlHelper methods such as DisplayFor.
  • Make your own ModelMetadataProvider and change the TemplateHint property of the resulting ModelMetadata.
Sam
  • 40,644
  • 36
  • 176
  • 219