Samouczek: dodawanie sortowania, filtrowania i stronicowania za pomocą platformy Entity Framework w aplikacji MVC ASP.NET

W poprzednim samouczku zaimplementowano zestaw stron internetowych dla podstawowych operacji CRUD dla Student jednostek. W tym samouczku dodasz funkcje sortowania, filtrowania i stronicowania do strony Indeks uczniów . Utworzysz również prostą stronę grupowania.

Na poniższej ilustracji przedstawiono wygląd strony po zakończeniu pracy. Nagłówki kolumn to linki, które użytkownik może kliknąć, aby posortować według tej kolumny. Kliknięcie nagłówka kolumny wielokrotnie przełącza się między rosnącą i malejącą kolejnością sortowania.

Students_Index_page_with_paging

W tym samouczku zostały wykonane następujące czynności:

  • Dodawanie łączy sortowania kolumn
  • Dodawanie pola wyszukiwania
  • Dodawanie stronicowania
  • Tworzenie strony Informacje

Wymagania wstępne

Aby dodać sortowanie do strony Indeks uczniówStudent, zmienisz Index metodę kontrolera i dodasz kod do Student widoku Indeks.

Dodawanie funkcji sortowania do metody Index

  • W pliku Controllers\StudentController.cs zastąp metodę Index następującym kodem:

    public ActionResult Index(string sortOrder)
    {
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
       var students = from s in db.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(students.ToList());
    }
    

Ten kod odbiera sortOrder parametr z ciągu zapytania w adresie URL. Wartość ciągu zapytania jest dostarczana przez ASP.NET MVC jako parametr metody akcji. Parametr jest ciągiem "Name" lub "Date", po którym opcjonalnie następuje podkreślenie i ciąg "desc", aby określić kolejność malejącą. Domyślna kolejność sortowania jest rosnąca.

Przy pierwszym żądaniu strony Indeks nie ma ciągu zapytania. Uczniowie są wyświetlani w kolejności rosnącej według LastName, która jest domyślnie określona w przypadku fall-through w instrukcji switch . Gdy użytkownik kliknie hiperlink nagłówka kolumny, w ciągu zapytania zostanie podana odpowiednia sortOrder wartość.

Dwie ViewBag zmienne są używane, aby widok mógł skonfigurować hiperlinki nagłówka kolumny z odpowiednimi wartościami ciągu zapytania:

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

Są toternarne instrukcje. Pierwszy określa, że jeśli sortOrder parametr ma wartość null lub jest pusty, ViewBag.NameSortParm powinien być ustawiony na wartość "name_desc"; w przeciwnym razie należy ustawić go na pusty ciąg. Te dwie instrukcje umożliwiają widokowi ustawianie hiperlinków nagłówka kolumny w następujący sposób:

Bieżąca kolejność sortowania Hiperłącze nazwisko Hiperłącze daty
Nazwisko rosnąco descending ascending
Nazwisko malejące ascending ascending
Data rosnąca ascending descending
Data malejąco ascending ascending

Metoda używa LINQ to Entities, aby określić kolumnę do sortowania. Kod tworzy zmienną przed instrukcjąIQueryable<T>, modyfikuje ją w switch instrukcji i wywołuje metodę ToList po switch instrukcji .switch Podczas tworzenia i modyfikowania IQueryable zmiennych żadne zapytanie nie jest wysyłane do bazy danych. Zapytanie nie jest wykonywane, dopóki obiekt nie zostanie przekonwertowany IQueryable na kolekcję przez wywołanie metody, takiej jak ToList. W związku z tym ten kod powoduje wykonanie pojedynczego zapytania, które nie zostanie wykonane, dopóki instrukcja nie zostanie return View wykonana.

Alternatywą dla pisania różnych instrukcji LINQ dla każdej kolejności sortowania jest możliwość dynamicznego utworzenia instrukcji LINQ. Aby uzyskać informacje na temat dynamicznego LINQ, zobacz Dynamic LINQ (Dynamiczny LINQ).

  1. W pliku Views\Student\Index.cshtml zastąp <tr> elementy i <th> dla wiersza nagłówka wyróżnionym kodem:

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
            </th>
            <th>First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
    

    Ten kod używa informacji we właściwościach ViewBag do konfigurowania hiperlinków z odpowiednimi wartościami ciągu zapytania.

  2. Uruchom stronę i kliknij nagłówki kolumn Last Name (Nazwisko ) i Enrollment Date (Data rejestracji ), aby sprawdzić, czy sortowanie działa.

    Po kliknięciu nagłówka Nazwisko uczniowie są wyświetlani w kolejności malejącego nazwiska.

Aby dodać filtrowanie do strony indeksu Uczniowie, dodasz pole tekstowe i przycisk przesyłania do widoku i wprowadzisz odpowiednie zmiany w metodzie Index . Pole tekstowe umożliwia wprowadzenie ciągu do wyszukania w polach imię i nazwisko.

Dodawanie funkcji filtrowania do metody Index

  • W pliku Controllers\StudentController.cs zastąp metodę Index następującym kodem (zmiany są wyróżnione):

    public ViewResult Index(string sortOrder, string searchString)
    {
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
        var students = from s in db.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(students.ToList());
    }
    

Kod dodaje searchString parametr do Index metody . Wartość ciągu wyszukiwania jest odbierana z pola tekstowego dodanego do widoku Indeks. Dodaje również klauzulę where do instrukcji LINQ, która wybiera tylko uczniów, których imię lub nazwisko zawiera ciąg wyszukiwania. Instrukcja, która dodaje klauzulę Where , jest wykonywana tylko wtedy, gdy istnieje wartość do wyszukania.

Uwaga

W wielu przypadkach można wywołać tę samą metodę w zestawie jednostki Entity Framework lub jako metodę rozszerzenia w kolekcji w pamięci. Wyniki są zwykle takie same, ale w niektórych przypadkach mogą się różnić.

Na przykład implementacja Contains .NET Framework metody zwraca wszystkie wiersze po przekazaniu do niego pustego ciągu, ale dostawca platformy Entity Framework dla SQL Server Compact 4.0 zwraca zero wierszy dla pustych ciągów. W związku z tym kod w przykładzie (umieszczenie Where instrukcji wewnątrz if instrukcji) zapewnia, że uzyskasz te same wyniki dla wszystkich wersji SQL Server. Ponadto implementacja Contains .NET Framework metody domyślnie wykonuje porównanie uwzględniające wielkość liter, ale dostawcy platformy Entity Framework SQL Server domyślnie wykonują porównania bez uwzględniania wielkości liter. Dlatego wywołanie ToUpper metody w celu jawnego uwzględniania wielkości liter w teście gwarantuje, że wyniki nie zmieniają się po zmianie kodu później w celu użycia repozytorium, które zwróci IEnumerable kolekcję zamiast IQueryable obiektu. (Po wywołaniu Contains metody w IEnumerable kolekcji uzyskujesz implementację .NET Framework; podczas wywoływania jej w IQueryable obiekcie uzyskuje się implementację dostawcy bazy danych).

Obsługa wartości null może być również inna dla różnych dostawców bazy danych lub w przypadku użycia IQueryable obiektu w porównaniu z użyciem IEnumerable kolekcji. Na przykład w niektórych scenariuszach Where warunek, taki jak table.Column != 0 może nie zwracać kolumn, które mają null jako wartość. Domyślnie program EF generuje dodatkowe operatory SQL, aby zapewnić równość między wartościami null w bazie danych, tak jak działa w pamięci, ale można ustawić flagę UseDatabaseNullSemantics w ef6 lub wywołać metodę UseRelationalNulls w programie EF Core w celu skonfigurowania tego zachowania.

Dodawanie pola wyszukiwania do widoku indeksu ucznia

  1. W pliku Views\Student\Index.cshtml dodaj wyróżniony kod bezpośrednio przed tagiem otwieraniatable, aby utworzyć podpis, pole tekstowe i przycisk Wyszukaj.

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    
    @using (Html.BeginForm())
    {
        <p>
            Find by name: @Html.TextBox("SearchString")  
            <input type="submit" value="Search" /></p>
    }
    
    <table>
        <tr>
    
  2. Uruchom stronę, wprowadź ciąg wyszukiwania, a następnie kliknij przycisk Wyszukaj , aby sprawdzić, czy filtrowanie działa.

    Zwróć uwagę, że adres URL nie zawiera ciągu wyszukiwania "an", co oznacza, że w przypadku zakładki tej strony nie otrzymasz filtrowanej listy podczas korzystania z zakładki. Dotyczy to również linków sortowania kolumn, ponieważ będą sortować całą listę. W dalszej części samouczka zmienisz przycisk Wyszukaj , aby użyć ciągów zapytania do kryteriów filtrowania.

Dodawanie stronicowania

Aby dodać stronicowanie do strony indeksu Uczniowie, zaczniesz od zainstalowania pakietu NuGet PagedList.Mvc . Następnie wprowadzisz dodatkowe zmiany w metodzie Index i dodasz linki stronicowania do Index widoku. PagedList.Mvc jest jednym z wielu dobrych pakietów stronicowania i sortowania dla ASP.NET MVC, a jego użycie jest przeznaczone tylko jako przykład, a nie jako zalecenie dla niego w przypadku innych opcji.

Instalowanie pakietu NuGet PagedList.MVC

Pakiet NuGet PagedList.Mvc automatycznie instaluje pakiet PagedList jako zależność. Pakiet PagedList instaluje PagedList metody typu kolekcji i rozszerzenia dla IQueryable kolekcji i IEnumerable . Metody rozszerzenia tworzą pojedynczą stronę danych w PagedList kolekcji z elementu IQueryable lub IEnumerable, a PagedList kolekcja udostępnia kilka właściwości i metod ułatwiających stronicowanie. Pakiet PagedList.Mvc instaluje pomocnika stronicowania, który wyświetla przyciski stronicowania.

  1. W menu Narzędzia wybierz pozycję Menedżer pakietów NuGet , a następnie pozycję Konsola menedżera pakietów.

  2. W oknie Konsola menedżera pakietów upewnij się, że źródło pakietu jest nuget.org , a domyślnym projektem jest ContosoUniversity, a następnie wprowadź następujące polecenie:

    Install-Package PagedList.Mvc
    
  3. Skompiluj projekt.

Uwaga

Pakiet PageList nie jest już utrzymywany. Dlatego w przypadku bieżących projektów lepiej jest użyć pakietu X.PagedList . Główną różnicą jest to, że X.PagedList jest przenośnym zestawem. Oznacza to, że pakiet jest międzyplatformowy i może być używany w projektach internetowych, a także w innych projektach platformy .NET. Nowy pakiet nie powinien powodować problemów ze zgodnością, ponieważ został on przekierowany do platformy .NET 6 od wersji 8.4.

Dodawanie funkcji stronicowania do metody Index

  1. W pliku Controllers\StudentController.cs dodaj instrukcję using dla PagedList przestrzeni nazw:

    using PagedList;
    
  2. Zastąp metodę Index poniższym kodem:

    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";
    
       if (searchString != null)
       {
          page = 1;
       }
       else
       {
          searchString = currentFilter;
       }
    
       ViewBag.CurrentFilter = searchString;
    
       var students = from s in db.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:  // Name ascending 
             students = students.OrderBy(s => s.LastName);
             break;
       }
    
       int pageSize = 3;
       int pageNumber = (page ?? 1);
       return View(students.ToPagedList(pageNumber, pageSize));
    }
    

    Ten kod dodaje page parametr, bieżący parametr kolejności sortowania i bieżący parametr filtru do podpisu metody:

    public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    

    Przy pierwszym wyświetleniu strony lub jeśli użytkownik nie kliknął łącza stronicowania lub sortowania, wszystkie parametry mają wartość null. Po kliknięciu linku stronicowania zmienna page zawiera numer strony do wyświetlenia.

    Właściwość ViewBag udostępnia widok z bieżącą kolejnością sortowania, ponieważ musi ona być uwzględniona w linkach stronicowania, aby zachować kolejność sortowania tak samo podczas stronicowania:

    ViewBag.CurrentSort = sortOrder;
    

    Inna właściwość , ViewBag.CurrentFilterudostępnia widok z bieżącym ciągiem filtru. Ta wartość musi być uwzględniona w linkach stronicowania, aby zachować ustawienia filtru podczas stronicowania i należy ją przywrócić do pola tekstowego, gdy strona zostanie ponownie odtworzona. Jeśli ciąg wyszukiwania zostanie zmieniony podczas stronicowania, strona musi zostać zresetowana do wartości 1, ponieważ nowy filtr może spowodować wyświetlenie różnych danych. Ciąg wyszukiwania jest zmieniany po wprowadzeniu wartości w polu tekstowym i naciśnięciu przycisku przesyłania. W takim przypadku searchString parametr nie ma wartości null.

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

    Na końcu metody ToPagedList metoda rozszerzenia obiektu students IQueryable konwertuje zapytanie ucznia na jedną stronę uczniów w typie kolekcji, który obsługuje stronicowanie. Ta pojedyncza strona uczniów jest następnie przekazywana do widoku:

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
    

    Metoda ToPagedList przyjmuje numer strony. Dwa znaki zapytania reprezentują operator łączenia wartości null. Operator łączenia wartości null definiuje wartość domyślną dla typu dopuszczającego wartość null; wyrażenie (page ?? 1) oznacza, że zwraca wartość page , jeśli ma wartość, lub zwraca wartość 1, jeśli page ma wartość null.

  1. W pliku Views\Student\Index.cshtml zastąp istniejący kod następującym kodem. Zmiany są wyróżnione.

    @model PagedList.IPagedList<ContosoUniversity.Models.Student>
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewBag.Title = "Students";
    }
    
    <h2>Students</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
            <input type="submit" value="Search" />
        </p>
    }
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th>
                First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th></th>
        </tr>
    
    @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>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
    
    </table>
    <br />
    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 }))
    

    Instrukcja @model w górnej części strony określa, że widok pobiera PagedList teraz obiekt zamiast List obiektu.

    Instrukcja using dla PagedList.Mvc elementu zapewnia dostęp do pomocnika MVC dla przycisków stronicowania.

    Kod używa przeciążenia elementu BeginForm , które umożliwia określenie metody FormMethod.Get.

    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  
            <input type="submit" value="Search" />
        </p>
    }
    

    Domyślny formularz BeginForm przesyła dane formularza za pomocą funkcji POST, co oznacza, że parametry są przekazywane w treści komunikatu HTTP, a nie w adresie URL jako ciągi zapytania. Po określeniu żądania HTTP GET dane formularza są przekazywane w adresie URL jako ciągi zapytania, co umożliwia użytkownikom dodawanie zakładek adresu URL. Wytyczne dotyczące W3C dotyczące korzystania z żądania HTTP GET zaleca się użycie polecenia GET, gdy akcja nie spowoduje aktualizacji.

    Pole tekstowe jest inicjowane przy użyciu bieżącego ciągu wyszukiwania, więc po kliknięciu nowej strony można zobaczyć bieżący ciąg wyszukiwania.

    Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
    

    Linki nagłówka kolumny używają ciągu zapytania, aby przekazać bieżący ciąg wyszukiwania do kontrolera, aby użytkownik mógł sortować wyniki filtru:

    @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
    

    Wyświetlana jest bieżąca strona i łączna liczba stron.

    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    

    Jeśli nie ma stron do wyświetlenia, zostanie wyświetlona wartość "Strona 0 z 0". (W takim przypadku numer strony jest większy niż liczba stron, ponieważ Model.PageNumber wynosi 1 i Model.PageCount wynosi 0).

    Przyciski stronicowania są wyświetlane przez PagedListPager pomocnika:

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

    Pomocnik PagedListPager udostępnia szereg opcji, które można dostosować, w tym adresy URL i style. Aby uzyskać więcej informacji, zobacz TroyGoode / PagedList w witrynie GitHub.

  2. Uruchom stronę.

    Kliknij łącza stronicowania w różnych kolejności sortowania, aby upewnić się, że stronicowanie działa. Następnie wprowadź ciąg wyszukiwania i spróbuj ponownie stronicować, aby sprawdzić, czy stronicowanie działa również poprawnie z sortowaniem i filtrowaniem.

Tworzenie strony Informacje

Na stronie Informacje o witrynie internetowej Contoso University zobaczysz, ilu studentów zarejestrowało się dla każdej daty rejestracji. Wymaga to grupowania i prostych obliczeń w grupach. W tym celu należy wykonać następujące czynności:

  • Utwórz klasę modelu widoku dla danych, które należy przekazać do widoku.
  • Zmodyfikuj metodę About w kontrolerze Home .
  • Zmodyfikuj About widok.

Tworzenie modelu widoku

Utwórz folder ViewModels w folderze projektu. W tym folderze dodaj plik klasy EnrollmentDateGroup.cs i zastąp kod szablonu następującym kodem:

using System;
using System.ComponentModel.DataAnnotations;

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

        public int StudentCount { get; set; }
    }
}

Modyfikowanie kontrolera głównego

  1. W pliku HomeController.cs dodaj następujące using instrukcje w górnej części pliku:

    using ContosoUniversity.DAL;
    using ContosoUniversity.ViewModels;
    
  2. Dodaj zmienną klasy dla kontekstu bazy danych natychmiast po otwarciu nawiasu klamrowego dla klasy:

    public class HomeController : Controller
    {
        private SchoolContext db = new SchoolContext();
    
  3. Zastąp metodę About poniższym kodem:

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

    Instrukcja LINQ grupuje jednostki uczniów według daty rejestracji, oblicza liczbę jednostek w każdej grupie i przechowuje wyniki w kolekcji EnrollmentDateGroup obiektów modelu widoku.

  4. Dodaj metodę Dispose :

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
    

Modyfikowanie widoku Informacje

  1. Zastąp kod w pliku Views\Home\About.cshtml następującym kodem:

    @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
               
    @{
        ViewBag.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>
    
  2. Uruchom aplikację i kliknij link Informacje .

    Liczba uczniów dla każdej daty rejestracji jest wyświetlana w tabeli.

    About_page

Uzyskiwanie kodu

Pobieranie ukończonego projektu

Dodatkowe zasoby

Linki do innych zasobów programu Entity Framework można znaleźć w ASP.NET Dostęp do danych — zalecane zasoby.

Następne kroki

W tym samouczku zostały wykonane następujące czynności:

  • Dodawanie łączy sortowania kolumn
  • Dodawanie pola wyszukiwania
  • Dodawanie stronicowania
  • Tworzenie strony Informacje

Przejdź do następnego artykułu, aby dowiedzieć się, jak używać odporności połączenia i przechwytywania poleceń.