Reputation: 1707
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 Id
s 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?
Upvotes: 0
Views: 183
Reputation: 1707
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".
Upvotes: 0
Reputation: 11606
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.
Upvotes: 1