web_cs_05.md 9.9 KB

ASP.NET Core MVC. Формы, фильтрация, поиск

Добавим в наше приложение фильтрацию и поиск (попытаемся повторить функционал WPFприложения).

Добавление формы

Поля ввода на странице обычно заворачиваются в форму (тег <form>) и передаются в контроллер при клике на кнопку с типом submit. Можно сделать и автоматическую отправку формы при изменении данных в полях ввода ("живой" ввод), но такой вариант не рекомендую использовать, так как получается повышенная нагрузка на сервер (ведь каждый раз запрашивается страница целиком).

Итак, добавим на страницу со списком продуктов форму с полями:

<h1 class="display-4">Список продуктов</h1>

<div class="row">
    <form method="get">
        <input type="text" name="search" value="@Context.Request.Query["search"]"/>
        <select name="productType">
            @foreach (ProductType pt in @ViewBag.productTypes)
            {
                <option 
                    value="@pt.ID" 
                    selected="@(pt.ID.ToString() == Context.Request.Query["productType"])"
                >
                    @pt.TitleType
                </option>
            }
        </select>
        <button type="submit">Применить</button>
    </form>
</div>

...

Должно получиться примерно такое окно:

Что тут происходит?

  1. Добавляем форму и задаем метод GET

    <form method="get">
        ...
    </form>
    

    По умолчанию для отправки форм используется метод POST, в таком варианте данные формы передаются в теле запроса. Для формирования представления это не принципиально, но если перезагрузить получившееся окно в браузере, то мы потеряем контекст (введенные ранее данные для фильтрации). Используя метод GET мы передаем данные в queryString и страница открывается с использованием этой строки и сохранением контекста (я подчеркнул эту строку на скриншоте).

    Еще в форме можно задать атрибут action, который задает альтернативный URL для отправки формы, по умолчанию форма отправляется на URL текущей страницы.

  2. Добавляем на форму поле ввода для фильтрации по названию

    <input 
        type="text" 
        name="search" 
        value="@Context.Request.Query["search"]"/>
    
    • name - название переменной (ключа в queryString)
    • value - содержимое поля ввода (при первом запуске оно пустое, но если мы применим фильтрацию, то в содержимое запишется текущее значение фильтра, полученное из queryString)
    • Context - про это свойстао было упоминание в лекциях, оно содержит все параметры запроса. Мы в данном случае вытаскиваем значение фильтра
  3. Добавляем выпадающий список с типами продукции

    <select name="productType">
        @foreach (ProductType pt in @ViewBag.productTypes)
        {
            <option 
                value="@pt.ID" 
                selected="@(pt.ID.ToString() == Context.Request.Query["productType"])"
            >
                @pt.TitleType
            </option>
        }
    </select>
    
    • тег <select> как раз объявляет выпадающий список, внутри него мы в цикле добавляем <option> - элементы выпадающего списка
    • атрибут selected устанавливаем true для выбранного элемента
    • ViewBag.productTypes - типы продукции из БД, заполняем их в контроллере (покажу ниже)

Доработка модели и поставщика данных

Нам нужно добавить в модель список типов продукции (я делаю пустой проект - у вас, если вы делали все лабы, такая модель уже должна быть) и предусмотреть фильтрацию при получении списка продукции.

Интерфейс поставщика данных, использованный в этом проекте

public interface IDataProvider
{
    // В метод получения списка продукции добавил номер страницы для пагинатора
    IEnumerable<Product> getProduct(int pageNum = 1);

    // Объявляем метод получения списка типов продукции
    IEnumerable<ProductType> getProductTypes();

    // Объявляем методы для задания фильтрации
    void setSearchFilter(string? searchString);
    void setProductTypeFilter(string? productTypeFilter);
}

Реализация поставщика данных

Объявляем приватные переменные для хранения значений фильтров и методы для их инициализации:

private string? searchFilter;
private string? productTypeFilter; 

public void setSearchFilter(string? searchString)
{
    this.searchFilter = searchString;
}

public void setProductTypeFilter(string? productTypeFilter)
{
    this.productTypeFilter = productTypeFilter;
}

Реализуем метод для получения списка типов продукции:

public IEnumerable<ProductType> getProductTypes()
{
    using (MySqlConnection db = new MySqlConnection(connectionString))
    {
        return db.Query<ProductType>("SELECT * FROM ProductType").ToList();
    }
}

Переписываем метод получения списка продукции с учетом фильтрации и пагинации:

Для формирования условий используется пакет Dapper.SqlBuilder

public IEnumerable<Product> getProduct(int pageNum = 1)
{
    using (MySqlConnection db = new MySqlConnection(connectionString))
    {
        var builder = new SqlBuilder();

        // если задан фильтр по названию
        if (!String.IsNullOrEmpty(this.searchFilter))
            // то добавляем условие с LIKE 
            builder.Where(
                "Title LIKE @tt", 
                // обратите внимание как задается шаблон поиска
                new { tt = '%' + this.searchFilter + '%' });

        // если задан фильтр по типу продукции
        if (!String.IsNullOrEmpty(this.productTypeFilter))
            builder.Where(
                "ProductTypeID = @id", 
                new { id = this.productTypeFilter });

        // шаблон запроса с пагинацией
        // (запрос не полный)
        var template = builder.AddTemplate(
            "SELECT p.ID, p.Title, p.ProductTypeID, p.ArticleNumber, p.Image, pt.TitleType AS ProductTypeTitle " +
            "FROM Product p " +
            "JOIN ProductType pt ON p.ProductTypeID=pt.ID " +
            "/**where**/ " +
            "LIMIT @pageLen OFFSET @offset",
            new
            {
                pageLen = 20,
                offset = (pageNum - 1) * 20
            }
        );

        return db.Query<Product>(
            template.RawSql,
            template.Parameters).ToList();
    }
}

Финал

На этом знакомство с разработкой веб-приложений на C# мы закончим. Для дальнейшей работы с классическими веб-приложениями нужно копать JavaScript и AJAX-запросы, но мне лень с этим разбираться.

На демо-экзамене вам веб-разработка вообще не понадобится, но если будете учавствовать в чемпионате, то этих основ должно хватить.


Домашнее задание

  1. Реализовать код из лекции
  2. Самостоятельно реализовать сортировку

    • в форму добавить радиокнопки для выбора типа сортировки (по возрастанию или убыванию)
    • предусмотреть в интерфейсе поставщика и реализовать в поставщике метод для сохранения типа сортировки
    • в методе формирования списка продукции в builder добавить сортировку

      Пример был в лекции про пагинацию в WPF

      builder.OrderBy(orderCondition);