Estou começando aqui uma nova série sobre ASP.Net MVC, que vai focar no Preview 5 (CTP 5). Se você está começando agora com o ASP.Net MVC, sugiro dar uma olhada na categoria ASP.Net MVC daqui do blog, e se você conhece mas ainda não viu as novidades do Preview 4, dê uma olhada na série em que abordei estas novidades.

Começo hoje falando de uma novidade que achei muito legal: a possibilidade de filtrar as requisições a partir do verbo HTTP. É uma necessidade genuína, e que foi implementada com extrema simplicidade.

O problema:

Até agora quando criávamos um formulário, tínhamos que pensar no nome dos métodos que o controlador utilizava, e que viria a ser nossas ações (para mais informações sobre como um método vira uma ação veja este excelente post do Phill Haack). Como eu adoro o banco de dados Northwind, imaginem que tivéssemos operações CRUD na tabela “categories”, e o controlador se chama também “CategoriesController”. Considerando que a aplicação está hospedada no caminho “http://localhost/aplicacao”, como poderiam ficar as ações até o Preview 4 (P4):

  • Listagem: http://localhost/aplicacao/categories(ação “index” oculta) ou http://localhost/aplicacao/categories/index
  • Edição de um item: http://localhost/aplicacao/categories/editar/1
  • Salvar item editado: http://localhost/aplicacao/categories/salvareditar/1
  • Exclusão: http://localhost/aplicacao/categories/excluir/1
  • Inclusão: http://localhost/aplicacao/categories/novo
  • Salvar item a ser incluido: http://localhost/aplicacao/categories/salvarnovo

A tela ficaria mais ou menos assim:

Listagem de categorias no MVC

Notem que há 2 ações para as operações CRUD de edição e inclusão, isso porque essas ações são feitas em 2 passos: primeiro o usuário inclui ou altera os dados, e então submete o que alterou. A consulta é uma ação única, assim como a exclusão, que apenas exclui o item já pré selecionado na listagem.

A solução:

Com uma novidade introduzida no P5, não temos mais esse problema. Um simples atributo chamado AcceptVerbs adicionado ao método que representa a ação resolveu o problema. Vejam o método de criação de um item novo, que mostra os textboxes a serem preenchidos (estou usando LINQ to Entities com Entity Framework):

[AcceptVerbs("GET")]
public ActionResult Novo()
{
    return View();
}

Já para salvar o item novo, fica assim:

[ActionName("Novo")]
[AcceptVerbs("POST")]
public ActionResult SalvarNovo(string CategoryName)
{
    var cat = new Models.Category();
    cat.CategoryName = CategoryName;
    var db = new Models.NorthwindEntities();
    db.AddToCategories(cat);
    db.SaveChanges();
    db.Dispose();
    return RedirectToAction("Index");
}

Note que para salvar utilizei também o atributo ActionName, que permite trocar o nome da ação, que por padrão é igual ao nome do método. Nesse caso, o método se chama SalvarNovo, mas a ação se chama Novo. E, mesmo com as duas ações tendo o mesmo nome, cada uma só atende a um tipo de verbo. A primeira só atende GET, a segunda só atende POST. Se você tiver mais de um método atendendo à uma ação vai ter um erro, então tenha cuidado.

As views não mudam nada, ou quase nada. Só achei um problema com esse negócio de o nome da ação ser diferente do nome do método: não dá para utilizar Expressions no nome de um link ou form (veja como fazer isso aqui). Por exemplo, no meu link de novo da figura aí acima usei o seguinte:

<% =Html.ActionLink((CategoriesController ctl)=>ctl.Novo(),"Novo"%>

Ele gerou sozinho um link para “/categories/novo”. Isso funciona porque o nome do método é o mesmo nome da ação. Se fosse diferente ia dar pau. Quer dizer, se você for lá no primeiro método Novo e colocar um atributo ActionName nele, algo assim:

[ActionName("New")]
[AcceptVerbs("GET")]
public ActionResult Novo()
{
    return View();
}

Você vai ter problemas. O link vai ser gerado como “/categories/novo” do mesmo jeito, e ao clicar, vai receber um belo erro 404. Da mesma forma, fica impossível trabalhar no form com expressions. Tive que usar o nome do controlador e da ação com strings, o que não é tão confiável (veja em fundo verde):

<% using (Html.Form("Categories","Novo"))
   { %>
   <table>
    <tr>
        <td>Category Name:</td>
        <td><% =Html.TextBox("CategoryName") %></td>
    </tr>
    <tr>
        <td>Description:</td>
        <td><% =Html.TextBox("Description") %></td>
    </tr>
   </table>
<% =Html.SubmitButton("Enviar","Enviar") %>
<% } %>

Enfim, esse pequeno problema à parte, ficou bem melhor. Antes você teria que verificar a ação no Request.HttpMethod, assim como faz o Account Controller (eles ainda não atualizaram):

public ActionResult Login(string username, string password, bool? rememberMe)
{
    // Non-POST requests should just display the Login form 
    if (Request.HttpMethod != "POST")
    {
        return View();
    }

Isso seria um grande problema se você quisesse testar. Ia ter que ficar fazendo test doubles (mocks ou stubs, veja mais aqui) do Request. Como você está separando a lógica em 2 métodos independentes, consegue testar um a um. Beeeeem, melhor.

Você pode ver o código do atributo AcceptVerbs aqui, direto no source do Codeplex. Vou adiantar que a classe tem de código mesmo só 35 linhas. É curta e simples. A implementação basicamente se dá no único método que importa: IsValidForRequest. Dêem uma olhada nele com o código de tratamento de erro omitido:

public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo) {
    string incomingVerb = controllerContext.HttpContext.Request.HttpMethod;
    return Verbs.Contains(incomingVerb, StringComparer.OrdinalIgnoreCase);
}

Ou seja, o MVC pergunta à cada método se ele é válido, e ele é válido se o que você colocou na string (“POST”, “GET”, “PUT”, ou outro) bater. Não falei que era simples?

Vejam o código de verificação do atributo ActionName, que faz a troca do nome da ação:

public virtual bool IsValidForRequest(ControllerContext controllerContext,
string action, MethodInfo methodInfo) {
    return String.Equals(action, Name, StringComparison.OrdinalIgnoreCase);
}

Dessa vez ele verifica se o nome que você passou está na solicitação. Se está, ele diz que é válido. Muito simples mesmo.

O que vocês acharam? Muito legal, não é? No próximo post vou abordar os Binders, que é uma feature muito legal, e deixa a interface do MVC mais focada em padrões e mais testável ainda. Vou postar em breve.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.