Clique para classificar e enviar comentários
Related Articles

Neste artigo, falaremos de algumas práticas recomendadas para ligar e carregar assemblies usando o CLR.

Aarthi Ramamurthy e Mark Miller

MSDN Magazine Maio 2009

...

Read more!

Mike Calligaro mostra as noções básicas de uso do XNA Game Studio 3.0 para gravar jogos para o Zune.

Mike Calligaro

MSDN Magazine Maio 2009

...

Read more!

Nunca houve um consenso sobre se grandes blobs, tais como documentos e itens multimídia, devem ser armazenados no banco de dados ou no sistema de arquivos. No SQL Server 2008, você não fica em dúvida; o armazenamento de fluxos de arquivos fornece a melhor abordagem.

Bob Beauchemin

MSDN Magazine Maio 2009

...

Read more!

Demonstramos como criar uma plataforma de processamento ponto a ponto onde diversas funções trabalham juntas por um objetivo comum: fazer o seu trabalho.

Matt Neely

MSDN Magazine Junho 2009

...

Read more!

Neste artigo, a autora analisa duas versões do mesmo aplicativo - uma que utiliza o serviço de dados no local e outra que utiliza o serviço de dados de tabela do Azure - para ilustrar o acesso aos dados na nuvem.

Elisa Flasko

MSDN Magazine Maio 2009

...

Read more!

Popular Articles

Agora, você pode executar análises eficientes e sofisticadas de texto usando expressões regulares no SQL Server 2005.

David Banister

MSDN Magazine February 2007

...

Read more!

Kenny Kerr faz grandes elogios ao novo Feature Pack do Visual C++ 2008, que apresenta recursos convenientes e modernos para o Visual C++.

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

Ray Djajadinata

MSDN Magazine May 2007

...

Read more!

Writing a Web application with ASP.NET is unbelievably easy. So many developers don't take the time to structure their applications for great performance. In this article, the author presents 10 tips for writing high-performance Web apps. The discussion is not limited to ASP.NET applications because they are just one subset of Web applications.

Rob Howard

MSDN Magazine January 2005

...

Read more!

James Avery does it again with his popular list of developer tools. This time he covers the best Visual Studio add-ins available today that you can download for free.

James Avery

MSDN Magazine December 2005

...

Read more!

ASP.NET MVC
Criando aplicativos Web sem Web Forms
Chris Tavares

Este artigo se baseia em uma versão de pré-lançamento da ASP.NET MVC Framework. Todos os detalhes aqui contidos estão sujeitos a alterações.
Este artigo aborda:
  • Padrão MVC (Model View Controller)
  • Criando controladores e modos de exibição
  • Criando formulários e executando postback
  • Alocadores de controlador e outros pontos de extensão
Este artigo utiliza as seguintes tecnologias:
ASP.NET
Download do código disponível em: MVCFramework2008_03.exe (189 KB)
Browse the Code Online
S ou desenvolvedor profissional há cerca de 15 anos, além de pelos menos mais 10 como entusiasta antes disso. Assim como a maior parte da minha geração, comecei em máquinas de 8 bits e, depois, passei para a plataforma PC. Passando por máquinas cada vez mais complexas, escrevi aplicativos que faziam de tudo, desde pequenos jogos até o gerenciamento de dados pessoais, passando pelo controle de hardwares externos.
No entanto, na primeira metade da minha carreira, todos os softwares que escrevia tinham uma coisa em comum: eram sempre aplicativos locais em execução na área de trabalho de um usuário. No começo dos anos 90, comecei a ouvir falar nessa coisa nova chamada World Wide Web. Nela eu vi uma oportunidade para criar um aplicativo Web que me deixasse inserir as informações sobre o meu cartão de ponto sem que fosse realmente necessário deixar o local de trabalho e ir até o meu escritório.
A experiência foi, em uma só palavra, desafiadora. Lidar com uma Web sem estado simplesmente não seguia a minha lógica de programação controlada por área de trabalho. Acrescente-se a isso muita depuração, um servidor UNIX ao qual não tinha acesso raiz e aquela coisa esquisita de colchete angular, e me recolhi intimidado ao velho desenvolvimento em área de trabalho durante mais alguns anos.
Permaneci afastado do desenvolvimento para a Web; é claro que ele era importante, mas eu realmente não compreendi o modelo de programação. Depois, foram lançados o Microsoft® .NET Framework e o ASP.NET. Finalmente, uma estrutura que me deixava trabalhar em aplicativos Web, ainda que fosse praticamente igual à programação de aplicativos de área de trabalho. Eu podia criar janelas (páginas), conectar controles a eventos, e o designer não deixava que eu me preocupasse com aquelas coisas de colchetes angulares. E o melhor de tudo: o ASP.NET lidava automaticamente com a natureza sem estado da Web para mim com o estado de exibição! E eu era um programador feliz novamente... pelo menos por algum tempo.
Na medida em que ganhava experiência, fazia as minhas escolhas em termos de design. Eu aprendi várias práticas recomendadas que apliquei enquanto trabalhava nos aplicativos de área de trabalho. Duas delas eram:
  • Separação de preocupações: não misturar lógica da interface do usuário com comportamento subjacente.
  • Testes de unidade automatizados: escreva testes automatizados que verifiquem se o código faz aquilo que você imagina.
Os princípios subjacentes se aplicam aqui independentemente da tecnologia. A separação de preocupações é um princípio fundamental existente para lhe ajudar a lidar com a complexidade. Misturar responsabilidades diferentes dentro do mesmo objeto – como calcular as horas de trabalho restantes, formatar dados e desenhar um gráfico – é pedir para que haja problemas de manutenção. E os testes automatizados são cruciais para se obter um código de qualidade de produção ao mesmo tempo em que mantêm sua saúde mental quando você está atualizando um projeto existente.
Os Web Forms do ASP.NET facilitaram muito a introdução, mas, por um outro lado, tentar aplicar os meus princípios de design aos aplicativos Web era uma luta. Os Web Forms estão essencialmente concentrados na interface do usuário; o essencial é a página. Você começa criando a interface do usuário e arrastando os controles. É muito sedutora a idéia de apenas jogar a lógica do aplicativo nos manipuladores de evento da página (de maneira muito semelhante ao Visual Basic® habilitado para aplicativos do Windows®).
Além disso, os testes de unidade das páginas costumam ser difíceis. Não é possível executar um objeto Page em todo o ciclo de vida sem bagunçar todo o ASP.NET. Embora seja possível testar os aplicativos Web enviando solicitações HTTP para um servidor ou automatizando um navegador, esse tipo de teste é frágil (basta alterar uma identificação de controle e o teste falha), difícil de configurar (você precisa configurar o servidor na máquina de todos os desenvolvedores exatamente da mesma forma) e reduzir a velocidade de execução.
Quando comecei a criar aplicativos Web mais sofisticados, as abstrações fornecidas pelos Web Forms como controles, estados de exibição e ciclos de vida da página, passaram mais a atrapalhar do que a ajudar. Estava passando mais e mais tempo configurando a vinculação de dados (e escrevendo inúmeros manipuladores de evento para conseguir a configuração correta). Eu tinha de imaginar como reduzir o tamanho do estado de exibição para que as minhas páginas fossem carregadas mais rapidamente. Os Web Forms requerem a existência de um arquivo físico para cada URL, algo que os sites dinâmicos (como um wiki, por exemplo) dificultam. E escrever com êxito um WebControl personalizado é um processo muito complexo que exige uma ampla compreensão tanto do ciclo de vida da página quanto do designer do Visual Studio®.
Desde que vim trabalhar na Microsoft, tive a oportunidade de compartilhar o meu conhecimento sobre vários pontos problemáticos e, felizmente, de resolver em parte esses problemas. Mais recentemente, surgiu uma oportunidade como essas por meio da minha participação como desenvolvedor no projeto Web Client Software Factory (codeplex.com/websf) das diretrizes. Em especial, uma das coisas que as diretrizes incorporam a seus resultados finais é o teste de unidade automatizado. No Web Client Software Factory, propusemos o uso do padrão MVP (Model View Presenter) para criar Web Forms que pudessem ser testados.
Resumindo, em vez de colocar a lógica na página, o MVP faz com que você crie as páginas de forma que elas (View) apenas façam chamadas em um objeto separado, o Presenter. Em seguida, o objeto Presenter executa toda a lógica necessária para responder à atividade na exibição, normalmente usando outros objetos (Model) para acessar bancos de dados, executar a lógica de negócios etc. Assim que as etapas são concluídas, Presenter atualiza a exibição. Essa abordagem lhe dá a capacidade de teste porque o apresentador está isolado do pipeline do ASP.NET; ele se comunica com a exibição por meio de uma interface e pode ser testado isoladamente em relação à página.
O MVP funciona, mas a implementação pode ser um pouco esquisita; você precisa de uma interface de exibição separada, sendo necessário escrever muitas funções de encaminhamento de eventos nos arquivos code-behind. Mas, se quiser uma interface do usuário que possa ser testada nos aplicativos dos Web Forms, isso é o melhor que você conseguirá. Todos os aprimoramentos exigiriam uma alteração na plataforma subjacente.

Padrão MVC (Model View Controller)
Felizmente, a equipe do ASP.NET tem ouvido desenvolvedores como eu e deu início ao desenvolvimento de uma estrutura de aplicativo Web que permanece no mesmo patamar dos Web Forms que você conhece e ama, mas que conta com um conjunto bem diferente de ferramentas de design:
  • Adote HTTP e HTML – sem ocultá-los.
  • A capacidade de teste é interna desde o começo.
  • Extensível em praticamente todos os pontos.
  • Controle total sobre a saída.
Essa nova estrutura se baseia no padrão MVC (Model View Controller), daí o nome ASP.NET MVC. O padrão MVC foi inventado originalmente nos anos 70 como parte da Smalltalk. Como mostrarei neste artigo, ela efetivamente respeita a natureza da Web. O MVC divide a interface do usuário em 3 objetos distintos: o controlador, que recebe e trata a entrada; o modelo, que contém a lógica do domínio e a exibição, que gera a saída. No contexto da Web, a entrada é uma solicitação HTTP, e o fluxo da solicitação é semelhante ao da Figura 1.
Figure 1 Fluxo de solicitação do padrão MVC (Clique na imagem para aumentar a exibição)
Isso é bem diferente do processo nos Web Forms. No modelo dos Web Forms, a entrada passa pela página (View), e a exibição é responsável tanto pelo tratamento da entrada quanto pela geração da saída. Já quando ela chega ao MVC, as responsabilidades são separadas.
Portanto, agora provavelmente deve estar passando uma das duas coisas pela sua cabeça. Pode ser, "Ei, isso é legal. Como uso?" ou "Por que escreveria três objetos quando tinha que escrever apenas um antes?". Ambas são perguntas excelentes e mais bem explicadas quando se observa um exemplo. Então, escreverei um pequeno aplicativo Web usando a MVC Framework para demonstrar suas vantagens.

Criando um controlador
Para acompanhar, você precisará instalar o Visual Studio 2008 e obter uma cópia da MVC Framework. No momento em que este artigo está sendo escrito, ele está disponível como parte da CTP (Community Technology Preview) de dezembro de 2007 das Extensões do ASP.NET (asp.net/downloads/3.5-extensions). Você desejará ter tanto a CTP com as extensões quanto o MVC Toolkit, que inclui alguns objetos auxiliares muito úteis. Assim que baixar e instalar a CTP, você terá um novo tipo de projeto na caixa de diálogo New Project chamado ASP.NET MVC Web Application.
Selecionar o projeto de aplicativo Web do MVC lhe oferece uma solução que parece ser um pouco diferente do site ou do aplicativo habitual. O modelo de solução cria um aplicativo Web com alguns diretórios novos (como mostrado na Figura 2). Em especial, o diretório Controllers contém as classes do controlador e o diretório Views (e todos os seus subdiretórios), as exibições.
Figure 2 A estrutura de projeto do MVC 
Escreverei um controlador bastante simples que retorna um nome passado na URL (Uniform Resource Locator). Clicar com o botão direito do mouse na pasta Controllers e escolher Add Item exibe a caixa de diálogo Add New Item com algumas novas adições, inclusive uma classe Controller do MVC e vários componentes de exibição do MVC. Nesse caso, estou adicionando uma classe tão criativa quanto chamada HelloController:
using System;
using System.Web;
using System.Web.Mvc;

namespace HelloFromMVC.Controllers
{
    public class HelloController : Controller
    {
        [ControllerAction]
        public void Index()
        {
            ...
        }
    }
}
Uma classe de controlador é bem mais leve do que uma página. Na verdade, as únicas coisas realmente necessárias são a derivação de System.Web.Mvc.Controller e a inserção do atributo [ControllerAction] nos métodos de ação. Uma ação é um método chamado em resposta a uma solicitação para uma determinada URL. As ações são responsáveis por realizar todo o processamento necessário e, em seguida, renderizar uma exibição. Começarei escrevendo uma ação simples que passa o nome com a exibição, como se pode ver aqui:
[ControllerAction]
 public void HiThere(string id)
 {
     ViewData["Name"] = id;
     RenderView("HiThere");
 }
O método de ação recebe o nome da URL por meio do parâmetro id (mais sobre como mais à frente), o armazena na coleção ViewData e, em seguida, renderiza uma exibição chamada HiThere.
Antes de abordar como esse método é chamado ou como é a exibição, gostaria de falar sobre capacidade de teste. Lembra-se dos meus comentários anteriores sobre a dificuldade de testar as classes de página dos Web Forms? Bem, os controladores são bem mais fáceis de testar. Na verdade, é possível criar uma instância de um controlador diretamente, e os métodos de ação são chamados, sem que haja nenhuma infra-estrutura adicional. Você não precisa de um contexto HTTP e de um servidor; basta um equipamento de teste. Como exemplo, incluí uma unidade de teste do VSTS (Visual Studio Team System) para a classe na Figura 3.
namespace HelloFromMVC.Tests
{
    [TestClass]
    public class HelloControllerFixture
    {
        [TestMethod]
        public void HiThereShouldRenderCorrectView()
        {
            TestableHelloController controller = new 
              TestableHelloController();
            controller.HiThere("Chris");

            Assert.AreEqual("Chris", controller.Name);
            Assert.AreEqual("HiThere", controller.ViewName);
        }

    }

    class TestableHelloController : HelloController
    {
        public string Name;
        public string ViewName;

        protected override void RenderView(
            string viewName, string master, object data)
        {
            this.ViewName = viewName;
            this.Name = (string)ViewData["Name"];
        }
    }

}

Há muitas coisas acontecendo aqui. O teste real é bem simples: crie uma instância do controlador, chame o método com os dados esperados e verifique se a exibição correta foi renderizada. Faço a verificação criando uma subclasse específica de teste que substitui o método RenderView. Isso me permite gerar um curto-circuito na criação da HTML. Só me preocupo com o envio dos dados certos para a exibição e a renderização da exibição correta. Nesse teste não me preocupo com os detalhes subjacentes da exibição propriamente dita.

Criando uma exibição
É claro que acaba sendo necessário que eu gere algum HTML, então criemos essa exibição HiThere. Para isso, primeiramente crio uma nova pasta na solução chamada Hello dentro da pasta Views. Por padrão, o controlador irá procurar uma exibição na pasta Views\<ControllerPrefix> (o prefixo do controlador é o nome da classe do controlador menos a palavra "Controller"). Dessa forma, para exibições renderizadas pelo HelloController, ele pesquisa em Views\Hello. A solução acaba parecida com a Figura 4.
Figure 4 Adicionando uma exibição ao projeto (Clique na imagem para aumentar a exibição)
O HTML da exibição é semelhante a este:
<html  >
<head runat="server">
    <title>Hi There!</title>
</head>
<body>
    <div>
        <h1>Hello, <%= ViewData["Name"] %></h1>
    </div>
</body>
</html>
Várias coisas devem lhe chamar a atenção. Não há nenhuma marca runat="server". Não há nenhuma marca form. Não há nenhuma declaração de controle. Na verdade, ele se parece muito mais com o ASP clássico do que com o ASP.NET. Observe que por serem responsáveis apenas por gerar a saída, as exibições do MVC não precisam de nenhum dos controles de manipulação de evento ou complexos dos quais as páginas dos Web Forms precisam.
A MVC Framework não empresta o formato de arquivo .aspx como uma linguagem de modelagem de texto útil. Você pode até mesmo usar o code-behind caso queira, mas o arquivo code-behind é, por padrão, semelhante a este:
using System;
using System.Web;
using System.Web.Mvc;

namespace HelloFromMVC.Views.Hello
{
    public partial class HiThere : ViewPage
    {
    }
}
Nada de métodos Init ou de carregamento na página, nada de manipuladores de eventos, nada senão a declaração da classe base, que não é Page, e sim ViewPage. Esse é todo o necessário para que haja uma exibição do MVC. Execute o aplicativo, navegue até http://localhost:<port>/Hello/HiThere/Chris, e você verá algo semelhante à Figura 5.
Figure 5 Exibição bem-sucedida do MVC (Clique na imagem para aumentar a exibição)
Caso, em lugar da Figura 5, você veja uma exceção desagradável, não entre em pânico. Se o arquivo HiThere.aspx estiver definido como sendo o documento ativo no Visual Studio, quando você pressionar F5, o Visual Studio tentará acessar diretamente o arquivo .aspx. Como as exibições do MVC exigem a execução do controlador antes da exibição, tentar navegar diretamente até a página não funcionará. Basta editar a URL de acordo com o que você vê na Figura 5, e isso deve funcionar.
Como a MVC Framework sabe chamar o meu método de ação? Sequer há uma extensão de arquivo para essa URL. A resposta é roteamento de URL. Se vir dentro do arquivo global.asax.cs, você verá a parte do código na Figura 6. A RouteTable global armazena uma coleção de objetos Route. Cada Route descreve um formato de URL e o que fazer com ele. Por padrão, duas rotas são adicionadas à tabela. É a primeira quem faz a mágica. Ela diz que, para cada URL consistindo em três partes após o nome do servidor, a primeira parte deve ser considerada um nome de controlador, a segunda como um nome de ação e a terceira, como o parâmetro ID:
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        // Change Url= to Url="[controller].mvc/[action]/[id]" 
        // to enable automatic support on IIS6 

        RouteTable.Routes.Add(new Route
        {
            Url = "[controller]/[action]/[id]",
            Defaults = new { action = "Index", id = (string)null },
            RouteHandler = typeof(MvcRouteHandler)
        });

        RouteTable.Routes.Add(new Route
        {
            Url = "Default.aspx",
            Defaults = new { 
                controller = "Home", 
                action = "Index", 
                id = (string)null },
            RouteHandler = typeof(MvcRouteHandler)
        });
    }
}

Url = "[controller]/[action]/[id]"
Foi essa rota padrão que permitiu que o meu método HiThere fosse invocado. Lembre-se desta URL: http://localhost/Hello/HiThere/Chris? Essa rota mapeou Hello para o controlador, HiThere para a ação e Chris para a identificação. Em seguida, a MVC Framework criou uma instância de HelloController, chamou o método HiThere e passou Chris como sendo o valor do parâmetro ID.
Essa rota padrão lhe oferece muito, mas também é possível adicionar rotas próprias. Por exemplo, quero um site realmente amigável em que as pessoas só precisem digitar seu nome para receber uma saudação personalizada. Caso adicione esta rota à parte superior da tabela de roteamento
  RouteTable.Routes.Add(new Route
  {
    Url = "[id]",
    Defaults = new { 
        controller = "Hello", 
        action = "HiThere" },
    RouteHandler = typeof(MvcRouteHandler)
  });
posso simplesmente ir para http://localhost/Chris, e a minha ação continua sendo invocada, e eu vejo a minha saudação amigável já conhecida.
Como o sistema sabia qual controlador e ação invocar? A resposta está no parâmetro Defaults. Ele usa a nova sintaxe de tipo anônimo do C# 3.0 para criar um pseudodicionário. O objeto Defaults em Route pode conter informações adicionais arbitrárias, mas também pode conter algumas entradas bem conhecidas do MVC: controlador e ação. Se não houver nenhum controlador ou ação especificada na URL, ele usará o nome em Defaults. É por isso que posso excluí-las da URL e, mesmo assim, continuar tendo a minha solicitação mapeada para o controlador e a ação correta.
Mais uma coisa a ser observada: lembra-se de que eu disse "adicionar à parte superior da tabela"? Se colocar na parte inferior, você obterá um erro. O roteamento funciona de acordo com a idéia de que o primeiro a chegar é o primeiro a ser atendido. Ao processar URLs, o sistema de roteamento percorre a tabela da parte superior para a parte inferior e a primeira rota correspondente ganha. Nesse caso, a rota padrão "[controller]/[action]/[id]" corresponde porque há valores padrão para a ação e a identificação. Por isso, ele procura ChrisController e, como não há um controlador, recebo um erro.

Um exemplo maior
Agora que demonstrei os fundamentos da MVC Framework, gostaria de lhe apresentar um exemplo maior, que vai além da exibição de uma cadeia de caracteres. Um wiki é um site que pode ser editado no navegador. As páginas podem ser adicionadas ou editadas facilmente. Escrevi um pequeno wiki de amostra usando a MVC Framework. A tela "Edit this page" é mostrada na Figura 7.
Figure 7 Editando a home page (Clique na imagem para aumentar a exibição)
É possível verificar o download do código referente a este artigo para ver como a lógica subjacente do wiki é implementada. Agora quero me concentrar em como a MVC Framework facilitou a inserção do wiki na Web. Comecei criando a estrutura da minha URL. Eu queria o seguinte:
  • /[pagename] exibe a página com esse nome.
  • /[pagename]?version=n mostra a versão solicitada da página, em que 0 = a versão atual, 1 = a versão anterior etc.
  • /Edit/[pagename] abre a tela de edição da página.
  • /CreateNewVersion/[pagename] é a URL postada para que uma edição seja enviada.
Comecemos com a exibição básica de uma página wiki. Para isso, criei uma nova classe chamada WikiPageController. Em seguida, adicionei uma ação chamada ShowPage. O WikiPageController começa a se assemelhar à Figura 8. O método ShowPage é bastante simples. As classes WikiSpace e WikiPage representam um conjunto de páginas wiki e uma página específica (e suas revisões), respectivamente. Essa ação apenas carrega o modelo e chama RenderView. Mas o que é aquela "nova linha WikiPageViewData" lá?
public class WikiPageController : Controller 
{
  ISpaceRepository repository;

  public ISpaceRepository Repository 
  {
    get {
      if (repository == null) 
      {
        repository = new FileBasedSpaceRepository(
            Request.MapPath("~/WikiPages"));
      }
      return repository;
    }

    set { repository = value; }
  }

  [ControllerAction]
  public void ShowPage(string pageName, int? version) 
  {
    WikiSpace space = new WikiSpace(Repository);
    WikiPage page = space.GetPage(pageName);

    RenderView("showpage", 
      new WikiPageViewData 
      { 
        Name = pageName,
        Page = page,
        Version = version ?? 0 
      });
  }
}

O meu exemplo anterior demonstrou uma forma de passar os dados do controlador para a exibição: o dicionário ViewData. Embora sejam práticos, os dicionários também são perigosos. Eles podem conter absolutamente qualquer coisa. Você não tem nada de IntelliSense® no conteúdo e, como o dicionário ViewData é do tipo Dictionary<string, object>, para usar o seu conteúdo você precisa converter tudo.
Quando sabe de quais dados precisará na exibição, você pode, na verdade, passar o objeto ViewData de tipo acentuado. No meu caso, criei um objeto simples, WikiPageViewData, como mostrado na Figura 9. Esse objeto transporta as informações da página wiki até a exibição com alguns métodos de utilitário para fazer coisas como obter a versão HTML da marcação wiki.
public class WikiPageViewData {

    public string Name { get; set; }
    public WikiPage Page { get; set; }
    public int Version { get; set; }

    public WikiPageViewData() {
        Version = 0;
    }

    public string NewVersionUrl {
        get {
            return string.Format("/CreateNewVersion/{0}", Name);
        }
    }

    public string Body {
        get { return Page.Versions[Version].Body; }
    }

    public string HtmlBody {
        get { return Page.Versions[Version].BodyAsHtml(); }
    }

    public string Creator {
        get { return Page.Versions[Version].Creator; }
    }

    public string Tags {
        get { return string.Join(",", Page.Versions[Version].Tags); }
    }
}

Agora que consegui definir os dados de exibição, como eu os uso? Em ShowPage.aspx.cs, você verá:
namespace MiniWiki.Views.WikiPage {
    public partial class ShowPage : ViewPage<WikiPageViewData>
    {
    }
}
Observe que eu defini a classe base para ser do tipo ViewPage<WikiPageViewData>. Isso significa que a propriedade ViewData da página é do tipo WikiPageViewData, e não Dictionary como no exemplo anterior.
A marcação real no arquivo .aspx é bem simples:
<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
  AutoEventWireup="true" CodeBehind="ShowPage.aspx.cs" 
  Inherits="MiniWiki.Views.WikiPage.ShowPage" %>
<asp:Content 
  ID="Content1"  
  ContentPlaceHolderID="MainContentPlaceHolder" 
  runat="server">
  <h1><%= ViewData.Name %></h1>
  <div id="content" class="wikiContent">
    <%= ViewData.HtmlBody %>
  </div>
</asp:Content>
Observe que eu não estou usando o operador de indexação [] ao referenciar a ViewData. Na verdade, como agora tenho um ViewData de tipo acentuado, basta acessar a propriedade diretamente. Não é necessária nenhuma conversão, e o Visual Studio lhe oferece IntelliSense.
O observador mais atento já terá percebido a marca <asp:Content> no arquivo. Sim, as páginas mestras funcionam com exibições do MVC. E as páginas mestras também podem ser exibições. Vejamos o code-behind da página mestra:
namespace MiniWiki.Views.Layouts
{
    public partial class Site :  
        System.Web.Mvc.ViewMasterPage<WikiPageViewData>
    {
    }
}
A marcação associada é mostrada na Figura 10. Agora, a página mestra tem exatamente o mesmo objeto ViewData da exibição. Como declarei que a classe base da página mestra deve ser ViewMasterPage<WikiPageViewData>, tenho o tipo apropriado de ViewData. Lá, configuro as várias marcas DIV para o layout da minha página, preencho a lista de versões e termino com o habitual espaço reservado do conteúdo.
<%@ Master Language="C#" 
  AutoEventWireup="true" 
  CodeBehind="Site.master.cs" 
  Inherits="MiniWiki.Views.Layouts.Site" %>
<%@ Import Namespace="MiniWiki.Controllers" %>
<%@ Import Namespace="MiniWiki.DomainModel" %>
<%@ Import Namespace="System.Web.Mvc" %>
<html >
<head runat="server">
  <title><%= ViewData.Name %></title>
  <link href="http://../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div id="inner">
    <div id="top">
      <div id="header">
        <h1><%= ViewData.Name %></h1>
      </div>
      <div id="menu">
        <ul>
          <li><a href="http://Home">Home</a></li>
          <li>
            <%= Html.ActionLink("Edit this page", 
                  new { controller = "WikiPage", 
                        action = "EditPage", 
                        pageName = ViewData.Name })%>
        </ul>
      </div>
    </div>
    <div id="main">
      <div id="revisions">
        Revision history:
        <ul>
          <% 
            int i = 0;
            foreach (WikiPageVersion version in ViewData.Page.Versions)
            { %>
              <li>
                <a href="http://<%= ViewData.Name %>?version=<%= i %>">
                  <%= version.CreatedOn %>
                  by
                  <%= version.Creator %>
                </a>
              </li>
          <%  ++i;
          } %>
        </ul>
      </div>
      <div id="maincontent">
        <asp:ContentPlaceHolder 
          ID="MainContentPlaceHolder" 
          runat="server">
        </asp:ContentPlaceHolder>
      </div>
    </div>
  </div>
</body>
</html>

Outra coisa a ser observada é a chamada para Html.ActionLink. Trata-se de um exemplo de um auxiliar de renderização. As várias classes de exibição têm duas propriedades, Html e Url. Cada uma tem métodos úteis de produzir partes de HTML. Nesse caso, Html.ActionLink escolhe um objeto (aqui, de tipo anônimo) e o executa em todo o sistema de roteamento. Isso produz uma URL que será roteada até o controlador e a ação que especifiquei. Dessa forma, não importa como altero minhas rotas, pois o link "Edit this page" apontará sempre para o local certo.
Você talvez também perceba que eu também tive de recorrer à criação manual de um link (os links para as versões de página anteriores). Infelizmente, o sistema de roteamento atual não funciona tão bem para gerar URLs quando há cadeias de caracteres de consulta envolvidas. Isso deve ser corrigido nas versões posteriores da estrutura.

Criando formulários e executando postback
Agora, vejamos a ação EditPage no controlador:
[ControllerAction]
public void EditPage(string pageName)
{
  WikiSpace space = new WikiSpace(Repository);
  WikiPage page = space.GetPage(pageName);

  RenderView("editpage", 
    new WikiPageViewData { 
      Name = pageName, 
      Page = page });
}
Novamente, a ação não faz muita coisa – apenas renderiza a exibição usando a página dada. As coisas ficam mais interessantes na exibição, mostrada na Figura 11. O arquivo está criando um formulário em HTML, mas não há nenhum Runat="server" a ser visto. O auxiliar Url.Action é usado para gerar a URL em que há o postback do formulário. Há também vários usos de vários auxiliares HTML como TextBox, TextArea e SubmitButton. Eles fazem bem aquilo que você esperava: gerar HTML para vários campos de entrada.
<%@ Page Language="C#" 
  MasterPageFile="~/Views/Shared/Site.Master" 
  AutoEventWireup="true" 
  CodeBehind="EditPage.aspx.cs" 
  Inherits="MiniWiki.Views.WikiPage.EditPage" %>
<%@ Import Namespace="System.Web.Mvc" %>
<%@ Import Namespace="MiniWiki.Controllers" %>
<asp:Content ID="Content1" 
  ContentPlaceHolderID="MainContentPlaceHolder" 
  runat="server">
  <form action="<%= Url.Action(
    new { controller = "WikiPage", 
    action = "NewVersion", 
    pageName = ViewData.Name })%>" method=post>
    <%
      if (ViewContext.TempData.ContainsKey("errors"))
      {
    %>
    <div id="errorlist">
      <ul>
      <%
        foreach (string error in 
          (string[])ViewContext.TempData["errors"])
        {
      %>
        <li><%= error%></li>
      <% } %>
      </ul>
    </div>
    <% } %>
    Your name: <%= Html.TextBox("Creator",
                   ViewContext.TempData.ContainsKey("creator") ? 
                   (string)ViewContext.TempData["creator"] : 
                   ViewData.Creator)%>
    <br />
    Please enter your updates here:<br />
    <%= Html.TextArea("Body", ViewContext.TempData.ContainsKey("body") ? 
        (string)ViewContext.TempData["body"] : 
        ViewData.Body, 30, 65)%>
    <br />
    Tags: <%= Html.TextBox(
              "Tags", ViewContext.TempData.ContainsKey("tags") ? 
              (string)ViewContext.TempData["tags"] : 
              ViewData.Tags)%>
    <br />
    <%= Html.SubmitButton("SubmitAction", "OK")%>
    <%= Html.SubmitButton("SubmitAction", "Cancel")%>
  </form>
</asp:Content>

Na programação Web, uma das coisas mais chatas é lidar com erros em um formulário. Mais especificamente, você deseja exibir mensagens de erro, mas quer manter os dados inseridos anteriormente. Todos nós já tivemos a experiência de cometer um erro em um formulário com 35 campos e sermos apresentados a várias mensagens de erro, além de um novo formulário, em branco. A MVC Framework oferece TempData como um local para armazenar as informações inseridas anteriormente para que o formulário possa ser preenchido novamente. Isso é uma coisa que, na verdade, ViewState simplificou muito nos Web Forms, uma vez que salvar o conteúdo dos controles era algo bastante automático.
Eu também gostaria de fazer isso no MVC, e é onde entra TempData. TempData é um dicionário, bem parecido com ViewData sem tipo. No entanto, o conteúdo de TempData só permanece durante uma única solicitação, sendo excluído em seguida. Para ver como ele é usado, observe a Figura 12, a ação NewVersion.
[ControllerAction]
public void NewVersion(string pageName) {
  NewVersionPostData postData = new NewVersionPostData();
  postData.UpdateFrom(Request.Form);

  if (postData.SubmitAction == "OK") {
    if (postData.Errors.Length == 0) {
      WikiSpace space = new WikiSpace(Repository);
      WikiPage page = space.GetPage(pageName);
      WikiPageVersion newVersion = new WikiPageVersion(
        postData.Body, postData.Creator, postData.TagList);
      page.Add(newVersion);
    } else {
      TempData["creator"] = postData.Creator;
      TempData["body"] = postData.Body;
      TempData["tags"] = postData.Tags;
      TempData["errors"] = postData.Errors;

      RedirectToAction(new { 
        controller = "WikiPage", 
        action = "EditPage", 
        pageName = pageName });
      return;
    }
  }

  RedirectToAction(new { 
    controller = "WikiPage",
    action = "ShowPage", 
    pageName = pageName });
}

Primeiramente, ela cria um objeto NewVersionPostData. Trata-se de outro objeto auxiliar que tem propriedades e métodos que armazenam o conteúdo da postagem e de parte da validação. Para carregar postData object, estou usando uma função auxiliar do MVC Toolkit. UpdateFrom é, na verdade, um método de extensão fornecido pelo kit de ferramentas e usa a reflexão para comparar os nomes dos campos de formulário com os nomes das propriedades no meu objeto. O resultado final é que todos os valores do campo são carregados no meu objeto postData. No entanto, usar UpdateFrom não tem a desvantagem de obter os dados diretamente de HttpRequest, o que dificulta ainda mais os testes de unidade.
A primeira coisa que NewVersion verifica é SubmitAction. Não haverá problemas se o usuário clicar no botão OK e realmente quiser postar a página editada. Caso haja qualquer outro valor aqui, a ação acaba sendo redirecionada para ShowPage, que apenas exibe novamente a página original.
Caso o usuário tenha clicado em OK, verifico a propriedade postData.Errors. Isso executa algumas validações simples no conteúdo da postagem. Caso haja algum erro, realizo o processamento para escrever a nova versão da página novamente no wiki. No entanto, em caso de erro, as coisas ficam mais interessantes.
Se houver algum erro, defino os vários campos do dicionário TempData para que ele apresente o conteúdo de PostData. Em seguida, redireciono novamente para a página Edit. Agora, como TempData está definido, a página será novamente exibida tendo o formulário inicializado com os valores postados pelo usuário na última vez.
Esse processo de lidar com postagens, validações e TempData é um pouco mais importante agora e exige um pouco mais de trabalho manual do que o realmente necessário. As versões futuras devem incluir métodos auxiliares que automatizem pelo menos parte da verificação de TempData. Uma última observação sobre TempData: o conteúdo de TempData é armazenado na sessão do servidor do usuário. Se você desativar a sessão, TempData não funcionará.

Criação do controlador
Agora os fundamentos do wiki estão funcionando, mas há alguns aspectos da implementação que eu gostaria de esclarecer antes de continuarmos. Por exemplo, a propriedade Repository é usada para desacoplar a lógica do wiki com o armazenamento físico. É possível fornecer repositórios que armazenam o conteúdo no sistema de arquivos (assim como fiz aqui), em um banco de dados ou em qualquer outro lugar de sua preferência. Infelizmente, tenho dois problemas para resolver.
Primeiro, a minha classe de controlador está rigidamente acoplada à classe concreta FileBasedSpaceRepository. Preciso ter um padrão para que, caso a propriedade não esteja definida, eu tenha algo razoável para usar. E o que é pior, o caminho dos arquivos no disco aqui também está codificado. Na pior das hipóteses, isso deve vir da configuração.
Segundo, Repository é realmente uma dependência necessária; o meu objeto não será executado sem ele. Um bom design indica que o repositório deva ser, de fato, um parâmetro de construtor, e não uma propriedade. Mas não posso adicioná-lo ao construtor porque a MVC Framework exige um construtor sem argumentos nos controladores.
Felizmente, existe um gancho de extensibilidade capaz de me tirar dessa vinculação: o alocador de controlador. Um alocador de controlador faz bem aquilo que seu nome diz: cria instâncias do controlador. Basta você criar uma classe que implemente a interface IControllerFactory e a registre usando o sistema do MVC. É possível registrar alocadores de controlador para todos os controladores ou apenas para tipos específicos. A Figura 13 mostra um alocador de controlador de WikiPageController, passando o repositório agora como um parâmetro de construtor.
public class WikiPageControllerFactory : IControllerFactory {

  public IController CreateController(RequestContext context, 
    Type controllerType)
  {
    return new WikiPageController(
      GetConfiguredRepository(context.HttpContext.Request));
  }

  private ISpaceRepository GetConfiguredRepository(IHttpRequest request)
  {
    return new FileBasedSpaceRepository(request.MapPath("~/WikiPages"));
  }
}

Nesse caso, a implementação é bem trivial, embora possa habilitar os controladores de criação que usam ferramentas muito mais eficientes (em especial, os contêineres de injeção de dependência). Em qualquer evento, agora tenho todos os detalhes da separação das dependências do controlador em um objeto que seja mais fácil de gerenciar e manter.
A última etapa na realização deste trabalho é registrar o alocador na estrutura. Faço isso por meio da classe ControllerBuilder adicionando a seguinte linha a Global.asax.cs no método Application_Start (antes ou depois das rotas):
ControllerBuilder.Current.SetControllerFactory(
  typeof(WikiPageController), typeof(WiliPageControllerFactory));
Isso registrará um alocador em WikiPageController. Se eu tivesse outros controladores neste projeto, eles usariam esse alocador, uma vez que ele está registrado apenas para o tipo WikiPageController. Também é possível chamar SetDefaultControllerFactory caso você queira definir um alocador a ser usado em todos os controladores.

Outros pontos de extensibilidade
O alocador de controlador é apenas o começo da extensibilidade da estrutura. Como não tenho espaço neste artigo para entrar em detalhes sobre todos eles, abordarei apenas os destaques. Primeiro, caso você queira produzir algo que não seja HTML ou usar um mecanismo de modelagem que não seja os Web Forms, é possível definir ViewFactory do controlador como outra coisa. Você pode implementar a interface IViewFactory, e terá um controle completo sobre como a saída é gerada. Isso é muito útil na geração de RSS, XML ou até mesmo de gráficos.
O sistema de roteamento é bastante flexível, como você já viu. Mas não há nada no sistema de roteamento que seja específico do MVC. Todas as rotas têm uma propriedade RouteHandler; até aqui, sempre a defini como MvcRouteHandler. Mas é possível implementar a interface IRouteHandler e conectar o sistema de roteamento às demais tecnologias da Web. Uma parte futura da estrutura será acompanhada de WebFormsRouteHandler, e as demais tecnologias usufruirão o sistema de roteamento genérico.
Os controladores não precisam derivar de System.Web.Mvc.Controller. Tudo o que um controlador precisa fazer é implementar a interface IController, que tem apenas um único método chamado Execute. Lá, é possível fazer tudo o que você quiser. Por outro lado, caso queira apenas ajustar alguns comportamentos da classe Controller base, ela tem muitas funções virtuais que podem ser substituídas:
  • OnPreAction, OnPostAction e OnError permitem que você conecte o pré e o pós-processamento genérico em todas as ações executadas. OnError lhe oferece um mecanismo para manipulação de erros em todo o controlador.
  • HandleUnknownAction é chamado quando uma URL é roteada até o controlador, mas este não implementa a ação solicitada na rota. Por padrão, esse método lança uma exceção, mas é possível substituí-la para fazer aquilo que você deseja.
  • InvokeAction é o método que define o método de ação a ser chamado e o chama. Caso queira personalizar o processo (por exemplo, para se livrar do requisito dos atributos [ControllerAction]), este é o lugar para isso.
Há muitos outros métodos virtuais em Controller, mas eles estão lá essencialmente como ganchos de teste, e não como pontos de extensão. Por exemplo, RedirectToAction é virtual para que seja possível criar uma classe derivada não redirecionada de fato. Isso lhe permite testar ações redirecionadas sem a necessidade de uma execução completa do servidor Web.

O adeus aos Web Forms?
Nesse momento, você deve estar se perguntando "O que está acontecendo com os Web Forms? O MVC está os substituindo?" A resposta é não! Os Web Forms formam uma tecnologia bem compreendida, e a Microsoft continuará dando suporte a ela e a aprimorando. Há muitos aplicativos nos quais os Web Forms funcionam muito bem. O aplicativo de relatórios do banco de dados da intranet típico, por exemplo, pode ser criado usando-se os Web Forms em uma fração do tempo que seria necessário para escrevê-lo no MVC. Além disso, os Web Forms dão suporte a um amplo mercado de controles, muitos dos quais sendo extremamente sofisticados e que economizam muito trabalho.
Então, quando você deve optar pelo MVC em detrimento aos Web Forms? Em muitos aspectos, ele atende a seus requisitos e preferências. Está brigando para ter as URLs formadas da forma que você quer? Deseja executar um teste de unidade na interface do usuário? Qualquer um desses cenários levaria ao MVC. Por outro lado, terá muitos dados exibidos, com grades editáveis e controles de exibição de árvore especiais? Então, por enquanto, é melhor você continuar usando os Web Forms.
Com o passar do tempo, a MVC Framework deverá fazer parte do departamento de controle da interface do usuário, mas é mais provável que sua inicialização não seja tão fácil quanto à dos Web Forms, em que inúmeras funcionalidades estavam a uma operação arrastar-e-soltar. Enquanto isso, a ASP.NET MVC Framework oferece aos desenvolvedores para a Web uma nova forma de criar aplicativos Web no Microsoft .NET Framework. A Framework foi projetada tendo em vista a capacidade de teste, adota HTTP, e não tenta abstraí-lo, além de ser extensível a praticamente qualquer ponto. Trata-se de um complemento atrativo para os Web Forms destinado aos desenvolvedores que desejam ter controle completo sobre os aplicativos Web.

Chris Tavares é desenvolvedor da equipe de diretrizes da Microsoft, onde trabalha para ajudar a comunidade de desenvolvimento a compreender as práticas recomendadas para a criação de sistemas em plataformas Microsoft. Ele também é membro virtual da equipe do ASP.NET MVC, ajudando na criação da nova estrutura. Chris pode ser contatado pelo email cct@tavaresstudios.com.

Page view tracker