Методы контроллера и представления

Rick Anderson

Мы хорошо поработали над нашим приложением, но оно не идеально. Нам не надо видеть время (12:00:00 AM на изображении снизу), а ReleaseDate должно писаться в два слова.

../../_images/m55.png

Откройте файл Models/Movie.cs и добавьте выделенные строки:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}
  • Кликните правой клавишей мышки по строке > Quick Actions.
../../_images/qa.png
  • Нажмите using System.ComponentModel.DataAnnotations;
../../_images/da.png

Visual Studio добавит using System.ComponentModel.DataAnnotations;.

Теперь давайте удалим выражения using, которые нам не нужны. Они показаны светло серым шрифтом. Кликните правой клавишей мышки в любом месте файла Movie.cs > Organize Usings > Remove Unnecessary Usings.

../../_images/rm.png

Обновленный код:

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }
}

Позже мы рассмотрим DataAnnotations. Атрибут Display <https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.aspx>`__указывает, что отображать для имени поля (в данном случае “Release Date” вместо “ReleaseDate”). Атрибут `DataType определяет тип данных, в данном случае это дата, так что информация о времени не отображается.

Перейдите к контроллеру Movies и задержите курсор мышки на ссылке Edit, чтобы увидеть целевой URL.

../../_images/edit7.png

Ссылки Edit, Details и Delete сгенерированы якорным тег-хелпером MVC Core в файле Views/Movies/Index.cshtml.

<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>

тег-хелперы включают серверный код в создание и отображение HTML элементов в Razor файлах. В коде выше :dn:class:`~Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper` динамически генерирует значение HTML атрибута href из метода действия контроллера и роутового id. Можно использовать View Source или инструменты F12, чтобы просмотреть сгенерированную разметку. Инструменты F12 показаны ниже.

../../_images/f12.png

Вот формат роутинга, настроенный в файле Startup.cs.

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core переводит http://localhost:1234/Movies/Edit/4 в запрос для метода действия Edit контроллера Movies с параметром Id равном 4.

Tag Helpers являются одной из наиболее популярных возможностей в ASP.NET Core. См. `Additional resources`_.

Откройте контроллер Movies два метода действия Edit:

../../_images/11.png
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

Атрибут [Bind] является одним из способов для защиты от !!!!!!`оверпостинга <http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application#overpost>`__. В атрибут [Bind] нужно добавлять только те свойства, которые вы хотите изменить. См. Защитите контроллер от оверпостинга. ViewModels предлагают альтернативный подход для защиты от оверпостинга.

Обратите внимание, что перед вторым Edit стоит атрибут [HttpPost].

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

Атрибут :dn:class:`~Microsoft.AspNetCore.Mvc.HttpPostAttribute` указывает, что этот метод Edit можно вызывать только для POST запросов. Вы можете применить атрибут [HttpGet] к первому методу edit, но в этом нет необходимости, потому что [HttpGet] - это атрибут по умолчанию.

Атрибут :dn:class:`~Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute` используется для предотвращения злоумышленных запросов и работает в паре с токеном против злоумышленных запросов, сгенерированным файлом представления (Views/Movies/Edit.cshtml). Этот токен создается с помощью Form тег-хелпера.

<form asp-action="Edit">

Form тег-хелпер генерирует скрытый токен против злоумышленных запросов, который должен соответствовать [ValidateAntiForgeryToken], сгенерированному токену в методе Edit контроллера Movies. См. 🔧 Anti-Request Forgery.

Метод HttpGet Edit принимает параметр ID, ищет ролик с помощью Entity Framework метода SingleOrDefaultAsync и возвращает выбранный ролик представлению Edit. Если ролик найти нельзя, возвращается NotFound (HTTP 404).

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

Когда система скаффолдинга создавала представление Edit, она изучила класс Movie и создала код для отображения элементов <label> и <input> для каждого свойства класса. В следующем примере показано представление Edit, которое было сгенерировано системой скаффолдинга:

@model MvcMovie.Models.Movie

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

<h2>Edit</h2>

<form asp-action="Edit">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <input type="hidden" asp-for="ID" />
        <div class="form-group">
            <label asp-for="Genre" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Price" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <label asp-for="Title" class="col-md-2 control-label"></label>
            <div class="col-md-10">
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Обратите внимание, что в представлении вверху есть выражение @model MvcMovie.Models.Movie, это обозначает, что модель для представления должна быть типа Movie.

Скаффолдинг использует несколько тег-хелпер методов, чтобы упростить HTML разметку. Label тег-хелпер отображает имя поля (“Title”, “ReleaseDate”, “Genre” или “Price”). Input тег-хелпер отображает HTML элемент <input>. Validation тег-хелпер отображает любые сообщения о валидации, связанные с этим свойством.

Запустите приложение и перейдите по URL /Movies. Кликните по ссылке Edit. В браузере просмотрите исходный код страницы. Сгенерированный HTML для элемента <form> показан ниже.

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup removed for brevity -->
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

Элементы <input> находятся в HTML элементе <form>. Атрибут action установлен на отправку формы URL /Movies/Edit/id. Данные из формы отправятся на сервер после нажатия клавиши Save. На последней строке перед закрывающим тегом </form> показан спрятанный XSRF токен, сгенерированный Form тег-хелпером.

Обработка POST запроса

В следующем листинге показана [HttpPost] версия метода Edit.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

Атрибут [ValidateAntiForgeryToken] валидирует спрятанный XSRF токен, сгенерированный Form тег-хелпером

Система связывания моделей принимает значения отправленной формы и создает объект Movie, который передается как параметр movie. Метод ModelState.IsValid проверяет, чтобы данные, отправленные через форму, можно было использовать для изменения (редактирования или обновления) объекта Movie. Если данные валидны, они сохраняются. Обновленные данные сохраняются в БД, когда вызывается метод SaveChangesAsync. После сохранения данных происходит перенаправление к методу действия Index класса MoviesController, который отображает коллекцию роликов, включая только что внесенные изменения.

Прежде чем форма отправится на сервер, валидация на стороне клиента проверяет все правила валидации в полях. Если возникли какие-то валидационные ошибки, отображается сообщение об ошибках и форма не отправляется. Если JavaScript отключен, то валидации на стороне клиента не будет, но сервер определит невалидные значения и значения формы будут отображены с сообщениями об ошибках. Далее в руководстве мы изучим Валидация модели валидацию более подробно. Validation тег-хелпер в представлении Views/Book/Edit.cshtml отвечает за отображение соответствующих сообщений об ошибках.

../../_images/val.png

Все``HttpGet`` методы в контроллере следуют схожему паттерну. Они получают объект (или список объектов, как в случае с Index) и передают объект (модель) представлению. Метод Create передает пустой объект представлению Create. Все методы, которые создают, редактируют, удаляют или еще как-то меняют данные, делают это с помощью переопределенной версии метода [HttpPost]. Изменение данных в методе HTTP GET является риском в плане безопасности, см. ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes. Изменение данных в методе HTTP GET также противоречит архитектурному паттерну REST, где утверждается, что GET запросы не должны менять состояние приложения. Другими словами, выполнение GET операций должно быть безопасным, без побочных эффектов, без изменения существующих данных.

Поделись хорошей новостью с друзьями!
Следи за новостями!