Esercitazione: Aggiungere ordinamento, filtro e paging - ASP.NET MVC con EF Core

Nell'esercitazione precedente è stato implementato un set di pagine Web per operazioni CRUD di base per le entità Student. In questa esercitazione si aggiungeranno le funzionalità di ordinamento, filtro e suddivisione in pagine alla pagina Student Index (Indice degli studenti). Verrà anche creata una pagina che esegue il raggruppamento semplice.

La figura seguente illustra l'aspetto della pagina al termine dell'operazione. Le intestazioni di colonna sono collegamenti su cui l'utente può fare clic per eseguire l'ordinamento in base alla colonna. Facendo clic più volte su un'intestazione di colonna è possibile passare dall'ordinamento crescente a quello decrescente e viceversa.

Students index page

In questa esercitazione:

  • Aggiungere collegamenti per l'ordinamento delle colonne
  • Aggiungere una casella di ricerca
  • Aggiungere la suddivisione in pagine per Student Index
  • Aggiungere la suddivisione in pagine al metodo Index
  • Aggiungere collegamenti per la suddivisione in pagine
  • Creare una pagina About

Prerequisiti

Per aggiungere l'ordinamento alla pagina Student Index (Indice degli studenti), sarà necessario modificare il metodo Index del controller Students e aggiungere codice alla visualizzazione Student Index (Indice degli studenti).

Aggiungere la funzionalità di ordinamento al metodo Index

In StudentsController.cssostituire il Index metodo con il codice seguente:

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in _context.Students
                   select s;
    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;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

Questo codice riceve un parametro sortOrder dalla stringa di query nell'URL. Il valore della stringa di query viene inviato da ASP.NET Core MVC come parametro al metodo di azione. Il parametro sarà una stringa "Name" o "Date", seguito facoltativamente da un carattere di sottolineatura e dalla stringa "desc" per specificare l'ordine decrescente. Per impostazione predefinita, l'ordinamento è crescente.

La prima volta che viene richiesta la pagina di indice, non è presente alcuna stringa di query. Gli studenti vengono visualizzati in ordine crescente in base al cognome, che è il valore predefinito determinato dal caso di fallthrough nell'istruzione switch. Quando l'utente fa clic sul collegamento ipertestuale di un'intestazione di colonna, nella stringa di query viene specificato il valore sortOrder appropriato.

I due elementi ViewData (NameSortParm e DateSortParm) vengono usati dalla visualizzazione per configurare i collegamenti ipertestuali dell'intestazione di colonna con i valori della stringa di query appropriata.

public async Task<IActionResult> Index(string sortOrder)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    var students = from s in _context.Students
                   select s;
    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;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

Si tratta di istruzioni ternarie. La prima specifica che, se il parametro sortOrder è Null o vuoto, NameSortParm deve essere impostato su "name_desc"; in caso contrario, deve essere impostato su una stringa vuota. Queste due istruzioni consentono alla visualizzazione di impostare i collegamenti ipertestuali dell'intestazione di colonna come indicato di seguito:

Ordinamento corrente Collegamento ipertestuale cognome Collegamento ipertestuale data
Cognome in ordine crescente decrescente ascending
Cognome in ordine decrescente ascending ascending
Data in ordine crescente ascending decrescente
Data in ordine decrescente ascending ascending

Il metodo usa LINQ to Entities per specificare la colonna in base alla quale eseguire l'ordinamento. Il codice crea una variabile IQueryable prima dell'istruzione switch, la modifica nell'istruzione switch e chiama il metodo ToListAsync dopo l'istruzione switch. Quando si creano e modificano variabili IQueryable, nessuna query viene inviata al database. La query non viene eseguita finché l'oggetto IQueryable non viene convertito in una raccolta chiamando un metodo, ad esempio ToListAsync. Questo codice genera pertanto una singola query che non viene eseguita fino all'istruzione return View.

Questo codice può essere reso dettagliato con un numero elevato di colonne. Nell'ultima esercitazione di questa serie viene illustrato come scrivere codice che consente di passare il nome della colonna OrderBy in una variabile di stringa.

Sostituire il codice in Views/Students/Index.cshtmlcon il codice seguente per aggiungere collegamenti ipertestuali all'intestazione di colonna. Le righe modificate sono evidenziate.

@model IEnumerable<ContosoUniversity.Models.Student>

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

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]">@Html.DisplayNameFor(model => model.LastName)</a>
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]">@Html.DisplayNameFor(model => model.EnrollmentDate)</a>
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Questo codice usa le informazioni contenute nelle proprietà ViewData per impostare i collegamenti ipertestuali con i valori della stringa di query appropriati.

Eseguire l'app, selezionare la scheda Students (Studenti) e fare clic sulle intestazioni di colonna Last Name (Cognome) e Enrollment Date (Data di iscrizione) per verificare che l'ordinamento funzioni correttamente.

Students index page in name order

Per aggiungere filtri alla pagina Student Index (Indice degli studenti), è necessario aggiungere alla visualizzazione una casella di testo e un pulsante di invio e apportare le modifiche corrispondenti nel metodo Index. La casella di testo consente di immettere una stringa per eseguire la ricerca nei campi di nome e cognome.

Aggiungere la funzionalità di filtro al metodo Index

In StudentsController.cssostituire il Index metodo con il codice seguente (le modifiche sono evidenziate).

public async Task<IActionResult> Index(string sortOrder, string searchString)
{
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";
    ViewData["CurrentFilter"] = searchString;

    var students = from s in _context.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                               || s.FirstMidName.Contains(searchString));
    }
    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;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }
    return View(await students.AsNoTracking().ToListAsync());
}

È stato aggiunto un parametro searchString al metodo Index. Il valore della stringa di ricerca viene ricevuto da una casella di testo che verrà aggiunta alla visualizzazione Index (Indice). È stata anche aggiunta all'istruzione LINQ una clausola where che seleziona solo gli studenti il cui nome o cognome contiene la stringa di ricerca. L'istruzione che aggiunge la clausola where viene eseguita solo se è presente un valore per la ricerca.

Nota

In questo esempio si chiama il metodo Where su un oggetto IQueryable e il filtro verrà elaborato nel server. In alcuni scenari potrebbe essere chiamato il metodo Where come metodo di estensione per una raccolta in memoria. Si supponga, ad esempio, di modificare il riferimento a in modo che anziché un'istanza di Entity Framework DbSet faccia riferimento a _context.Students un metodo del repository che restituisce una IEnumerable raccolta. Il risultato sarebbe normalmente lo stesso, ma in alcuni casi potrebbe essere diverso.

Ad esempio, l'implementazione di .NET Framework del metodo Contains esegue un confronto con la distinzione tra maiuscole e minuscole per impostazione predefinita, ma in SQL Server questo è determinato dall'impostazione delle regole di confronto dell'istanza di SQL Server. Questa impostazione usa come valore predefinito la non applicazione della distinzione tra maiuscole e minuscole. È possibile chiamare il ToUpper metodo per rendere il test senza distinzione tra maiuscole e minuscole in modo esplicito: Where(s => s.LastName.ToUpper(). Contains(searchString.ToUpper()). Questo fa sì che i risultati rimangano invariati se si modifica il codice in un secondo momento per usare un repository che restituisce una raccolta IEnumerable invece di un oggetto IQueryable. Quando si chiama il Contains metodo in una IEnumerable raccolta, si ottiene l'implementazione di .NET Framework. Quando viene chiamata su un IQueryable oggetto, si ottiene l'implementazione del provider di database. Tuttavia, si verifica una riduzione delle prestazioni per questa soluzione. Il codice ToUpper dovrà inserire una funzione nella clausola WHERE dell'istruzione TSQL SELECT. In questo modo si evita che l'ottimizzazione usi un indice. Dato che SQL viene installato per lo più con l'impostazione senza distinzione tra maiuscole e minuscole, è consigliabile evitare il codice ToUpper fino a quando non si esegue la migrazione a un archivio con distinzione tra maiuscole e minuscole.

Aggiungere una casella di ricerca alla visualizzazione Student Index (Indice degli studenti)

In Views/Student/Index.cshtmlaggiungere il codice evidenziato immediatamente prima del tag della tabella di apertura per creare un didascalia, una casella di testo e un pulsante Cerca.

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">

Questo codice usa l'helper tag<form> per aggiungere la casella di testo e il pulsante di ricerca. Per impostazione predefinita, l'helper tag <form> invia i dati del modulo con una richiesta POST, il che significa che i parametri vengono passati nel corpo del messaggio HTTP e non nell'URL come stringhe di query. Quando si specifica HTTP GET, i dati del modulo vengono passati nell'URL come stringhe di query, il che consente agli utenti di inserire l'URL tra i segnalibri. Le linee guida W3C consigliano di usare l'istruzione GET quando l'azione non risulta in un aggiornamento.

Eseguire l'app, selezionare la scheda Students (Studenti), immettere una stringa di ricerca e fare clic su Search (Ricerca) per verificare che il filtro funzioni.

Students index page with filtering

Si noti che l'URL contiene la stringa di ricerca.

http://localhost:5813/Students?SearchString=an

Se questa pagina viene inserita tra i segnalibri, quando si usa il segnalibro viene visualizzato l'elenco filtrato. L'aggiunta di method="get" al tag form è l'elemento che ha determinato la generazione della stringa di query.

In questa fase, se si fa clic sul collegamento di ordinamento di un'intestazione di colonna si perderà il valore del filtro immesso nella casella Search (Ricerca). Nella sezione successiva verrà spiegato come correggere il problema.

Aggiungere la suddivisione in pagine per Student Index

Per aggiungere la suddivisione in pagine alla pagina Student Index (Indice degli studenti), creare una classe PaginatedList che usa le istruzioni Skip e Take per filtrare i dati sul server invece di recuperare sempre tutte le righe della tabella. È quindi possibile apportare altre modifiche nel metodo Index e aggiungere i pulsanti di suddivisione in pagine alla visualizzazione Index. Nella figura seguente vengono illustrati i pulsanti di suddivisione in pagine.

Students index page with paging links

Nella cartella del progetto, creare PaginatedList.cs e quindi sostituire il codice del modello con il codice seguente.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity
{
    public class PaginatedList<T> : List<T>
    {
        public int PageIndex { get; private set; }
        public int TotalPages { get; private set; }

        public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
        {
            PageIndex = pageIndex;
            TotalPages = (int)Math.Ceiling(count / (double)pageSize);

            this.AddRange(items);
        }

        public bool HasPreviousPage => PageIndex > 1;

        public bool HasNextPage => PageIndex < TotalPages;

        public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
        {
            var count = await source.CountAsync();
            var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
            return new PaginatedList<T>(items, count, pageIndex, pageSize);
        }
    }
}

Il metodo CreateAsync in questo codice accetta le dimensioni di pagina e il numero delle pagine e applica le istruzioni Skip e Take appropriate a IQueryable. Quando ToListAsync viene chiamato su IQueryable, restituisce un elenco contenente solo la pagina richiesta. Le proprietà HasPreviousPage e HasNextPage possono essere usate per abilitare o disabilitare i pulsanti di suddivisione in pagine Previous (Indietro) e Next (Avanti).

Viene usato un metodo CreateAsync invece di un costruttore per creare l'oggetto PaginatedList<T> poiché i costruttori non possono eseguire codice asincrono.

Aggiungere la suddivisione in pagine al metodo Index

In StudentsController.cssostituire il Index metodo con il codice seguente.

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? pageNumber)
{
    ViewData["CurrentSort"] = sortOrder;
    ViewData["NameSortParm"] = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
    ViewData["DateSortParm"] = sortOrder == "Date" ? "date_desc" : "Date";

    if (searchString != null)
    {
        pageNumber = 1;
    }
    else
    {
        searchString = currentFilter;
    }

    ViewData["CurrentFilter"] = searchString;

    var students = from s in _context.Students
                   select s;
    if (!String.IsNullOrEmpty(searchString))
    {
        students = students.Where(s => s.LastName.Contains(searchString)
                               || s.FirstMidName.Contains(searchString));
    }
    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;
        default:
            students = students.OrderBy(s => s.LastName);
            break;
    }

    int pageSize = 3;
    return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
}

Questo codice aggiunge un parametro di numeri di pagina, un parametro di ordinamento corrente e un parametro di filtro corrente alla firma del metodo.

public async Task<IActionResult> Index(
    string sortOrder,
    string currentFilter,
    string searchString,
    int? pageNumber)

La prima volta che viene visualizzata la pagina o se l'utente non ha selezionato un collegamento di suddivisione in pagine o di ordinamento, tutti i parametri saranno Null. Se si seleziona un collegamento di suddivisione in pagine, la variabile di pagina conterrà il numero della pagina da visualizzare.

L'elemento ViewData denominato CurrentSort offre la visualizzazione con l'ordinamento corrente, in quanto esso deve essere incluso nei collegamenti di suddivisione in pagine per mantenere l'ordinamento durante la suddivisione in pagine.

L'elemento ViewData denominato CurrentFilter offre la visualizzazione con la stringa di filtro corrente. Questo valore deve essere incluso nei collegamenti di suddivisione in pagine per mantenere le impostazioni di filtro nella suddivisione in pagine e deve essere ripristinato nella casella di testo quando la pagina viene nuovamente visualizzata.

Se la stringa di ricerca viene modificata nella suddivisione in pagine, la pagina deve essere reimpostata su 1, poiché il nuovo filtro può comportare la visualizzazione di dati diversi. La stringa di ricerca viene modificata quando si immette un valore nella casella di testo e si preme il pulsante Invia. In tal caso, il parametro searchString non è Null.

if (searchString != null)
{
    pageNumber = 1;
}
else
{
    searchString = currentFilter;
}

Alla fine del metodo Index, il metodo PaginatedList.CreateAsync converte la query degli studenti in una pagina singola di studenti in un tipo di raccolta che supporta la suddivisione in pagine. La pagina singola di studenti viene quindi passata alla visualizzazione.

return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));

Il metodo PaginatedList.CreateAsync accetta un numero di pagina. I due punti interrogativi rappresentano l'operatore null-coalescing. L'operatore null-coalescing definisce un valore predefinito per un tipo nullable. L'espressione (pageNumber ?? 1) significa restituzione del valore di pageNumber se ha un valore oppure restituzione di 1 se pageNumber è Null.

In Views/Students/Index.cshtmlsostituire il codice esistente con il codice seguente. Le modifiche sono evidenziate.

@model PaginatedList<ContosoUniversity.Models.Student>

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

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>

<form asp-action="Index" method="get">
    <div class="form-actions no-color">
        <p>
            Find by name: <input type="text" name="SearchString" value="@ViewData["CurrentFilter"]" />
            <input type="submit" value="Search" class="btn btn-default" /> |
            <a asp-action="Index">Back to Full List</a>
        </p>
    </div>
</form>

<table class="table">
    <thead>
        <tr>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["NameSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Last Name</a>
            </th>
            <th>
                First Name
            </th>
            <th>
                <a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter="@ViewData["CurrentFilter"]">Enrollment Date</a>
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.LastName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.FirstMidName)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.EnrollmentDate)
                </td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                    <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                    <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
                </td>
            </tr>
        }
    </tbody>
</table>

@{
    var prevDisabled = !Model.HasPreviousPage ? "disabled" : "";
    var nextDisabled = !Model.HasNextPage ? "disabled" : "";
}

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled">
    Previous
</a>
<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex + 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @nextDisabled">
    Next
</a>

L'istruzione @model nella parte superiore della pagina specifica che la vista ottiene ora un oggetto PaginatedList<T> anziché un oggetto List<T>.

I collegamenti delle intestazioni di colonna usano la stringa di query per passare la stringa di ricerca corrente al controller in modo che l'utente possa procedere all'ordinamento all'interno dei risultati di filtro:

<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>

I pulsanti di suddivisione in pagine vengono visualizzati dagli helper tag:

<a asp-action="Index"
   asp-route-sortOrder="@ViewData["CurrentSort"]"
   asp-route-pageNumber="@(Model.PageIndex - 1)"
   asp-route-currentFilter="@ViewData["CurrentFilter"]"
   class="btn btn-default @prevDisabled">
   Previous
</a>

Eseguire l'app e passare alla pagina Students (Studenti).

Students index page with paging links

Fare clic sui collegamenti di suddivisione in pagine in diversi tipi di ordinamento per verificare che la suddivisione in pagine funzioni. Immettere quindi una stringa di ricerca e provare nuovamente la suddivisione in pagine per verificare che funzioni correttamente anche con l'ordinamento e il filtro.

Creare una pagina About

Per la pagina About (Informazioni) del sito Web Contoso University verrà visualizzato il numero di studenti iscritti per ogni data di iscrizione. Questa operazione richiede calcoli di raggruppamento e semplici sui gruppi. Per completare questa procedura, è necessario eseguire le operazioni seguenti:

  • Creare una classe modello di visualizzazione per i dati che è necessario passare alla visualizzazione.
  • Creare il metodo About nel Home controller.
  • Creare la visualizzazione About.

Creare il modello di visualizzazione

Creare una cartella SchoolViewModels nella cartella Models.

Nella nuova cartella aggiungere un file EnrollmentDateGroup.cs di classe e sostituire il codice del modello con il codice seguente:

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models.SchoolViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

Modificare il Home controller

In HomeController.csaggiungere le istruzioni using seguenti all'inizio del file:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;

Aggiungere una variabile di classe per il contesto del database immediatamente dopo la parentesi graffa di apertura per la classe e ottenere un'istanza del contesto da ASP.NET Core DI:

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;
    private readonly SchoolContext _context;

    public HomeController(ILogger<HomeController> logger, SchoolContext context)
    {
        _logger = logger;
        _context = context;
    }

Aggiungere un metodo About con il codice seguente:

public async Task<ActionResult> About()
{
    IQueryable<EnrollmentDateGroup> data = 
        from student in _context.Students
        group student by student.EnrollmentDate into dateGroup
        select new EnrollmentDateGroup()
        {
            EnrollmentDate = dateGroup.Key,
            StudentCount = dateGroup.Count()
        };
    return View(await data.AsNoTracking().ToListAsync());
}

L'istruzione LINQ raggruppa le entità di studenti per data di registrazione, calcola il numero di entità in ogni gruppo e archivia i risultati in una raccolta di oggetti di modello della visualizzazione EnrollmentDateGroup.

Creare la visualizzazione About

Aggiungere un Views/Home/About.cshtml file con il codice seguente:

@model IEnumerable<ContosoUniversity.Models.SchoolViewModels.EnrollmentDateGroup>

@{
    ViewData["Title"] = "Student Body Statistics";
}

<h2>Student Body Statistics</h2>

<table>
    <tr>
        <th>
            Enrollment Date
        </th>
        <th>
            Students
        </th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
</table>

Eseguire l'app e passare alla pagina About. Il numero di studenti per ogni data di registrazione viene visualizzato in una tabella.

Ottenere il codice

Scaricare o visualizzare l'applicazione completata.

Passaggi successivi

In questa esercitazione:

  • Aggiungere collegamenti per l'ordinamento delle colonne
  • Aggiungere una casella di ricerca
  • Aggiungere la suddivisione in pagine per Student Index
  • Aggiungere la suddivisione in pagine al metodo Index
  • Aggiungere collegamenti per la suddivisione in pagine
  • Creare una pagina About

Passare all'esercitazione successiva per apprendere come gestire le modifiche al modello di dati tramite le migrazioni.