ASP.NET MVC ASP.NET MVC é um framework para desenvolvimento de aplicações web que usa os padrões MVC (Model View Controller) e Convention over Configuration. O padrão MVC aplicado a aplicações web conduz a uma separação de responsabilidades. Esta separação explícita de responsabilidades adiciona uma pequena quantidade de complexidade extra ao projecto de uma aplicação, mas os benefícios compensam o esforço extra. Modelos: são classes que representam o domínio. Os objectos do domínio encapsulam os dados armazenados numa base de dados assim como o código usado para manipular esses dados e para aplicar lógica de negócio específica do domínio. Incluiem a camada de acesso a dados (Data Access Layer – DAL) podendo usar uma ferramenta como Entity Framework. Vistas: são templates para gerar dinamicamente HTML. Controladores: classes que respondem às interacções do utilizador, acedem ao Modelo e decidem que Vista usar. Em Asp.Net estas classes têm o sufixo Controller. O framework ASP‐NET MVC usa convenções em vez de configurações (Convention over Configuration), o que permite fazer assunções baseadas nos nomes dos diretórios. O uso de convenções reduz a quantidade de código que é necessário escrever, e também facilita a compreensão do projecto por outros desenvolvedores. Construção de uma aplicação ASP.NET MVC4 File > New Project > Visual C#, Web, ASP.NET MVC4 Web Application Name: MvcApplication1 Project Template: Internet Application – contém o princípio de uma aplicação Web MVC, incluindo funções de gestão de contas (que correm no sistema Membership Asp.Net). É possível correr a aplicação logo após ser criada. View Engine: linguagem usada para gerar código HTML. Duas opções: Razor View Engine ou ASPX View Engine. Selecionar Razor. Os projectos Asp.Net MVC criados com o template “Internet Application” apresentam 9 directórios top‐level: /Controllers ‐ para colocar classes Controller que tratam os pedidos. /Models ‐ para colocar classes que representam e manipulam os dados e objectos de negócio. /Views ‐ para colocar ficheiros template responsáveis por gerar output, tal como HTML. /Scripts ‐ para colocar ficheiros JavaScript. /Images ‐ para colocar imagens usadas no site. /Content ‐ para colocar código CSS e outro conteúdo do site sem ser scripts e imagens. /Filters ‐ para colocar código de filtros. /App_Data ‐ para colocar ficheiros de bases de dados para leitura e escrita. /App_Start ‐ para colocar ficheiros de configuração para Routing, Bundling, e API Web. É possível mudar esta estrutura, por exemplo colocar as classes do modelo num projecto class library separado. Contudo esta estrutura do projecto corresponde à convenção dos directórios por omissão. O directório /Controllers contém 2 classes Controller: HomeController e AccountController. O directório /Views contém 3 subdirectórios: Home, Account, e Shared. Os directórios /Content e /Script contêm um ficheiro Site.css e bibliotecas JavaScript ‐ jQuery. 1. Controladores Os controladores respondem aos pedidos HTTP e retornam informação para o browser. Recebem os dados de entrada, muitas vezes efectuam mudanças no modelo e fornecem dados para a vista. São responsáveis pelo fluxo da aplicação. Nos frameworks Web tradicionais um URL era mapeado num ficheiro do disco do servidor web. Por exemplo um pedido feito com o URL “/Produtos.aspx” ou “/Produtos.php” é processado pelos ficheiros “Produtos.aspx” ou “Produtos.php”. Em MVC o mecanismo de routing mapeia um URL num método de uma classe Controller. O mecanismo de routing decide que classe Controller instanciar, que método de acção invocar, e fornece os argumentos necessários a esse método. O método de acção do controlador decide que vista usar e a vista gera o HTML. Asp.Net MVC implementa a variante Front Controller do padrão MVC. Analisemos o que está incluído no projecto criado usando o template Internet Application. 2 Controladores, subclasses da classe Controller: HomeController – responsável pelas “home page”, “about page” e “contact page” na raiz do website. AccountController – responsável por login e registo. F5 – Start Debugging HomeController O método Index é responsável pela home page: Colocar no IE o endereço ou ou http://localhost:xxx/ http://localhost:xxx/Home http://localhost:xxx/Home/Index Criar um novo Controlador Controllers > Add > Controller… > StoreController Empty MVC Controller O Controlador já tem um método Index. Suponhámos que o método Index será para listar os itens, vamos acrescentar mais 2 métodos: Browse para pesquisar itens e Details para mostrar os detalhes de um item. Estes métodos dentro do controlador são chamados métodos de Acção – têm a responsabilidade de responder aos pedidos, realizar as acções apropriadas e retornar a resposta para o browser. 1. Mudar a assinatura do método Index() para retornar uma string public string Index() { return "Olá de Store/Index()"; } 2. Adicionar métodos de acção para Browse e Details. // GET: /Store/ public string Index() { return "Olá de Store/Index()"; } // GET: /Store/Browse public string Browse() { return "Olá de Store/Browse()"; } // GET: /Store/Details public string Details() { return "Olá de Store/Details()"; } 3. Correr o projecto. Não usámos modelos nem vistas. http://localhost:1755/Store http://localhost:1755/Store/Browse http://localhost:1755/Store/Details 4. Parâmetros nos Action Methods de um Controlador Vamos modificar o método de acção Browse() para receber um valor de uma query string // GET: /Store/Browse?genero=Disco public string Browse(string genero) { return ("Olá de Store/Browse(), Género = " + genero; } Testar: http://localhost:1755/Store/Browse http://localhost:1755/Store/Browse?genero=Disco Vamos modificar o método de acção Details() para receber um parâmetro com o nome ID do tipo inteiro. // GET: /Store/Details/5 public string Details(int id) { return "Olá de Store/Details(), ID = " + id; } Testar: http://localhost:1755/Store/Details ‐ dá erro. http://localhost:1755/Store/Details?id=5 http://localhost:1755/Store/Details/5 É como se o browser invocasse directamente métodos no Controlador. Modificar o método de acção Details() colocando um valor por omissão para o parâmetro com o nome ID. // GET: /Store/Details/5 public string Details(int id = 0) { return "Olá de Store/Details(), ID = " + id; } Testar: http://localhost:1755/Store/Details ‐ já não dá erro. 2. Vistas A primeira impressão que um utilizador tem de uma Aplicação Web depende da vista e toda a interacção com a aplicação é realizada através de vistas. Os controladores e os modelos são muito importantes mas esse trabalho não é visível. Já vimos como um controlador poderia retornar uma string, mas a maior parte das acções dos controladores necessitam de mostrar informação dinâmica no formato HTML. A vista é responsável por construir a interface com o utilizador (UI). Recebe o modelo (a informação que o controlador necessita de mostrar) e a vista transforma esse modelo num formato próprio para ser apresentado ao utilizador. As vistas não são directamente acessíveis pelo browser. Uma vista é sempre invocada pelo controlador que lhe fornece os dados. Convenções em Asp.Net MVC As aplicações Asp.Net MVC, por omissão, funcionam com base em convenções. Isto evita ter de especificar tudo o que pode ser inferido através de convenções. MVC usa uma estrutura de nomes de directórios baseados em convenções que permite determinar os templates das vistas referidos pelos controladores sem especificar a localização. O ficheiro com o template da vista referido num método de um controlador encontra‐se no directório \Views\<Nome_do_Controlador>\ e tem o nome igual ao método do qual é invocado. As convenções do MVC podem ser substituídas quando necessário. Este conceito é designado por “convenções em vez de configurações” (convention over configuration). Este conceito foi introduzido pelo Ruby on Rails. Convenções Asp.Net MVC: O nome de cada classe Controlador termina em Controller, e é colocada no directório Controllers. Há um único directório Views para todas as vistas da aplicação. As vistas que cada Controlador usa são colocadas num subdirectório de Views com o nome do Controlador (excepto o sufixo Controller). Todas as vistas partilhadas são colocadas no subdirectório de Views com o nome Shared. Especificação das Vistas O directório Views contém um subdirectório por controlador, com o mesmo nome do controlador, mas sem o sufixo controller. Dentro de cada um destes subdirectórios existe um ficheiro view para cada método de acção, com o mesmo nome do método de acção. Este é o processo base de associação de vistas a métodos de acção. Exemplo1: Se o método de acção do controlador não especificar o nome da vista aplica‐se a convenção para localizar o objecto ViewResult retornado pelo método View(): é retornada uma vista com o mesmo nome do método de acção dentro do directório Views/Nome_do_Controlador. Para o método /Store/Index a vista seleccionada será /Views/Store/Index.cshtml Modificar o método Index do Controlador StoreController: public class StoreController : Controller { // GET: /Store/ public ActionResult Index() { ViewBag.Mensagem = "Mensagem de Store/Index"; return View(); } . . . } Botão direito do rato em cima do código deste método e Add View... View name: Index Retirar a selecção a Use a Layout or master page Obtém‐se o seguinte ficheiro: @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device‐width" /> <title>Index</title> </head> <body> <div> </div> </body> </html> Acrescentar no div do body: <h1>@ViewBag.Mensagem</h1> Executar: http://localhost:1755/Store/ A vista mostra a mensagem colocada pelo controlador usando a expressão ViewBag.Mensagem. Exemplo2: Se pretendermos que o método de acção /Store/Index retornasse uma vista diferente colocada no subdirectório Store (por exemplo Teste.cshtml) temos de fornecer o nome da vista. Copiar o ficheiro Index.cshtml, colocar no mesmo directório com o nome Teste.cshtml, e fazer a modificação. <body> <div> <h1>Da vista Teste: @ViewBag.Mensagem</h1> </div> </body> No Index de StoreController alterar: public ActionResult Index() { ViewBag.Mensagem = "Mensagem de Store/Index"; return View("Teste"); } Executar. Exemplo3: Se pretendermos que o método de acção /Store/Index retornasse uma vista diferente colocada num subdirectório diferente de Store (por exemplo Teste.cshtml em /Views/Home/) temos de fornecer o nome completo da vista usando sintaxe com ~. Nesta sintaxe com ~ também temos de fornecer a extensão porque não é usado o mecanismo interno de procura de vistas. Mudar a localização do ficheiro Teste.cshtml para Views/Home Executar. Dá erro. Alterar para return View("~/Views/Home/Teste"); Alterar para return View("~/Views/Home/Teste.cshtml"); Executar. Dá erro. Executar. Não dá erro Vistas fortemente Tipadas Para passar um objecto modelo para a vista, podemos colocá‐lo como argumento do método View(). Na vista este objecto é colocado na propriedade Model. Mas esta propriedade é do tipo dinâmico, isto é, o seu tipo de dados só é determinado em tempo de execução. Podemos usá‐la deste modo mas também é possível atribuir‐lhe o tipo de dados correcto. Atribuindo o tipo correcto beneficiamos do Intellisense do Visual Studio quando acedemos às propriedades do objecto, e também às verificações em tempo de compilação (compile‐time checking). Para que lhe seja atribuído o tipo de dados correcto temos de indicar à vista o tipo de dados da propriedade Model com a seguinte declaração: @model IEnumerable<MvcApplication1.Models.Album> View Models Muitas vezes uma vista tem necessidade de mostrar informação que não mapeia directamente num objecto do modelo do domínio. Como na vista só podemos ter um objecto Model, uma possibilidade para mostrar informação extra que não faça parte do modelo da vista é enviá‐la através do ViewBag. Embora seja uma solução flexível, os dados obtidos pela vista através do VIewBag não são fortemente tipados, pelo que o programador da vista não terá a vantagem do intellisense do Visual Studio. Assim, pode ser útil construir uma classe para agregar toda a informação necessária para fornecer à vista, designada por ViewModel, um modelo (classe) construído com o único objectivo de fornecer informação para a vista. Uma classe ViewModel serve para encapsular múltiplos dados representados por instâncias de outras classes num objecto para passar à Vista. Não há nenhuma localização pré‐defenida para colocar os ViewModels. Normalmente cria‐se um directório ViewModels na raiz do projecto MVC. Nesse directório colocam‐se todos os ViewModels, um ficheiro para cada classe, com a designação do nome do Controlador seguida do nome do método de acção (ou Vista). Exemplo1: MvcApplication1 > Add > New Folder: ViewModels ViewModels/StoreIndexViewModel.cs public class StoreIndexViewModel { public int NumeroDeGeneros { get; set; } public List<string> Generos { get; set; } } Consideremos o seguinte método de acção: Controllers/StoreController.cs using MvcApplication1.ViewModels; public ActionResult Index2() { // Criar uma lista de géneros var generos = new List<string> {"Rock", "Jazz", "Country", "Pop", "Disco"}; var viewModel = new StoreIndexViewModel { NumeroDeGeneros = generos.Count(), Generos = generos }; return View(viewModel); } Criar uma Vista Uma vista pode ser criada escrevendo código e adicionada ao directório Views. Mas é muito mais fácil de criar através da ferramenta do Visual Studio “Add View”. Clicando com o botão direito do rato em qualquer parte do código de um método de acção, seleccionar Add View… Na janela de diálogo “Add View”: View name: inicializado com o nome do método de acção. View engine: Razor ou ASPX. Create a strongly–typed view: esta selecção permite escrever ou seleccionar uma classe para modelo a enviar à vista. A lista de classes para modelos é preenchida usando reflexão. Portanto é importante compilar o projecto antes de seleccionar uma classe para modelo. Scaffold template: A selecção de um modelo a enviar à vista permite seleccionar também um template para gerar a vista baseado no modelo seleccionado. Scaffolding é usado por muitas tecnologias de software para significar geração rápida de um esquema do software que depois se pode editar. O template que gera este esquema inicial do código é usado como um andaime (scaffold) a partir do qual se constrói um programa mais poderoso. Tipos de templates: Empty: Cria uma vista vazia. Create: Cria uma vista com um formulário para criar instâncias do modelo. Delete: Cria uma vista com um formulário para apagar instâncias do modelo. Details: Cria uma vista que mostra o valor de cada propriedade do modelo. Edit: Cria uma vista com um formulário para editar instâncias do modelo. List: Cria uma vista com um tabela das instâncias do modelo. A tabela apresenta uma coluna para cada propriedade do modelo. O modelo que é passado pelo método de acção à vista tem de ser do tipo de dados IEnumerable<Modelo>. Esta vista também apresenta links para as acções create/edit/delete. Reference script libraries: a selecção desta opção na criação de uma vista que contenha entrada de dados, tal como Edit ou Create, faz com que a vista gerada referencie ficheiros JavaScript adequados para essa vista, tais como jQuery Validation library. Create as a partial view: gera uma vista parcial, que vai constituir apenas parte de uma página, sem a opção layout. O layout, por omissão, está definido no ficheiro _ViewStart.cshtml. Use a layout or master page: permite determinar se a vista referencia um layout (ou master page) diferente do layout por omissão ou se é uma vista completa. Botão direito do rato no código do método public ActionResult Index2(): Seleccionar Add View … Na janela de diálogo “Add View”: View name:Index2 View engine: Razor Create a strongly–typed view: selecionar Model class: StoreIndexViewModel (MvcApplication1.ViewModels) Scaffolf template: Details Reference script libraries: selecionar Create as a partial view: : não selecionar Use a layout or master page: não selecionar Add O ficheiro Views/Store/Index2.cshtml é criado com o seguinte código: @model MvcA.ViewModels.StoreIndexViewModel @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device‐width" /> <title>Index2</title> </head> <body> <fieldset> <legend>StoreIndexViewModel</legend> <div class="display‐label"> @Html.DisplayNameFor(model => model.NumeroDeGeneros) </div> <div class="display‐field"> @Html.DisplayFor(model => model.NumeroDeGeneros) </div> </fieldset> <p> @Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) | @Html.ActionLink("Back to List", "Index") </p> </body> </html> Alterar o conteúdo do <div> para: <div class="display‐label"> @Html.DisplayNameFor(model => model.NumeroDeGeneros) : @Html.DisplayFor(model => model.NumeroDeGeneros) </div> <div class="display‐field"> @Html.DisplayNameFor(model => model.Generos) : @Html.DisplayFor(model => model.Generos) </div> Layouts Os layouts têm a mesma finalidade das master pages nas Web Forms. Podemos usar um layout para definir um template comum para o site ou parte do site. Este template conterá um ou mais placeholders. As outras vistas da aplicação fornecerão conteúdos para estes placeholders. Exemplo: Layout1.cshtml <!DOCTYPE html> <html> <head><title>@ViewBag.Title</title></head> <body> <header> <h1>@ViewBag.Title</h1> </header> <div id="main‐content">@RenderBody()</div> <footer>@RenderSection("Footer", required: false)</footer> </body> </html> Index.cshtml @{ Layout = "~/Views/Shared/Layout1.cshtml"; ViewBag.Title = "Index"; } <p>Conteudo principal</p> @section Footer { Este é o footer } @RenderBody() é um placeholder que marca o lugar onde as vistas que usam este layout colocarão o seu conteúdo principal. @RenderSection(“Footer”) é um placeholder que marca o lugar onde as vistas que usam este layout colocarão o conteúdo especificado na secção @section Footer. As vistas têm de fornecer conteúdo para todas as secções definidas no layout, a não ser que a secção seja definida como opcional: @RenderSection(“Footer”, required: false) ViewStart Cada vista pode especificar a sua página de layout usando a propriedade Layout. Mas para um grupo de vistas que usem o mesmo layout podemos colocar num directório um ficheiro com o nome _ViewStart.cshtml. Este código é executado antes do código de qualquer vista nesse directório ou subdirectórios. _ViewStart.cshtml: @{ Layout = "~/Views/Shared/_Layout.cshtml"; } Forms e HTML Helpers Html.Label O helper Html.Label(”idTitulo”) cria o elemento html <lable for=”idTitulo”>Titulo</label> O objectivo do tag label é definir um label para um elemento input (tal como campo de texto, checkbox, ou radio button), e melhorar a acessibilidade, porque se o utilizador clicar no texto do label o browser transfere o foco para o controlo de input associado. O atributo for deve conter o id do elemento input associado. Html.DropDownList e Html.ListBox Estes helpers criam elementos select. Html.Label(”idTitulo”) cria o elemento html <lable for=”idTitulo”>Titulo</label> O objectivo do tag label é definir um label para um elemento input (tal como campo de Exemplo de DropDownList Ficheiro Controllers/HomeController.cs: public ActionResult SelecionarCategoria() { List<SelectListItem> itens = new List<SelectListItem>(); itens.Add(new SelectListItem { Text = "Ação", Value = "0", Selected = true }); itens.Add(new SelectListItem { Text = "Drama", Value = "1" }); itens.Add(new SelectListItem { Text = "Comedia", Value = "2" }); itens.Add(new SelectListItem { Text = "Ficção Científica", Value = "3" }); ViewBag.TipoFilme = itens; return View(); } Ficheiro SelecionarCategoria.cshtml: @{ ViewBag.Title = "SelecionarCategoria"; } <h2>SelecionarCategoria</h2> @using (Html.BeginForm("CategoriaEscolhida", "Home", FormMethod.Get)) { <fieldset> Tipo de Filme @Html.DropDownList("TipoFilme") <p> <input type="submit" value="Submit" /> </p> </fieldset> } Código html gerado: <h2>SelecionarCategoria</h2> <form action="/Home/CategoriaEscolhida" method="get"> <fieldset> Tipo de Filme <select id="TipoFilme" name="TipoFilme"> <option value="0">Ação</option> <option value="1">Drama</option> <option selected="selected" value="2">Comedia</option> <option value="3">Ficção Científica</option> </select> <p> <input type="submit" value="Submit" /> </p> </fieldset> </form> Exemplo de ListBox Semelhante ao exemplo anterior excepto no Ficheiro SelecionarCategoria.cshtml: @Html.DropDownList("TipoFilme") Alterado para: @Html.ListBox("TipoFilme") Código html gerado: <h2>SelecionarCategoria</h2> <form action="/Home/CategoriaEscolhida" method="get"> <fieldset> Tipo de Filme <select id="TipoFilme" multiple="multiple" name="TipoFilme"> <option value="0">Ação</option> <option value="1">Drama</option> <option selected="selected" value="2">Comedia</option> <option value="3">Ficção Científica</option> </select> <p> <input type="submit" value="Submit" /> </p> </fieldset> </form>