Kurz: Přidání řazení, filtrování a stránkování – ASP.NET MVC pomocí EF Core
V předchozím kurzu jste implementovali sadu webových stránek pro základní operace CRUD pro entity studentů. V tomto kurzu přidáte funkce řazení, filtrování a stránkování na stránku Index studentů. Vytvoříte také stránku, která provede jednoduché seskupení.
Následující obrázek ukazuje, jak bude stránka vypadat po dokončení. Záhlaví sloupců jsou odkazy, na které uživatel může kliknout a seřadit podle tohoto sloupce. Opakovaným kliknutím na záhlaví sloupce se přepíná mezi vzestupným a sestupným pořadím řazení.
V tomto kurzu se naučíte:
- Přidání odkazů pro řazení sloupců
- Přidání vyhledávacího pole
- Přidání stránkování do indexu Studentů
- Přidání stránkování do metody Index
- Přidání stránkovacího odkazu
- Vytvoření stránky O aplikaci
Požadavky
Přidání odkazů pro řazení sloupců
Pokud chcete přidat řazení na stránku Index studenta, změníte Index
metodu kontroleru Students a přidáte kód do zobrazení Index studenta.
Přidání funkce řazení do metody indexu
V StudentsController.cs
příkazu nahraďte metodu Index
následujícím kódem:
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());
}
Tento kód obdrží sortOrder
parametr z řetězce dotazu v adrese URL. Hodnotu řetězce dotazu poskytuje ASP.NET Core MVC jako parametr metody akce. Parametr bude řetězec, který je buď "Name" nebo "Date", volitelně následuje podtržítko a řetězec "desc" k určení sestupného pořadí. Výchozí pořadí řazení je vzestupné.
Při prvním vyžádání indexové stránky neexistuje žádný řetězec dotazu. Studenti se zobrazují ve vzestupném pořadí podle příjmení, což je výchozí nastavení podle písmen v switch
příkazu. Když uživatel klikne na hypertextový odkaz záhlaví sloupce, v řetězci dotazu se zobrazí příslušná sortOrder
hodnota.
ViewData
Dva prvky (NameSortParm a DateSortParm) se používají v zobrazení ke konfiguraci hypertextových odkazů záhlaví sloupců s příslušnými řetězcovými hodnotami dotazu.
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());
}
Jedná se o ternární příkazy. První určuje, že pokud sortOrder
je parametr null nebo prázdný, nameSortParm by měl být nastaven na "name_desc". Jinak by měl být nastaven na prázdný řetězec. Tyto dva příkazy umožňují zobrazení nastavit hypertextové odkazy záhlaví sloupců následujícím způsobem:
Aktuální pořadí řazení | Hypertextový odkaz na příjmení | Hypertextový odkaz na datum |
---|---|---|
Příjmení vzestupně | descending | ascending |
Příjmení sestupně | ascending | ascending |
Vzestupné datum | ascending | descending |
Datum sestupně | ascending | ascending |
Metoda používá LINQ to Entities k určení sloupce, podle který se má seřadit. Kód vytvoří proměnnou IQueryable
před příkazem switch, upraví ho v příkazu switch a zavolá ToListAsync
metodu za příkazem switch
. Při vytváření a úpravě IQueryable
proměnných se do databáze neposílají žádné dotazy. Dotaz se nespustí, dokud nepřeveďte IQueryable
objekt do kolekce voláním metody, jako ToListAsync
je . Výsledkem tohoto kódu je proto jeden dotaz, který se nespustí, dokud se return View
příkaz nespustí.
Tento kód by mohl získat podrobný počet sloupců. Poslední kurz v této sérii ukazuje, jak napsat kód, který umožňuje předat název OrderBy
sloupce v řetězcové proměnné.
Přidání hypertextových odkazů záhlaví sloupců do zobrazení Index studentů
Nahraďte kód v Views/Students/Index.cshtml
, následujícím kódem pro přidání hypertextových odkazů záhlaví sloupce. Změněné čáry jsou zvýrazněné.
@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>
Tento kód používá informace ve ViewData
vlastnostech k nastavení hypertextových odkazů s příslušnými hodnotami řetězce dotazu.
Spusťte aplikaci, vyberte kartu Studenti a kliknutím na záhlaví sloupců Příjmení a Datum registrace ověřte, že řazení funguje.
Přidání vyhledávacího pole
Pokud chcete přidat filtrování na stránku Index studentů, přidáte do zobrazení textové pole a tlačítko odeslat a provedete odpovídající změny v Index
metodě. Textové pole vám umožní zadat řetězec, který se má vyhledat v polích jméno a příjmení.
Přidání funkce filtrování do metody Index
V StudentsController.cs
příkazu nahraďte metodu Index
následujícím kódem (změny jsou zvýrazněny).
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());
}
Do metody jste přidali searchString
parametr Index
. Hodnota vyhledávacího řetězce se přijímá z textového pole, které přidáte do zobrazení indexu. Přidali jste také do příkazu LINQ klauzuli where, která vybere jenom studenty, jejichž křestní jméno nebo příjmení obsahuje hledaný řetězec. Příkaz, který přidá klauzuli where, se provede pouze v případě, že je hodnota, kterou chcete vyhledat.
Poznámka
Tady voláte metodu Where
IQueryable
na objektu a filtr se zpracuje na serveru. V některých scénářích můžete metodu Where
volat jako metodu rozšíření v kolekci v paměti. (Předpokládejme například, že změníte odkaz tak _context.Students
, aby místo EF DbSet
odkaz na metodu úložiště, která vrací kolekci IEnumerable
.) Výsledek by obvykle byl stejný, ale v některých případech se může lišit.
Například implementace Contains
rozhraní .NET Framework metody ve výchozím nastavení provádí porovnání s rozlišováním velkých a malých písmen, ale na SQL Serveru je to určeno nastavením kolace instance SYSTÉMU SQL Server. Toto nastavení ve výchozím nastavení nerozlišuje malá a velká písmena. Metodu ToUpper
můžete volat tak, aby test explicitně nerozlišil malá a velká písmena: Where(s>=s.LastName.ToUpper(). Contains(searchString.Toupper()) To zajistí, aby výsledky zůstaly stejné, pokud později změníte kód tak, aby používal úložiště, které vrací IEnumerable
kolekci místo objektu IQueryable
. (Při volání Contains
metody v IEnumerable
kolekci získáte implementaci rozhraní .NET Framework; když ji zavoláte na IQueryable
objektu, získáte implementaci zprostředkovatele databáze.) Pro toto řešení je ale snížení výkonu. Kód ToUpper
by vložil funkci do klauzule WHERE příkazu TSQL SELECT. Tím by se zabránilo použití indexu optimalizátoru. Vzhledem k tomu, že SQL se většinou instaluje bez rozlišování velkých a malých písmen, je nejlepší se ToUpper
vyhnout kódu, dokud nemigrujete do úložiště dat citlivých na malá a velká písmena.
Přidání vyhledávacího pole do zobrazení indexu studenta
Přidejte Views/Student/Index.cshtml
zvýrazněný kód bezprostředně před levou značku tabulky, abyste vytvořili popis, textové pole a tlačítko Hledat.
<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">
Tento kód používá pomocnou rutinu <form>
značky k přidání vyhledávacího textového pole a tlačítka. Ve výchozím nastavení <form>
pomocník značky odesílá data formuláře pomocí post, což znamená, že parametry se předávají v textu zprávy HTTP, a ne v adrese URL jako řetězce dotazu. Když zadáte HTTP GET, předají se data formuláře v adrese URL jako řetězce dotazu, což uživatelům umožňuje vytvořit záložku adresy URL. Pokyny W3C doporučují, abyste měli použít get, když akce nemá za následek aktualizaci.
Spusťte aplikaci, vyberte kartu Studenti , zadejte hledaný řetězec a kliknutím na Hledat ověřte, že filtrování funguje.
Všimněte si, že adresa URL obsahuje hledaný řetězec.
http://localhost:5813/Students?SearchString=an
Pokud si tuto stránku označíte záložkou, zobrazí se filtrovaný seznam při použití této záložky. Přidání method="get"
ke form
značce je to, co způsobilo vygenerování řetězce dotazu.
Pokud v této fázi kliknete na odkaz pro řazení záhlaví sloupce, ztratíte hodnotu filtru, kterou jste zadali do vyhledávacího pole. Opravíte to v další části.
Přidání stránkování do indexu Studentů
Pokud chcete přidat stránkování na stránku Index studentů, vytvoříte PaginatedList
třídu, která používá Skip
a Take
uvádí příkazy k filtrování dat na serveru místo toho, abyste vždy načítá všechny řádky tabulky. Pak provedete další změny v Index
metodě a přidáte do zobrazení stránkovací tlačítka Index
. Následující obrázek znázorňuje stránkovací tlačítka.
Ve složce projektu vytvořte PaginatedList.cs
a pak nahraďte kód šablony následujícím kódem.
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);
}
}
}
Metoda CreateAsync
v tomto kódu má velikost stránky a číslo stránky a použije příslušné Skip
příkazy a Take
příkazy na IQueryable
. Při ToListAsync
zavolání na této stránce IQueryable
vrátí seznam obsahující pouze požadovanou stránku. Vlastnosti HasPreviousPage
a HasNextPage
lze je použít k povolení nebo zakázání předchozí a další stránkovací tlačítka.
Metoda CreateAsync
se používá místo konstruktoru k vytvoření objektu PaginatedList<T>
, protože konstruktory nemohou spustit asynchronní kód.
Přidání stránkování do metody Index
V StudentsController.cs
, nahraďte metodu Index
následujícím kódem.
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));
}
Tento kód přidá do podpisu metody parametr čísla stránky, aktuální parametr pořadí řazení a aktuální parametr filtru.
public async Task<IActionResult> Index(
string sortOrder,
string currentFilter,
string searchString,
int? pageNumber)
Při prvním zobrazení stránky nebo pokud uživatel neklikli na stránkovací nebo seřazovací odkaz, budou mít všechny parametry hodnotu null. Pokud kliknete na stránkovací odkaz, proměnná stránky bude obsahovat číslo stránky, které se má zobrazit.
Element ViewData
s názvem CurrentSort poskytuje zobrazení s aktuálním pořadím řazení, protože to musí být součástí stránkovací odkazy, aby pořadí řazení bylo stejné při stránkování.
Element ViewData
s názvem CurrentFilter poskytuje zobrazení s aktuálním řetězcem filtru. Tato hodnota musí být zahrnuta ve stránkovacích odkazech, aby bylo možné zachovat nastavení filtru během stránkování, a při opětovném zobrazení stránky se musí obnovit do textového pole.
Pokud se během stránkování změní hledaný řetězec, musí se stránka resetovat na hodnotu 1, protože nový filtr může vést k zobrazení různých dat. Hledaný řetězec se změní při zadání hodnoty do textového pole a stisknete tlačítko Odeslat. V takovém případě searchString
parametr nemá hodnotu null.
if (searchString != null)
{
pageNumber = 1;
}
else
{
searchString = currentFilter;
}
Na konci Index
metody PaginatedList.CreateAsync
metoda převede dotaz studenta na jednu stránku studentů v typu kolekce, která podporuje stránkování. Tato jedna stránka studentů se pak předá do zobrazení.
return View(await PaginatedList<Student>.CreateAsync(students.AsNoTracking(), pageNumber ?? 1, pageSize));
Metoda PaginatedList.CreateAsync
přebírá číslo stránky. Dvě otazníky představují operátor nulového sjednocení. Operátor nulového sjednocení definuje výchozí hodnotu pro typ s možnou hodnotou null; výraz (pageNumber ?? 1)
znamená, že vrátí hodnotu pageNumber
, pokud má hodnotu, nebo vrátí hodnotu 1, pokud pageNumber
má hodnotu null.
Přidání stránkovacího odkazu
Nahraďte Views/Students/Index.cshtml
stávající kód následujícím kódem. Změny jsou zvýrazněné.
@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>
Příkaz @model
v horní části stránky určuje, že zobrazení nyní získá PaginatedList<T>
objekt místo objektu List<T>
.
Odkazy záhlaví sloupce používají řetězec dotazu k předání aktuálního vyhledávacího řetězce kontroleru, aby uživatel mohl řadit ve výsledcích filtru:
<a asp-action="Index" asp-route-sortOrder="@ViewData["DateSortParm"]" asp-route-currentFilter ="@ViewData["CurrentFilter"]">Enrollment Date</a>
Stránkovací tlačítka se zobrazují pomocnými rutiny značek:
<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>
Spusťte aplikaci a přejděte na stránku Studenti.
Klikněte na stránkovací odkazy v různých pořadích řazení a ujistěte se, že stránkování funguje. Potom zadejte hledaný řetězec a zkuste stránkování zopakovat, abyste ověřili, že stránkování funguje s řazením a filtrováním správně.
Vytvoření stránky O aplikaci
Na stránce Informace o webu Contoso University se zobrazí počet studentů, kteří se zaregistrovali ke každému datu registrace. To vyžaduje seskupování a jednoduché výpočty skupin. Uděláte to takto:
- Vytvořte třídu modelu zobrazení pro data, která potřebujete předat do zobrazení.
- Vytvořte v kontroleru metodu Home About.
- Vytvořte zobrazení O aplikaci.
Vytvoření modelu zobrazení
Ve složce Models vytvořte složku SchoolViewModels.
Do nové složky přidejte soubor EnrollmentDateGroup.cs
třídy a nahraďte kód šablony následujícím kódem:
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; }
}
}
Home Úprava kontroleru
Do HomeController.cs
horní části souboru přidejte následující příkazy using:
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Data;
using ContosoUniversity.Models.SchoolViewModels;
using Microsoft.Extensions.Logging;
Přidejte proměnnou třídy pro kontext databáze bezprostředně za levou složenou závorku třídy a získejte instanci kontextu z 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;
}
Přidejte metodu About
s následujícím kódem:
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());
}
Příkaz LINQ seskupí entity studentů podle data registrace, vypočítá počet entit v každé skupině a uloží výsledky do kolekce EnrollmentDateGroup
objektů modelu zobrazení.
Vytvoření zobrazení o aplikaci
Views/Home/About.cshtml
Přidejte soubor s následujícím kódem:
@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>
Spusťte aplikaci a přejděte na stránku O aplikaci. Počet studentů pro každé datum registrace se zobrazí v tabulce.
Získání kódu
Stáhněte nebo zobrazte dokončenou aplikaci.
Další kroky
V tomto kurzu se naučíte:
- Přidání odkazů pro řazení sloupců
- Přidání vyhledávacího pole
- Přidání stránkování do indexu Studentů
- Přidání stránkování do metody Index
- Přidání stránkovacího odkazu
- Vytvoření stránky O aplikaci
V dalším kurzu se dozvíte, jak zpracovávat změny datového modelu pomocí migrací.
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro