Examinando os métodos e exibições de ação de edição para o controlador de filme

por Rick Anderson

Observação

Uma versão atualizada deste tutorial está disponível aqui usando a versão mais recente do Visual Studio. O novo tutorial usa ASP.NET Core MVC, que fornece muitas melhorias ao longo deste tutorial.

Este tutorial ensina a usar o ASP.NET Core MVC com controladores e exibições. O Razor Pages é uma nova alternativa no ASP.NET Core, um modelo de programação baseado em página que torna a criação da interface do usuário da Web mais fácil e produtiva. Recomendamos que você experimente o tutorial do Razor Pages antes da versão do MVC. O tutorial Páginas do Razor:

  • É mais fácil de acompanhar.
  • Aborda mais recursos.
  • É a abordagem preferencial para o desenvolvimento de novos aplicativos.

Nesta seção, você examinará os métodos de ação gerados Edit e as exibições do controlador de filme. Mas primeiro vamos fazer uma pequena distração para fazer a data de lançamento parecer melhor. Abra o arquivo Models\Movie.cs e adicione as linhas realçadas mostradas abaixo:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

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

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Você também pode tornar a cultura de data específica desta forma:

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

Abordaremos DataAnnotations no próximo tutorial. O atributo Display especifica o que deve ser exibido no nome de um campo (neste caso, “Release Date” em vez de “ReleaseDate”). O atributo DataType especifica o tipo dos dados, nesse caso, é uma data, portanto, as informações de hora armazenadas no campo não são exibidas. O atributo DisplayFormat é necessário para um bug no navegador Chrome que renderiza os formatos de data incorretamente.

Execute o aplicativo e navegue até o Movies controlador. Segure o ponteiro do mouse sobre um link Editar para ver a URL à qual ele se vincula.

EditLink_sm

O link Editar foi gerado pelo Html.ActionLink método na exibição Views\Movies\Index.cshtml :

@Html.ActionLink("Edit", "Edit", new { id=item.ID })

Html.ActionLink

O Html objeto é um auxiliar exposto usando uma propriedade na classe base System.Web.Mvc.WebViewPage . O ActionLink método do auxiliar facilita a geração dinâmica de hiperlinks HTML que se vinculam a métodos de ação em controladores. O primeiro argumento para o ActionLink método é o texto do link a ser renderizado (por exemplo, <a>Edit Me</a>). O segundo argumento é o nome do método de ação a ser invocado (nesse caso, a ação Edit ). O argumento final é um objeto anônimo que gera os dados de rota (nesse caso, a ID de 4).

O link gerado mostrado na imagem anterior é http://localhost:1234/Movies/Edit/4. A rota padrão (estabelecida em App_Start\RouteConfig.cs) usa o padrão {controller}/{action}/{id}de URL . Portanto, ASP.NET se traduz http://localhost:1234/Movies/Edit/4 em uma solicitação para o Edit método de ação do Movies controlador com o parâmetro ID igual a 4. Examine o código a seguir do arquivo App_Start\RouteConfig.cs . O método MapRoute é usado para rotear solicitações HTTP para o controlador e o método de ação corretos e fornecer o parâmetro de ID opcional. O método MapRoute também é usado pelos HtmlHelpers , como ActionLink para gerar URLs, considerando o controlador, o método de ação e quaisquer dados de rota.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", 
            id = UrlParameter.Optional }
    );
}

Você também pode passar parâmetros de método de ação usando uma cadeia de caracteres de consulta. Por exemplo, a URL http://localhost:1234/Movies/Edit?ID=3 também passa o parâmetro ID de 3 para o Edit método de ação do Movies controlador.

EditQueryString

Abra o Movies controlador. Os dois Edit métodos de ação são mostrados abaixo.

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

Observe se o segundo método de ação Edit é precedido pelo atributo HttpPost. Esse atributo especifica que a sobrecarga do Edit método pode ser invocada apenas para solicitações POST. Você pode aplicar o HttpGet atributo ao primeiro método de edição, mas isso não é necessário porque é o padrão. (Vamos nos referir a métodos de ação que são atribuídos implicitamente ao HttpGet atributo como HttpGet métodos.) O atributo Bind é outro mecanismo de segurança importante que impede que os hackers sobrepubliquem dados em seu modelo. Você só deve incluir propriedades no atributo de associação que deseja alterar. Você pode ler sobre sobreposto e o atributo bind na minha nota de segurança de sobreposto. No modelo simples usado neste tutorial, associaremos todos os dados no modelo. O atributo ValidateAntiForgeryToken é usado para evitar a falsificação de uma solicitação e é emparelhado com @Html.AntiForgeryToken() no arquivo de exibição de edição (Views\Movies\Edit.cshtml), uma parte é mostrada abaixo:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

@Html.AntiForgeryToken() gera um token anti-falsificação de formulário oculto que deve corresponder ao Edit método do Movies controlador. Você pode ler mais sobre a solicitação intersite forjada (também conhecida como XSRF ou CSRF) em meu tutorial Prevenção XSRF/CSRF no MVC.

O HttpGetEdit método usa o parâmetro ID do filme, pesquisa o filme usando o método Entity Framework Find e retorna o filme selecionado para o modo de exibição Editar. Se um filme não puder ser encontrado, HttpNotFound será retornado. Quando o sistema de scaffolding criou a exibição de Edição, ele examinou a classe Movie e o código criado para renderizar os elementos <label> e <input> de cada propriedade da classe. O seguinte exemplo mostra a exibição de Edição que foi gerada pelo sistema de scaffolding do Visual Studio:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price 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>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Observe como o modelo de exibição tem uma @model MvcMovie.Models.Movie instrução na parte superior do arquivo — isso especifica que a exibição espera que o modelo de exibição seja do tipo Movie.

O código scaffolded usa vários métodos auxiliares para simplificar a marcação HTML. O Html.LabelFor auxiliar exibe o nome do campo ("Title", "ReleaseDate", "Genre" ou "Price"). O Html.EditorFor auxiliar renderiza um elemento HTML <input> . O Html.ValidationMessageFor auxiliar exibe todas as mensagens de validação associadas a essa propriedade.

Execute o aplicativo e navegue até a URL /Movies . Clique em um link Editar. No navegador, exiba a origem da página. O HTML para o elemento de formulário é mostrado abaixo.

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <legend>Movie</legend>

      <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

      <div class="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
            <input class="text-box single-line" 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" type="text" value="7.99" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

Os <input> elementos estão em um elemento HTML <form> cujo action atributo está definido para postar na URL /Movies/Editar . Os dados do formulário serão postados no servidor quando o botão Salvar for clicado. A segunda linha mostra o token XSRF oculto gerado pela @Html.AntiForgeryToken() chamada.

Processando a solicitação POST

A lista a seguir mostra a versão HttpPost do método de ação Edit.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

O atributo ValidateAntiForgeryToken valida o token XSRF gerado pela @Html.AntiForgeryToken() chamada na exibição.

O ASP.NET associador de modelo MVC usa os valores de formulário postados e cria um Movie objeto que é passado como o movie parâmetro . O ModelState.IsValid verifica se os dados enviados no formulário podem ser usados para modificar (editar ou atualizar) um Movie objeto . Se os dados forem válidos, os dados do filme serão salvos na Movies coleção da dbinstância (MovieDBContext ). Os novos dados de filme são salvos no banco de dados chamando o SaveChanges método de MovieDBContext. Depois de salvar os dados, o código redireciona o usuário para o método de ação Index da classe MoviesController, que exibe a coleção de filmes, incluindo as alterações feitas recentemente.

Assim que a validação do lado do cliente determinar que o valor de um campo não é válido, uma mensagem de erro será exibida. Se o JavaScript estiver desabilitado, a validação do lado do cliente será desabilitada. No entanto, o servidor detecta que os valores postados não são válidos e os valores de formulário são exibidos novamente com mensagens de erro.

A validação é examinada mais detalhadamente posteriormente no tutorial.

Os Html.ValidationMessageFor auxiliares no modelo de exibição Edit.cshtml cuidam da exibição de mensagens de erro apropriadas.

abcNotValid

Todos os HttpGet métodos seguem um padrão semelhante. Eles obtêm um objeto de filme (ou uma lista de objetos, no caso de Index) e passam o modelo para o modo de exibição. O Create método passa um objeto de filme vazio para o modo de exibição Criar. Todos os métodos que criam, editam, excluem ou, de outro modo, modificam dados fazem isso na sobrecarga HttpPost do método. Modificar dados em um método HTTP GET é um risco de segurança, conforme descrito na entrada de postagem do blog ASP.NET Dica MVC nº 46 – Não use Excluir Links porque eles criam falhas de segurança. Modificar dados em um método GET também viola as práticas recomendadas de HTTP e o padrão REST de arquitetura, que especifica que as solicitações GET não devem alterar o estado do aplicativo. Em outras palavras, a execução de uma operação GET deve ser uma operação segura que não tem efeitos colaterais e não modifica os dados persistentes.

Validação de jQuery para localidades que não são em inglês

Se você estiver usando um computador US-English, ignore esta seção e vá para o próximo tutorial. Você pode baixar a versão Globalizar deste tutorial aqui. Para obter um excelente tutorial de duas partes sobre internacionalização, confira Internacionalização do ASP.NET MVC 5 da Nadeem.

Observação

para dar suporte à validação de jQuery para localidades que não são em inglês que usam uma vírgula (",") para um ponto decimal e formatos de data não US-English, você deve incluir globalize.js e seus arquivos de culturas/globalize.cultures.jsespecíficos (de https://github.com/jquery/globalize ) e JavaScript para usar Globalize.parseFloat. Você pode obter a validação jQuery que não é em inglês do NuGet. (Não instale o Globalize se você estiver usando uma localidade em inglês.)

  1. No menu Ferramentas , clique em Gerenciador de Pacotes NuGet e, em seguida, clique em Gerenciar Pacotes NuGet para solução.

    Captura de tela do menu Ferramentas para iniciar a validação do jQuery para localidades que não são em inglês.

  2. No painel esquerdo, selecione Procurar*. *(Veja a imagem abaixo.)

  3. Na caixa de entrada, insira Globalizar*.

    Captura de tela da caixa de entrada para inserir Globalizar.

    Escolha jQuery.Validation.Globalize, escolha MvcMovie e clique em Instalar. O arquivo Scripts\jquery.globalize\globalize.js será adicionado ao seu projeto. A pasta *Scripts\jquery.globalize\cultures* conterá muitos arquivos JavaScript de cultura. Observe que pode levar cinco minutos para instalar esse pacote.

    O código a seguir mostra as modificações no arquivo Views\Movies\Edit.cshtml:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Use the Globalization plugin to parse the value
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "yyyy-MM-dd");
    }
</script>
}

Para evitar repetir esse código em todos os modos de exibição editar, você pode movê-lo para o arquivo de layout. Para otimizar o download do script, confira meu tutorial Agrupamento e Minificação.

Para obter mais informações , consulte ASP.NET Internacionalização do MVC 3 e ASP.NET Internacionalização MVC 3 – Parte 2 (NerdDinner).

Como uma correção temporária, se você não conseguir obter a validação funcionando em sua localidade, poderá forçar seu computador a usar o inglês dos EUA ou desabilitar o JavaScript em seu navegador. Para forçar o computador a usar o inglês dos EUA, você pode adicionar o elemento de globalização ao arquivo deweb.config raiz de projetos. O código a seguir mostra o elemento de globalização com a cultura definida como Estados Unidos inglês.

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

No próximo tutorial, implementaremos a funcionalidade de pesquisa.