Search code examples
linqasp.net-coremodel-view-controller

MVC Core pass LINQ object to iPagedList


I am trying to pass a select new {} to a view using the following:

var results = 
    (from n in _context.Nodes
    join o in _context.Organizations on n.OrgId equals o.Id
    join t in _context.Organizations on n.ContractingOrgId equals t.Id

    select new
    {
        Id = n.Id,
        Name = n.Name,
        assigned_org = o.ShortName,
        con_org_short_name = t.ShortName,
        expiry = n.ExpireDate,
        lastActive = n.ActiveDate
    });
            
if (!String.IsNullOrEmpty(searchString))
{
    results = results.Where(s => s.con_org_short_name.Contains(searchString)
                            || s.Id.ToString().StartsWith(searchString));
}

switch (sortOrder)
{
    case "Client_desc":
        results = results.OrderByDescending(s => s.con_org_short_name);
        break;
    case "Client":
        results = results.OrderBy(s => s.con_org_short_name);
        break;
    case "Assigned":
        results = results.OrderBy(s => s.assigned_org);
        break;
    case "Assigned_desc":
        results = results.OrderByDescending(s => s.assigned_org);
        break;
    case "Expires":
        results = results.OrderBy(s => s.expiry);
        break;
    case "Expires_desc":
        results = results.OrderByDescending(s => s.expiry);
        break;
    case "LastActive":
        results = results.OrderBy(s => s.lastActive);
        break;
    case "LastActive_desc":
        results = results.OrderByDescending(s => s.lastActive);
        break;
    case "Id_desc":
        results = results.OrderByDescending(s => s.Id);
        break;
    default:
        results = results.OrderBy(s => s.Id);
        break;
};
int pageSize = 12;
int pageNumber = (page ?? 1);
return View(await results.ToPagedListAsync(pageNumber, pageSize));

This throws an error at return View()

"InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'X.PagedList.StaticPagedList1[<>f__AnonymousType26[System.Int32,System.String,System.String,System.String,System.Nullable1[System.DateTime],System.Nullable1[System.DateTime]]]', but this ViewDataDictionary instance requires a model item of type 'X.PagedList.IPagedList`1[AdminPortal.Models.Node]'.

And I get a build error on these lines:

if (!String.IsNullOrEmpty(searchString))
{
    results = results.Where(s => s.con_org_short_name.Contains(searchString)
                            || s.Id.ToString().StartsWith(searchString));
}

In the ienumerable visualiser on 'results' the object is shown in the first column as an anonymous type and it looks like an array but it has the expected output.

The question is, how to I access just that object and pass it to the return View() in the correct type?

This is the view to which the results are supposed to be passed:

@model X.PagedList.IPagedList<AdminPortal.Models.Node>
@using X.PagedList;
@using X.PagedList.Mvc.Core;
<link href="~/css/PagedList.css" rel="stylesheet" type="text/css" />

@{
    ViewData["Title"] = "Index";
}

<div class="row pt-4">
    <div class=" col-12 text-center">
        <h2 class="text-white">Node List</h2>
    </div>
</div>

<div class="container">
    <div class=" row pt-4" style="padding-bottom:10px">
        <div class=" col-6">
            @using (Html.BeginForm("Index", "Nodes", FormMethod.Get))
            {
               <p>
                    Find by name/id: @Html.TextBox("SearchString",ViewBag.CurrentFilter as string, htmlAttributes: new  {style="background-color:#cef2eb"})
                    <input type="submit" value="Search" class=" btn btn-info" />
               </p>

                
            }
        </div>

        <div class=" col-6 text-end">
            <a asp-controller="Nodes" asp-action="Create" class=" btn btn-primary">
                <i class="fa-solid fa-circle-plus"></i> &nbsp; Create Node
            </a>
        </div>
    </div>
    <table class="table table-striped table-bordered dt-responsive nowrap" width="100%" cellspacing="0">
    <thead>
            <tr style="color:#126E75; background-color:lightcyan">
            <th class=col-sm-1 style="text-align:left">
                @Html.ActionLink("ID", "Index", new { sortOrder = ViewBag.IDSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th class=col-sm-2 style ="text-align:left">
                Node Name
            </th>
            <th class=col-sm-1 style="text-align:left">
                Mode
            </th>
            <th class=col-sm-2 style ="text-align:left">
                @Html.ActionLink("Assigned To", "Index", new { sortOrder = ViewBag.AssignedSortParm, currentFilter=ViewBag.CurrentFilter })
             </th>
            <th class=col-sm-2 style ="text-align:left">
                @Html.ActionLink("Contracting Org", "Index", new { sortOrder = ViewBag.ClientSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th class=col-sm-1 style ="text-align:left">
                @Html.ActionLink("Expires", "Index", new { sortOrder = ViewBag.ExpiresSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th class=col-sm-1 style ="text-align:left">
                    @Html.ActionLink("LastActive", "Index", new { sortOrder = ViewBag.LastActiveSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th scope="col" colspan="2" style=" width:10%; text-align:center">
                Action
            </th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr class="table-light">
            <td align="left">
                @Html.DisplayFor(modelItem => item.Id)
            </td>
            <td align="left">
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td align="left">
                @Html.DisplayFor(modelItem => item.IsStudy)
            </td>
            <td align="left">
                @Html.DisplayFor(modelItem => item.Org.ShortName)
            </td>
            <td align ="left">
                @Html.DisplayFor(modelItem => item.ContractingOrg.ShortName)
            </td>
            <td align="left">
                @Html.DisplayFor(modelItem => item.ExpireDate)
            </td>
            <td align="left">
                @Html.DisplayFor(modelItem => item.ActiveDate)
            </td>
            <td style="text-align: center">
                <a asp-action="Edit" asp-route-id="@item.Id">
                    <i class="fas fa-edit"></i>
                </a>
            </td>
            <td style="text-align: center">
                <a asp-action="Details" asp-route-id="@item.Id">
                    <i class="fa-solid fa-eye" style="color:blue"></i>
                </a>
            </td>
        </tr>
    }
    </tbody>
</table>
</div>
<div class=" col-12 text-end">
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount  @Html.PagedListPager(Model, page => Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
</div>


Solution

  • There is one solution for your problem.

    View Model

    NodeViewModel.CS
    
    public class NodeViewModel
    {
      public int Id {get;set;}
      public string Name {get;set;}
      public string assigned_org {get;set;}
      public string con_org_short_name {get;set;}
      public string expiry  {get;set;}
      public string lastActive  {get;set;}
    }
    

    Controller method:

    var results = 
        (from n in _context.Nodes
        join o in _context.Organizations on n.OrgId equals o.Id
        join t in _context.Organizations on n.ContractingOrgId equals t.Id
    
        select new NodeViewModel
        {
            Id = n.Id,
            Name = n.Name,
            assigned_org = o.ShortName,
            con_org_short_name = t.ShortName,
            expiry = n.ExpireDate,
            lastActive = n.ActiveDate
        });
        if (!String.IsNullOrEmpty(searchString))
    {
        results = results.Where(s => s.con_org_short_name.Contains(searchString)
                                || s.Id.ToString().StartsWith(searchString));
    }
    
    switch (sortOrder)
    {
        case "Client_desc":
            results = results.OrderByDescending(s => s.con_org_short_name);
            break;
        case "Client":
            results = results.OrderBy(s => s.con_org_short_name);
            break;
        case "Assigned":
            results = results.OrderBy(s => s.assigned_org);
            break;
        case "Assigned_desc":
            results = results.OrderByDescending(s => s.assigned_org);
            break;
        case "Expires":
            results = results.OrderBy(s => s.expiry);
            break;
        case "Expires_desc":
            results = results.OrderByDescending(s => s.expiry);
            break;
        case "LastActive":
            results = results.OrderBy(s => s.lastActive);
            break;
        case "LastActive_desc":
            results = results.OrderByDescending(s => s.lastActive);
            break;
        case "Id_desc":
            results = results.OrderByDescending(s => s.Id);
            break;
        default:
            results = results.OrderBy(s => s.Id);
            break;
    };
    int pageSize = 12;
    int pageNumber = (page ?? 1);
    return View(await results.ToPagedListAsync(pageNumber, pageSize));
    

    View

    @model X.PagedList.IPagedList<NodeViewModel>
    @using X.PagedList;
    @using X.PagedList.Mvc.Core;
    <link href="~/css/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewData["Title"] = "Index";
    }
    
    <div class="row pt-4">
        <div class=" col-12 text-center">
            <h2 class="text-white">Node List</h2>
        </div>
    </div>
    
    <div class="container">
        <div class=" row pt-4" style="padding-bottom:10px">
            <div class=" col-6">
                @using (Html.BeginForm("Index", "Nodes", FormMethod.Get))
                {
                   <p>
                        Find by name/id: @Html.TextBox("SearchString",ViewBag.CurrentFilter as string, htmlAttributes: new  {style="background-color:#cef2eb"})
                        <input type="submit" value="Search" class=" btn btn-info" />
                   </p>
    
                    
                }
            </div>
    
            <div class=" col-6 text-end">
                <a asp-controller="Nodes" asp-action="Create" class=" btn btn-primary">
                    <i class="fa-solid fa-circle-plus"></i> &nbsp; Create Node
                </a>
            </div>
        </div>
        <table class="table table-striped table-bordered dt-responsive nowrap" width="100%" cellspacing="0">
        <thead>
                <tr style="color:#126E75; background-color:lightcyan">
                <th class=col-sm-1 style="text-align:left">
                    @Html.ActionLink("ID", "Index", new { sortOrder = ViewBag.IDSortParm, currentFilter=ViewBag.CurrentFilter })
                </th>
                <th class=col-sm-2 style ="text-align:left">
                    Node Name
                </th>
                <th class=col-sm-1 style="text-align:left">
                    Mode
                </th>
                <th class=col-sm-2 style ="text-align:left">
                    @Html.ActionLink("Assigned To", "Index", new { sortOrder = ViewBag.AssignedSortParm, currentFilter=ViewBag.CurrentFilter })
                 </th>
                <th class=col-sm-2 style ="text-align:left">
                    @Html.ActionLink("Contracting Org", "Index", new { sortOrder = ViewBag.ClientSortParm, currentFilter=ViewBag.CurrentFilter })
                </th>
                <th class=col-sm-1 style ="text-align:left">
                    @Html.ActionLink("Expires", "Index", new { sortOrder = ViewBag.ExpiresSortParm, currentFilter=ViewBag.CurrentFilter })
                </th>
                <th class=col-sm-1 style ="text-align:left">
                        @Html.ActionLink("LastActive", "Index", new { sortOrder = ViewBag.LastActiveSortParm, currentFilter=ViewBag.CurrentFilter })
                </th>
                <th scope="col" colspan="2" style=" width:10%; text-align:center">
                    Action
                </th>
            </tr>
        </thead>
        <tbody>
    @foreach (var item in Model) {
            <tr class="table-light">
                <td align="left">
                    @Html.DisplayFor(modelItem => item.Id)
                </td>
                <td align="left">
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td align="left">
                    @Html.DisplayFor(modelItem => item.IsStudy)
                </td>
                <td align="left">
                    @Html.DisplayFor(modelItem => item.Org.ShortName)
                </td>
                <td align ="left">
                    @Html.DisplayFor(modelItem => item.ContractingOrg.ShortName)
                </td>
                <td align="left">
                    @Html.DisplayFor(modelItem => item.ExpireDate)
                </td>
                <td align="left">
                    @Html.DisplayFor(modelItem => item.ActiveDate)
                </td>
                <td style="text-align: center">
                    <a asp-action="Edit" asp-route-id="@item.Id">
                        <i class="fas fa-edit"></i>
                    </a>
                </td>
                <td style="text-align: center">
                    <a asp-action="Details" asp-route-id="@item.Id">
                        <i class="fa-solid fa-eye" style="color:blue"></i>
                    </a>
                </td>
            </tr>
        }
        </tbody>
    </table>
    </div>
    <div class=" col-12 text-end">
        Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount  @Html.PagedListPager(Model, page => Url.Action("Index", new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
    </div>
    

    after making all above changes your view will be working like charm