Aplicativo de página única: Modelo KnockoutJS

O Modelo knockout MVC faz parte do ASP.NET and Web Tools 2012.2

Baixar ASP.NET and Web Tools 2012.2

A atualização do ASP.NET and Web Tools 2012.2 inclui um modelo spa (aplicativo Single-Page) para ASP.NET MVC 4. Este modelo foi projetado para que você comece a criar rapidamente aplicativos Web interativos do lado do cliente.

"SPA (aplicativo de página única" é o termo geral para um aplicativo Web que carrega uma única página HTML e atualiza a página dinamicamente, em vez de carregar novas páginas. Após o carregamento da página inicial, o SPA conversa com o servidor por meio de solicitações AJAX.

Diagrama que mostra duas caixas rotuladas como Cliente e Servidor. Uma seta rotulada AJAX vai de Cliente para Servidor. Uma seta rotulada H T M L e uma seta rotulada J SON vão de Servidor para Cliente.

O AJAX não é novidade, mas hoje há estruturas JavaScript que facilitam a criação e a manutenção de um grande aplicativo SPA sofisticado. Além disso, HTML 5 e CSS3 estão facilitando a criação de interfaces do usuário avançadas.

Para começar, o modelo SPA cria um aplicativo de exemplo de "Lista de tarefas pendentes". Neste tutorial, faremos um tour guiado pelo modelo. Primeiro, examinaremos o próprio aplicativo de lista de tarefas pendentes e examinaremos as partes de tecnologia que o fazem funcionar.

Criar um novo projeto de modelo spa

Requisitos:

  • Visual Studio 2012 ou Visual Studio Express 2012 para Web
  • ASP.NET atualização do Web Tools 2012.2. Você pode instalar a atualização aqui.

Inicie o Visual Studio e selecione Novo Projeto na página Iniciar. Ou, no menu Arquivo , selecione Novo e Projeto.

No painel Modelos , selecione Modelos Instalados e expanda o nó Visual C #. Em Visual C#, selecione Web. Na lista de modelos de projeto, selecione ASP.NET Aplicativo Web MVC 4. Nomeie o projeto e clique em OK.

Captura de tela que mostra a caixa de diálogo Novo Projeto. Um aplicativo Web SP dot NET M V C 4 é selecionado na lista de Modelos da Web.

No assistente Novo Projeto , selecione Aplicativo de Página Única.

Captura de tela que mostra a caixa de diálogo Novo Projeto do SP dot NET M V C 4. O modelo Aplicativo de Página Única está selecionado.

Pressione F5 para compilar e executar o aplicativo. Quando o aplicativo é executado pela primeira vez, ele exibe uma tela de logon.

Captura de tela que mostra a tela de logon Minha Lista de Tarefas Pendentes.

Clique no link "Inscrever-se" e crie um novo usuário.

Captura de tela que mostra a tela Inscrever-se.

Depois de fazer logon, o aplicativo cria uma lista de Tarefas Pendentes padrão com dois itens. Você pode clicar em "Adicionar Lista de Tarefas Pendentes" para adicionar uma nova lista.

Captura de tela que mostra duas Listas de Tarefas Pendentes e um botão Adicionar à Lista de Tarefas Pendentes na parte superior.

Renomeie a lista, adicione itens à lista e marcar-os. Você também pode excluir itens ou excluir uma lista inteira. As alterações são mantidas automaticamente em um banco de dados no servidor (na verdade, LocalDB neste ponto, porque você está executando o aplicativo localmente).

Captura de tela que mostra uma lista com três itens. O último item é verificado e tem uma greve por meio dele.

Arquitetura do modelo SPA

Este diagrama mostra o main blocos de construção do aplicativo.

Diagrama que mostra os blocos de construção separados do Cliente e do Servidor. Knockout dot j s, H T M L e J SON estão em Client. Um S P dot NET M V C, um SP dot NET Web A P I, o Entity Framework e o Banco de Dados estão em Servidor.

No lado do servidor, ASP.NET MVC atende ao HTML e também lida com a autenticação baseada em formulários.

ASP.NET Web API lida com todas as solicitações relacionadas a ToDoLists e ToDoItems, incluindo obtenção, criação, atualização e exclusão. O cliente troca dados com a API Web no formato JSON.

O EF (Entity Framework) é a camada O/RM. Ele media entre o mundo orientado a objetos de ASP.NET e o banco de dados subjacente. O banco de dados usa LocalDB, mas você pode alterá-lo no arquivo Web.config. Normalmente, você usaria o LocalDB para desenvolvimento local e, em seguida, implantaria em um banco de dados SQL no servidor, usando a migração de código EF primeiro.

No lado do cliente, a biblioteca Knockout.js manipula atualizações de página de solicitações AJAX. O Knockout usa a associação de dados para sincronizar a página com os dados mais recentes. Dessa forma, você não precisa escrever nenhum código que percorre os dados JSON e atualiza o DOM. Em vez disso, você coloca atributos declarativos no HTML que informam ao Knockout como apresentar os dados.

Uma grande vantagem dessa arquitetura é que ela separa a camada de apresentação da lógica do aplicativo. Você pode criar a parte da API Web sem saber nada sobre a aparência da sua página da Web. No lado do cliente, você cria um "modelo de exibição" para representar esses dados e o modelo de exibição usa Knockout para associar ao HTML. Isso permite alterar facilmente o HTML sem alterar o modelo de exibição. (Vamos examinar o Knockout um pouco mais tarde.)

Modelos

No projeto do Visual Studio, a pasta Modelos contém os modelos usados no lado do servidor. (Também há modelos no lado do cliente; chegaremos a eles.)

Captura de tela que mostra a pasta Modelos aberta.

TodoItem, TodoList

Estes são os modelos de banco de dados para o Entity Framework Code First. Observe que esses modelos têm propriedades que apontam umas para as outras. ToDoList contém uma coleção de ToDoItems e cada ToDoItem um tem uma referência de volta para seu ToDoList pai. Essas propriedades são chamadas de propriedades de navegação e representam a relação um-para-muitos uma lista de tarefas pendentes e seus itens pendentes.

A ToDoItem classe também usa o atributo [ForeignKey] para especificar que ToDoListId é uma chave estrangeira na ToDoList tabela. Isso instrui o EF a adicionar uma restrição de chave estrangeira ao banco de dados.

[ForeignKey("TodoList")]
public int TodoListId { get; set; }
public virtual TodoList TodoList { get; set; }

TodoItemDto, TodoListDto

Essas classes definem os dados que serão enviados ao cliente. "DTO" significa "objeto de transferência de dados". O DTO define como as entidades serão serializadas em JSON. Em geral, há vários motivos para usar DTOs:

  • Para controlar quais propriedades são serializadas. O DTO pode conter um subconjunto das propriedades do modelo de domínio. Você pode fazer isso por motivos de segurança (para ocultar dados confidenciais) ou simplesmente para reduzir a quantidade de dados enviados.
  • Para alterar a forma dos dados , por exemplo, para nivelar uma estrutura de dados mais complexa.
  • Para manter qualquer lógica de negócios fora do DTO (separação de preocupações).
  • Se os modelos de domínio não puderem ser serializados por algum motivo. Por exemplo, referências circulares podem causar problemas ao serializar um objeto Há maneiras de lidar com esse problema na API Web (consulte Manipulando referências de objeto circular); mas usar um DTO simplesmente evita o problema completamente.

No modelo SPA, os DTOs contêm os mesmos dados que os modelos de domínio. No entanto, eles ainda são úteis porque evitam referências circulares das propriedades de navegação e demonstram o padrão geral de DTO.

AccountModels.cs

Esse arquivo contém modelos para associação de site. A UserProfile classe define o esquema para perfis de usuário no BD de associação. (Nesse caso, as únicas informações são a ID de usuário e o nome de usuário.) As outras classes de modelo neste arquivo são usadas para criar os formulários de registro e logon do usuário.

Entity Framework

O modelo SPA usa o EF Code First. No desenvolvimento do Code First, você define os modelos primeiro no código e, em seguida, o EF usa o modelo para criar o banco de dados. Você também pode usar o EF com um banco de dados existente (Database First).

A TodoItemContext classe na pasta Models deriva de DbContext. Essa classe fornece a "cola" entre os modelos e o EF. O TodoItemContext contém uma ToDoItem coleção e uma TodoList coleção. Para consultar o banco de dados, basta escrever uma consulta LINQ nessas coleções. Por exemplo, veja como você pode selecionar todas as listas de tarefas pendentes para o usuário "Alice":

TodoItemContext db = new TodoItemContext();
IEnumerable<TodoList> lists = 
    from td in db.TodoLists where td.UserId == "Alice" select td;

Você também pode adicionar novos itens à coleção, atualizar itens ou excluir itens da coleção e persistir as alterações no banco de dados.

Controladores de ASP.NET Web API

Em ASP.NET Web API, os controladores são objetos que lidam com solicitações HTTP. Conforme mencionado, o modelo SPA usa a API Web para habilitar operações CRUD em ToDoList instâncias e ToDoItem . Os controladores estão localizados na pasta Controladores da solução.

Captura de tela que mostra a pasta Controladores aberta. Para fazer Controlador do ponto c s e Para fazer o controlador de lista ponto c s são ambos circulados em vermelho.

  • TodoController: manipula solicitações HTTP para itens pendentes
  • TodoListController: manipula solicitações HTTP para listas de tarefas pendentes.

Esses nomes são significativos, pois a API Web corresponde ao caminho do URI com o nome do controlador. (Para saber como a API Web roteia solicitações HTTP para controladores, consulte Roteamento em ASP.NET Web API.)

Vamos ver a ToDoListController classe. Ele contém um único membro de dados:

private TodoItemContext db = new TodoItemContext();

O TodoItemContext é usado para se comunicar com o EF, conforme descrito anteriormente. Os métodos no controlador implementam as operações CRUD. A API Web mapeia solicitações HTTP do cliente para os métodos do controlador, da seguinte maneira:

Solicitação HTTP Método Controller Descrição
GET /api/todo GetTodoLists Obtém uma coleção de listas de tarefas pendentes.
GET /api/todo/id GetTodoList Obtém uma lista de tarefas pendentes por ID
PUT /api/todo/id PutTodoList Atualizações uma lista de tarefas pendentes.
POST /api/todo PostTodoList Cria uma nova lista de tarefas pendentes.
DELETE /api/todo/id DeleteTodoList Exclui uma lista TODO.

Observe que os URIs de algumas operações contêm espaços reservados para o valor da ID. Por exemplo, para excluir uma lista com uma ID de 42, o URI é /api/todo/42.

Para saber mais sobre como usar a API Web para operações CRUD, consulte Criando uma API Web que dá suporte a operações CRUD. O código para esse controlador é bastante simples. Aqui estão alguns pontos interessantes:

  • O GetTodoLists método usa uma consulta LINQ para filtrar os resultados pela ID do usuário conectado. Dessa forma, um usuário só vê os dados que pertencem a ele ou a ela. Além disso, observe que uma instrução Select é usada para converter as ToDoList instâncias em TodoListDto instâncias.
  • Os métodos PUT e POST marcar o estado do modelo antes de modificar o banco de dados. Se ModelState.IsValid for false, esses métodos retornarão HTTP 400, Solicitação Incorreta. Leia mais sobre a validação de modelo na API Web em Validação de Modelo.
  • A classe de controlador também é decorada com o atributo [Authorize] . Esse atributo verifica se a solicitação HTTP está autenticada. Se a solicitação não for autenticada, o cliente receberá HTTP 401, Não autorizado. Leia mais sobre autenticação em Autenticação e Autorização no ASP.NET Web API.

A TodoController classe é muito semelhante a TodoListController. A maior diferença é que ele não define nenhum método GET, pois o cliente obterá os itens pendentes junto com cada lista de tarefas pendentes.

Controladores e exibições do MVC

Os controladores MVC também estão localizados na pasta Controladores da solução. HomeControllerrenderiza o HTML main para o aplicativo. A exibição do controlador Home é definida em Views/Home/Index.cshtml. O modo de exibição Página Inicial renderiza conteúdo diferente dependendo se o usuário está conectado:

@if (@User.Identity.IsAuthenticated)
{
    // ....
}

Quando os usuários estão conectados, eles veem a interface do usuário main. Caso contrário, eles verão o painel de logon. Observe que essa renderização condicional ocorre no lado do servidor. Nunca tente ocultar conteúdo confidencial no lado do cliente— qualquer coisa que você enviar em uma resposta HTTP fica visível para alguém que está assistindo às mensagens HTTP brutas.

Client-Side JavaScript e Knockout.js

Agora vamos voltar do lado do servidor do aplicativo para o cliente. O modelo SPA usa uma combinação de jQuery e Knockout.js para criar uma interface do usuário interativa e suave. Knockout.js é uma biblioteca JavaScript que facilita a associação de HTML aos dados. Knockout.js usa um padrão chamado "Model-View-ViewModel".

  • O modelo são os dados de domínio (listas de ToDo e itens ToDo).
  • A exibição é o documento HTML.
  • O modelo de exibição é um objeto JavaScript que contém os dados do modelo. O modelo de exibição é uma abstração de código da interface do usuário. Ele não tem conhecimento da representação HTML. Em vez disso, ele representa recursos abstratos do modo de exibição, como "uma lista de itens ToDo".

A exibição é associada a dados ao modelo de exibição. Atualizações para o modelo de exibição são refletidos automaticamente na exibição. As associações também funcionam na outra direção. Os eventos no DOM (como cliques) são associados a dados para funções no modelo de exibição, que disparam chamadas AJAX.

O modelo SPA organiza o JavaScript do lado do cliente em três camadas:

  • todo.datacontext.js: envia solicitações AJAX.
  • todo.model.js: define os modelos.
  • todo.viewmodel.js: define o modelo de exibição.

Diagrama que mostra uma seta indo de Knockout dot j s para Exibir Modelo para Modelos para Contexto de Dados. A seta entre Knockout dot j s e View Model é rotulada como Associação de Dados e aponta para ambos os itens.

Esses arquivos de script estão localizados na pasta Scripts/aplicativo da solução.

Captura de tela que mostra o aplicativo rotulado como subpasta aberto.

todo.datacontext manipula todas as chamadas AJAX para os controladores de API Web. (As chamadas do AJAX para fazer logon são definidas em outro lugar, em ajaxlogin.js.)

todo.model.js define os modelos do lado do cliente (navegador) para as listas de tarefas pendentes. Há duas classes de modelo: todoItem e todoList.

Muitas das propriedades nas classes de modelo são do tipo "ko.observable". Observáveis são como o Knockout faz sua magia. Na documentação do Knockout: um observável é um "objeto JavaScript que pode notificar os assinantes sobre as alterações". Quando o valor de um observável é alterado, o Knockout atualiza todos os elementos HTML associados a esses observáveis. Por exemplo, todoItem tem observáveis para o título e as propriedades isDone:

self.title = ko.observable(data.title);
self.isDone = ko.observable(data.isDone);

Você também pode assinar um observável no código. Por exemplo, a classe todoItem assina as alterações nas propriedades "isDone" e "title":

saveChanges = function () {
    return datacontext.saveChangedTodoItem(self);
};

// Auto-save when these properties change
self.isDone.subscribe(saveChanges);
self.title.subscribe(saveChanges);

Exibir Modelo

O modelo de exibição é definido em todo.viewmodel.js. O modelo de exibição é o ponto central em que o aplicativo associa os elementos de página HTML aos dados de domínio. No modelo SPA, o modelo de exibição contém uma matriz observável de todoLists. O código a seguir no modelo de exibição informa ao Knockout para aplicar as associações:

ko.applyBindings(window.todoApp.todoListViewModel);

Associação de dados e HTML

O HTML main para a página é definido em Views/Home/Index.cshtml. Como estamos usando a associação de dados, o HTML é apenas um modelo para o que realmente é renderizado. O Knockout usa associações declarativas . Você associa elementos de página a dados adicionando um atributo "data-bind" ao elemento . Aqui está um exemplo muito simples, obtido da documentação do Knockout:

<p>There are <span data-bind="text: myItems().count"></span> items<p>

Neste exemplo, o Knockout atualiza o conteúdo do <elemento span> com o valor de myItems.count(). Sempre que esse valor for alterado, o Knockout atualizará o documento.

O Knockout fornece vários tipos de associação diferentes. Aqui estão algumas das associações usadas no modelo SPA:

  • foreach: permite iterar por meio de um loop e aplicar a mesma marcação a cada item na lista. Isso é usado para renderizar as listas de tarefas pendentes e os itens pendentes. No foreach, as associações são aplicadas aos elementos da lista.
  • visível: usado para alternar a visibilidade. Ocultar marcação quando uma coleção estiver vazia ou tornar a mensagem de erro visível.
  • value: usado para preencher valores de formulário.
  • clique: associa um evento de clique a uma função no modelo de exibição.

Proteção anti-CSRF

CSRF (Solicitação Entre Sites Forjada) é um ataque em que um site mal-intencionado envia uma solicitação para um site vulnerável em que o usuário está conectado no momento. Para ajudar a evitar ataques CSRF, ASP.NET MVC usa tokens anti-falsificação, também chamados de tokens de verificação de solicitação. A ideia é que o servidor coloque um token gerado aleatoriamente em uma página da Web. Quando o cliente envia dados para o servidor, ele deve incluir esse valor na mensagem de solicitação.

Os tokens anti-falsificação funcionam porque a página mal-intencionada não pode ler os tokens do usuário devido a políticas de mesma origem. (As políticas de mesma origem impedem que documentos hospedados em dois sites diferentes acessem o conteúdo um do outro.)

ASP.NET MVC fornece suporte interno para tokens anti-falsificação, por meio da classe AntiForgery e do atributo [ValidateAntiForgeryToken] . Atualmente, essa funcionalidade não é incorporada à API Web. No entanto, o modelo spa inclui uma implementação personalizada para a API Web. Esse código é definido na ValidateHttpAntiForgeryTokenAttribute classe , que está localizada na pasta Filtros da solução. Para saber mais sobre o anti-CSRF na API Web, consulte Preventing Cross-Site Request Forgery (CSRF).

Conclusão

O modelo spa foi projetado para que você comece a escrever rapidamente aplicativos Web modernos e interativos. Ele usa a biblioteca Knockout.js para separar a apresentação (marcação HTML) dos dados e da lógica do aplicativo. Mas o Knockout não é a única biblioteca JavaScript que você pode usar para criar um SPA. Se você quiser explorar algumas outras opções, dê uma olhada nos modelos de SPA criados pela comunidade.