Using @Html.DisplayNameFor() with PagedList
Asked Answered
F

5

48

I've been trying out the PagedList package to get paging for my index views. Everything was going well, and at the controller level everything is working fine, it only displays 5 records per page, and displays the appropriate page based on the querystring.

My problem is in the view. I changed the @Model to PagedList.IPagedList so I could access the Model.HasNextPage and other properties, but now the @Html.DisplayNameFor(model => model.ItemName) are no longer working. I get this error:

PagedList.IPagedList<Dossier.Models.Item>' does not contain a definition for 'ItemName' and no extension method 'ItemName' accepting a first argument of type 'PagedList.IPagedList<Dossier.Models.Item>' could be found (are you missing a using directive or an assembly reference?)

Here are the relevant parts of the view:

@model PagedList.IPagedList<Dossier.Models.Item>
@using Dossier.Models.Item

...

<th>
    @Html.DisplayNameFor(model => model.ItemName)
</th>

It seems IPagedList is not compatible with DisplayNameFor(). Any idea why this is happening, and how I could fix it? I know I could just manually enter the column names, but I'd like for that information to stay (and be changeable) in the model later.

Fleetwood answered 18/2, 2013 at 4:4 Comment(1)
Why not use the ViewBag to carry your data and leave the @model as it was originally?Helminthology
N
85

You can try this

@Html.DisplayNameFor(model => model.FirstOrDefault().ItemName)
Naiad answered 27/2, 2013 at 1:35 Comment(2)
Using First() will throw an InvalidOperationException when there are no elements in the sequence. Using FirstOrDefault() avoids this exception. Or you could use a FirstOrDefault 'dummy element', as suggested in this duplicate question: https://mcmap.net/q/357070/-calling-ienumerable-overload-of-displaynamefor-duplicateJeromejeromy
@viniciusdeLemos: that's fixed my problem too :)Privily
B
43

As an alternate solution to the accepted answer, remember that IPagedList inherits from IEnumerable. That means that you could write:

@model IEnumerable<Dossier.Models.Item>

At the beginning of the page, and just cast the model to IPagedList when needed:

@Html.PagedListPager((IPagedList)Model, page => Url.Action("Index", new { page = page }))

You can even declare the casted variable in the header, in order to use it multiple times within the page:

@{
    ViewBag.Title = "My page title";
    var pagedlist = (IPagedList)Model;
}

This would allow you to use the DisplayNameFor helper method, and access all PagedList methods/properties, without the need for dummy elements nor calling .FirstOrDefault() for each field.

Bluebottle answered 30/6, 2014 at 11:9 Comment(3)
One year later and this solution still works. Of all the answers presented, this one was the most helpful for me because (a) it worked, and (b) it's short and simple.Felodese
This feels like the cleanest solution.Wb
This should be the answer. Great!Annabellannabella
R
4

I solved the problem by creating an overload of DisplayNameFor that accepts a IPagedList<TModel>.

namespace PagedList.Mvc
{
    public static class Extensions
    {

        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
        public static MvcHtmlString DisplayNameFor<TModel, TValue>(this HtmlHelper<IPagedList<TModel>> html, Expression<Func<TModel, TValue>> expression)
        {
            return DisplayNameForInternal(html, expression);
        }

        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification = "This is an extension method")]
        internal static MvcHtmlString DisplayNameForInternal<TModel, TValue>(this HtmlHelper<IPagedList<TModel>> html, Expression<Func<TModel, TValue>> expression)
        {
            return DisplayNameHelper(ModelMetadata.FromLambdaExpression(expression, new ViewDataDictionary<TModel>()),
                                     ExpressionHelper.GetExpressionText(expression));
        }

        internal static MvcHtmlString DisplayNameHelper(ModelMetadata metadata, string htmlFieldName)
        {
            string resolvedDisplayName = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();

            return new MvcHtmlString(HttpUtility.HtmlEncode(resolvedDisplayName));
        }
    }
}

I'll be sending a pull request to PageList project to include it into the project for everyone.

Ribald answered 10/2, 2015 at 14:43 Comment(0)
S
1

You do not need to change @Html.DisplayNameFor. Declare model in the view as:

@model IEnumerable<Dossier.Models.Item>

Just move your pager to partial view (lets name it "_Pager"):

@model IPagedList

...

@Html.PagedListPager(Model, 
   page => Url.Action("Index", new { page, pageSize = Model.PageSize }))

...

Render the pager in your view:

@Html.Partial("_Pager", Model)

Thats it.

P.S. You can create Html helper instead of partial view...

Secessionist answered 25/9, 2015 at 12:49 Comment(0)
C
0

As an alternate solution you could try:

@Html.DisplayNameFor(x => x.GetEnumerator().Current.ItemName)

It will work even if the list is empty!

Cocklebur answered 5/5, 2020 at 21:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.