Question

I am using ViewBag to help me sort a list of students found within a list of classes. I have read that ViewBag is something that should be avoided at all costs attempting to build a proper MVC project.

When viewing the page that the below code generates, one is able to sort through a list of students in a variety of ways (First Name by alpha, Last name by alpha, date enrolled, etc), and view only a limited number of students per page.

I do not know exactly how to translate my code to use a ViewModel in place of my current design.

I am using the following code:

Model (Student):

public class Student
{
    public int StudentID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public string Email { get; set; }
    public DateTime EnrollmentDate { get; set; } 
    public virtual ICollection<Enrollment> Enrollments { get; set; }
}

Model (Enrollment):

public class Enrollment
{
    public int EnrollmentID { get; set; }
    public int CourseID { get; set; }
    public int StudentID { get; set; }
    public string Grade { get; set; } // pass, fail, incomplete
    public virtual Course Course { get; set; } 
    public virtual Student Student { get; set; } 
}

(I also have a Course model, but it is not referenced directly by the controller below, so I will omit it here - if it is necessary to show its details, please let me know.)

Controller:

public class StudentController : Controller
{
    private SchoolContext db = new SchoolContext();

    //
    // GET: /Student/

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
        ViewBag.CurrentSort = sortOrder;
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date";
        ViewBag.FNameSortParm = sortOrder == "FName" ? "FName desc" : "FName";
        ViewBag.EmailSortParm = sortOrder == "Email" ? "Email desc" : "Email";

        if (Request.HttpMethod == "GET")
        {
            searchString = currentFilter;
        }
        else
        {
            page = 1;
        }
        ViewBag.CurrentFilter = searchString;

        var students = from s in db.Students
                       select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                                   || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
        }
        switch (sortOrder)
        {
            case "Name desc":
                students = students.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                students = students.OrderBy(s => s.EnrollmentDate);
                break;
            case "Date desc":
                students = students.OrderByDescending(s => s.EnrollmentDate);
                break;
            case "FName":
                students = students.OrderBy(s => s.FirstMidName);
                break;
            case "FName desc":
                students = students.OrderByDescending(s => s.FirstMidName);
                break;
            case "Email":
                students = students.OrderBy(s => s.Email);
                break;
            case "Email desc":
                students = students.OrderByDescending(s => s.Email);
                break;
            default:
                students = students.OrderBy(s => s.LastName);
                break;
        }
        int pageSize = 4;
        int pageNumber = (page ?? 1);
        return View(students.ToPagedList(pageNumber, pageSize));
    }

And my view:

@model PagedList.IPagedList<MVCAppName.Models.Student>

@{
    ViewBag.Title = "Students";
}

<h2>Students</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
@using (Html.BeginForm())
{
    <p>
        Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string) &nbsp;
        <input type="submit" value="Search" /></p>
}
<table>
    <tr>
    <th></th>
    <th>
        @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
    <th>
        @Html.ActionLink("First Name", "Index", new { sortOrder = ViewBag.FNameSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
    <th>
        @Html.ActionLink("Email", "Index", new { sortOrder = ViewBag.EmailSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
    <th>
        @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter = ViewBag.CurrentFilter })
    </th>
</tr>

@foreach (var item in Model) {
    <tr>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) |
            @Html.ActionLink("Details", "Details", new { id=item.StudentID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.StudentID })
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FirstMidName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Email)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.EnrollmentDate)
        </td>
    </tr>
}

</table>

<div>
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber)
    of @Model.PageCount
    &nbsp;
    @if (Model.HasPreviousPage)
    {
        @Html.ActionLink("<<", "Index", new { page = 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter  })
        @Html.Raw("&nbsp;");
        @Html.ActionLink("< Prev", "Index", new { page = Model.PageNumber - 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter  })
    }
    else
    {
        @:<<
        @Html.Raw("&nbsp;");
        @:< Prev
    }
    &nbsp;
    @if (Model.HasNextPage)
    {
        @Html.ActionLink("Next >", "Index", new { page = Model.PageNumber + 1, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter  })
        @Html.Raw("&nbsp;");
        @Html.ActionLink(">>", "Index", new { page = Model.PageCount, sortOrder = ViewBag.CurrentSort, currentFilter=ViewBag.CurrentFilter  })
    }
    else
    {
        @:Next >
        @Html.Raw("&nbsp;")
        @:>>
    }
</div>
Was it helpful?

Solution

The controller should handle the sorting, the view just displays the data. You already do that, so you need only to define the view model, which preivosuly you'd put in the ViewBag

public class ShowStudentsModel
{
    public string CurrentSort {get;set;}
    public string NameSortParm {get;set;}
      //and so on... you create a property for each property set in the ViewBag
    public IEnumerable<Student> Students {get;set;}
}

Then in the view

@model ShowStudentsModel

@foreach(var item in Model.Students)
{
      //html code
  }

OTHER TIPS

I think the nicest would be to subclass the PagedList.IPagedList<T> which you are using and add sort order there. So at the end of your controller you'd have this:

    return View(students.ToPagedList(pageNumber, pageSize, sortOrder));

But if you're not willing to do that, then you could simply create a new ViewModel class to hold the PagedList (your current model) as well as the supplemental data you need (i.e. your sort order).

    return View(new SortedStudents
    {
        Students = students.ToPagedList(pageNumber, pageSize);
        SortOrder = sortOrder
    });

With the SortedStudents defined like this:

public class SortedStudents
{
    public PagedList.IPagedList<MVCAppName.Models.Student> Students { get; set; }
    public string SortOrder { get; set; }
}

You could make a wrapper around your Students class

public class StudentWrapper
{

List<Students> studentList { get; set; }
String currentSort { get; set; }

public StudentWrapper() {

studentlist = new List<Students>();

}

In your controller, you would create a new StudentWrapper

StudentWrapper sw = new StudentWrapper();

and set the list of students:

sw.studentList = db.Students.ToList();

and the sortOrder

sw.currentSort = SortOder;

You pass this model to your View

return View(sw);

in your View, you would use the StudentWrapper

@model List<MVCAppName.Models.StudentWrapper>

I dont know how your paging works, so you would have to figure this out.

But i dont see any problems in using the ViewBag either.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top