DisplayTemplate being ignored (covarient interface?)

187 Views Asked by At

This is a weird one. I have the following view file (Views/Search/Submit.cshtml):

@model IEnumerable<KeyValuePair<string, ISearchProvider>>

@foreach (var provider in Model)
{
    var results = provider.Value.Results.Take(10);

    if (results.Count() > 0)
    {
        <text><li class="dropdown-header">@provider.Key</li></text>
        @Html.DisplayFor(x => results)   
    }
}

... where results is a System.Collections.Generic.IEnumerable<out T>, and T is ISearchMatch.

I have then defined a display template in Views/Search/DisplayTemplates/SiteSearchMatch.cshtml;

@model SiteSearchMatch
<li>@Html.ActionLink(Model.Name, "details", "site", new { Id = Model.Id }, null)</li>

... and SiteSearchMatch implements ISearchMatch like so;

public class SiteSearchMatch: ISearchMatch
{
    public int Id { get; set; }
    public string Name { get; set; }
}

I'd expect that my display template gets used; but it doesn't. Instead, the output I see being output is;

<li class="dropdown-header">sites</li>
11147166811481897189813271028

... where that string of numbers is the combination of all the Ids of the ISearchMatch's I wanted to render via the display template.

It seems Razor is simply rendering the ISearchMatch using the first attribute defined in the class; if I remove the definition of the Id property, I instead see the combination of all the Name's of the ISearchMatch's.


Does anyone know why this is happening, and how I can get Razor to use the display template I've specified?

2

There are 2 best solutions below

0
On BEST ANSWER

The lame answer is that the "Build Action" on my View file Views/Search/DisplayTemplates/SiteSearchMatch.cshtml was set to "None", rather than "Content".

This meant the code worked fine when running in Debug mode within Visual Studio, but didn't work when any deployment was made.

Just to reiterate; this fix required no code changes. Simply change the "Build Action" back to "Content".

3
On

Your expectation is wrong:

I'd expect that my display template gets used; but it doesn't.

The output you see is the ID's simply listed. I suspect your ISearchMatch-interface does only expose the Id-property, but this does not matter. What matters is the actual type of the instance of the result. In your case the following line:

@Html.DisplayFor(x => results)

can be implicitly evaluated as

HtmlHelper<IEnumerable<KeyValuePair<string, ISearchProvider>>>
    .DisplayFor<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>>
        (Func<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>> expression);

Looks pretty complex, but basically it's just a implicit substitution of your model and expression result. Your model is of type IEnumerable<KeyValuePair<string, ISearchProvider>>. That's also the type for the input of your lampda-expression. The result is of type IEnumerable<ISiteMatch>. And here come's the important thing!

The DisplayFor implementation checks, if the result type is enumerable or not. If not, it searches for a fitting template for the type, otherwise it will iterate through the elements and does this for all elements. 1

Searching for a template works based on the type name. In your case the template uses the name of the enumerated type, which is ISearchMatch. It does not find any display template, so it simply dumps the properties, resulting in what you see:

11147166811481897189813271028

To fix this problem, you need to convert your result set to the correct type first. You can do this in different ways. Either you cast the whole result of your provider results:

var results = provider.Value.Results
    .Cast<SiteSearchMatch>()
    .Take(10);

or you cast them individually within your lamda expression:

@Html.DisplayFor(x => (SiteSearchMatch)results)

The important thing is, that the scalar result type is the same as the model in your display template.

1 Note that this is a little bit more complex, for example the the extension also keeps track of an index and applys it to the output, so that the model could be bound for postback purposes.