简体   繁体   中英

Html.DisplayNameFor List vs IEnumerable in Razor

I am implementing the PaginatedList from the tutorial.

It works. My issue if that if I defined the @model as an IEnumerable in my Razor page I can do this:

@model IEnumerable<CustomerDisplayViewModel>
@Html.DisplayNameFor(model => model.LastName)

If I define @model as List , the @Html.DisplayNameFor helper works differently, and I have to call it like this:

@model List<CustomerDisplayViewModel>
@Html.DisplayNameFor(model => model.First().LastName)

Difference being that in the first call the expression cast model to be CustomerDisplayViewModel and in the second it is a List<CustomerDisplayViewModel> .

The issue is caused by the compiler prefering to cast the call to

Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<TModel> 
string DisplayNameFor<TResult>(Expression<Func<TModel, TResult>> expression);

Instead of

Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperDisplayNameExtensions
public static string DisplayNameFor<TModelItem, TResult>(
this IHtmlHelper<IEnumerable<TModelItem>> htmlHelper, Expression<Func<TModelItem, TResult>> expression);

I know I can use my workaround ( @Html.DisplayNameFor( model => model.First().LastName) ) but it doesn't feel correct.

Is there a way to cast the call or maybe generating my own extension that calls the IEnumerable extension (I would prefer to not create an extension from scratch, this call should work exactly as the IEnumerable). I can create the extension method with List<>, however, I haven't been able to cast it.

 public static string DisplayNameFor<TModelItem, TResult>
            (this Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper<List<TModelItem>> htmlHelper,
            Expression<Func<TModelItem, TResult>> expression)
        {
            var castHelper = --- somehow cast helper to IHtmlHelper<IEnumerable<TModelItem>
            return castHelper.DisplayNameFor(expression);
        }

Thanks.

This is happening because the generic parameter TModel of IHtmlHelper is not covariant . Basically, you cannot do this:

IHtmlHelper<List<CustomerDisplayViewModel>> helperList = new HtmlHelper<List<CustomerDisplayViewModel>(...);

IHtmlHelper<IEnumerable<CustomerDisplayViewModel>> helperIEnumerable = helperList;
// the above line is an error

However, you can do that with IEnumerable<T> :

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList; // no error

This is because IEnumerable is declared like this:

public interface IEnumerable<out T> : IEnumerable { .. }

Note the out keyword which specifies that the generic parameter T is covariant. Had IHtmlHelper<TModel> been declared like this in the framework:

interface IHtmlHelper<out TModel> { .. }

your code would have worked.


Despite this, you can still use Html.DisplayNameForInnerType() to get the display name(ASP.NET Core only) in such cases:

@model PaginatedList<CustomerDisplayViewModel>
...
@Html.DisplayNameForInnerType((CustomerDisplayViewModel c) => c.LastName)

Note that you have to explicitly specify the type of the lambda expression parameter.( CustomerDisplayViewModel c ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM