Compartir vía


Aplicación de una sola página: Plantilla KnockoutJS

La plantilla de Knockout MVC forma parte de ASP.NET and Web Tools 2012.2

Descargar ASP.NET and Web Tools 2012.2

La actualización de ASP.NET and Web Tools 2012.2 incluye una plantilla de aplicación de página única (SPA) para ASP.NET MVC 4. Esta plantilla está diseñada para que pueda empezar a desarrollar rápidamente aplicaciones web interactivas del lado del cliente.

"Aplicación de página única" (SPA) es el término general para una aplicación web que carga una sola página HTML y después la actualiza dinámicamente, en lugar de cargar páginas nuevas. Tras la carga inicial de la página, la SPA habla con el servidor a través de solicitudes AJAX.

Diagram that shows two boxes labeled Client and Server. An arrow labeled AJAX goes from Client to Server. An arrow labeled H T M L and an arrow labeled J SON go from Server to Client.

AJAX no es nada nuevo, pero hoy en día existen marcos de JavaScript que facilitan la tarea de compilar y mantener una gran aplicación SPA sofisticada. Además, HTML 5 y CSS3 están facilitando la creación de interfaces de usuario enriquecidas.

Para empezar, la plantilla de SPA crea un ejemplo de aplicación "Lista de tareas pendientes". En este tutorial, realizaremos una visita guiada a la plantilla. Primero veremos la propia aplicación de listas de tareas pendientes y después examinaremos las piezas tecnológicas que la hacen funcionar.

Creación de un nuevo proyecto de plantilla de SPA

Requisitos:

  • Visual Studio 2012 o Visual Studio Express 2012 para la Web
  • Actualización de ASP.NET Web Tools 2012.2. Puede instalar la actualización aquí.

Inicie Visual Studio y seleccione Nuevo proyecto en la página Inicio. O, en el menú Archivo, seleccione Nuevo y después Proyecto.

En el panel Plantillas, seleccione Plantillas instaladas y expanda el nodo Visual C#. En Visual C#, seleccione Web. En la lista de plantillas de proyecto, seleccione Aplicación web ASP.NET MVC 4. Proporcione un nombre al proyecto y haga clic en Aceptar.

Screenshot that shows the New Project dialog box. A S P dot NET M V C 4 Web Application is selected from the list of Web Templates.

En el asistente para Nuevo proyecto, seleccione Aplicación de página única.

Screenshot that shows the New A S P dot NET M V C 4 Project dialog box. The Single Page Application template is selected.

Presione F5 para compilar y ejecutar la aplicación. Cuando la aplicación se ejecuta por primera vez, muestra una pantalla de inicio de sesión.

Screenshot that shows the My To do List login screen.

Haga clic en el vínculo "Regístrese" y cree un nuevo usuario.

Screenshot that shows the Sign up screen.

Tras su registro, la aplicación crea de manera predeterminada una lista de tareas pendientes con dos elementos. Puede hacer clic en "Agregar lista de tareas pendientes" para agregar una nueva lista.

Screenshot that shows two To do Lists and an Add To do List button at the top.

Cambie el nombre de la lista, agregue elementos a la lista y márquelos. También puede eliminar elementos o eliminar una lista entera. Los cambios se conservan automáticamente en una base de datos del servidor (en realidad LocalDB en este punto, porque está ejecutando la aplicación localmente).

Screenshot that shows a list with three items. The last item is checked and has a strike through it.

Arquitectura de la plantilla de SPA

Este diagrama muestra los principales bloques de compilación de la aplicación.

Diagram that shows the separate building blocks of the Client and Server. Knockout dot j s, H T M L, and J SON are under Client. A S P dot NET M V C, A S P dot NET Web A P I, Entity Framework, and Database are under Server.

En el lado del servidor, ASP.NET MVC sirve el HTML y también controla la autenticación basada en formularios.

ASP.NET Web API controla todas las solicitudes relacionadas con ToDoLists y ToDoItems, incluyendo la obtención, creación, actualización y eliminación. El cliente intercambia datos con Web API en formato JSON.

Entity Framework (EF) es la capa de O/RM. Media entre el mundo orientado a objetos de ASP.NET y la base de datos subyacente. La base de datos usa LocalDB pero puede cambiarlo en el archivo Web.config. Normalmente, usaría LocalDB para el desarrollo local y, a continuación, implementaría en una base de datos SQL en el servidor mediante la migración de Code First de Entity Framework.

En el lado del cliente, la biblioteca Knockout.js controla las actualizaciones de la página a partir de solicitudes AJAX. Knockout usa el enlace de datos para sincronizar la página con los datos más recientes. De esta forma, no tendrá que escribir nada del código que recorre los datos JSON y actualiza el DOM. En su lugar, se colocan atributos declarativos en el HTML que indican a Knockout cómo presentar los datos.

Una gran ventaja de esta arquitectura es que separa la capa de presentación de la lógica de la aplicación. Puede crear la parte de Web API sin saber nada sobre el aspecto que tendrá su página web. En el lado del cliente, se crea un "modelo de vista" para representar esos datos, y el modelo de vista usa Knockout para enlazarse con el HTML. Eso le permite cambiar fácilmente el HTML sin cambiar el modelo de vista. (Veremos Knockout un poco más adelante).

Modelos

En el proyecto de Visual Studio, la carpeta Models contiene los modelos que se usan en el lado del servidor. (También existen modelos en el lado del cliente; ya hablaremos de ellos).

Screenshot that shows the Models folder open.

TodoItem, TodoList

Estos son los modelos de base de datos para Code First de Entity Framework. Observe que estos modelos tienen propiedades que se señalan mutuamente. ToDoList contiene una colección de ToDoItems y cada ToDoItem tiene una referencia a su ToDoList primario. Estas propiedades se denominan propiedades de navegación y representan la relación uno a muchos entre una lista de tareas pendientes y sus elementos pendientes.

La clase ToDoItem también usa el atributo [ForeignKey] para especificar que ToDoListId es una clave externa en la tabla ToDoList. Esto le dice a EF que agregue una restricción de clave externa a la base de datos.

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

TodoItemDto, TodoListDto

Estas clases definen los datos que se enviarán al cliente. "DTO" significa "objeto de transferencia de datos". El DTO define cómo se serializarán las entidades en JSON. En general, hay varias razones para usar los DTO:

  • Para controlar qué propiedades se serializan. El DTO puede contener un subconjunto de las propiedades del modelo de dominio. Puede hacerlo por motivos de seguridad (para ocultar datos confidenciales) o simplemente para reducir la cantidad de datos que envía.
  • Para cambiar la forma de los datos: por ejemplo, para aplanar una estructura de datos más compleja.
  • Para mantener cualquier lógica de negocios fuera del DTO (separación de intereses).
  • Si sus modelos de dominio no pueden serializarse por alguna razón. Por ejemplo, las referencias circulares pueden causar problemas al serializar un objeto. Hay formas de controlar este problema en Web API (consulte Control de referencias de objetos circulares); pero usando un DTO simplemente se evita el problema por completo.

En el modelo de SPA, los DTO contienen los mismos datos que los modelos de dominio. Sin embargo, siguen siendo útiles porque evitan las referencias circulares de las propiedades de navegación y demuestran el patrón de DTO general.

AccountModels.cs

Este archivo contiene modelos para la pertenencia al sitio. La clase UserProfile define el esquema de los perfiles de usuario en la base de datos de pertenencia. (En este caso, la única información es el id. de usuario y el nombre de usuario). Las otras clases de modelo de este archivo se usan para crear los formularios de registro e inicio de sesión del usuario.

Entity Framework

La plantilla de SPA usa Code First de EF. En el desarrollo de Code First, primero se definen los modelos en código y después EF usa el modelo para crear la base de datos. También puede usar EF con una base de datos existente (Database First).

La clase TodoItemContext de la carpeta Models deriva de DbContext. Esta clase proporciona el "pegamento" entre los modelos y EF. El TodoItemContext contiene una colección ToDoItem y una colección TodoList. Para consultar la base de datos, simplemente escriba una consulta LINQ en estas colecciones. Por ejemplo, así es como puede seleccionar todas las listas de tareas pendientes del usuario "Alice":

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

También puede agregar nuevos elementos a la colección, actualizarlos o eliminarlos, y conservar los cambios en la base de datos.

Controladores de ASP.NET Web API

En ASP.NET Web API, los controladores son objetos que controlan las solicitudes HTTP. Como se ha mencionado, la plantilla de SPA usa Web API para habilitar las operaciones CRUD en las instancias de ToDoList y ToDoItem. Los controladores se encuentran en la carpeta Controllers de la solución.

Screenshot that shows the Controllers folder open. To do Controller dot c s and To do List Controller dot c s are both circled in red.

  • TodoController: controla las solicitudes HTTP para los elementos pendientes
  • TodoListController: controla las solicitudes HTTP para las listas de tareas pendientes.

Estos nombres son significativos, porque Web API hace coincidir la ruta de acceso de URI con el nombre del controlador. (Para aprender cómo Web API enruta las solicitudes HTTP a los controladores, consulte Enrutamiento en ASP.NET Web API).

Echemos un vistazo a la clase ToDoListController. Contiene un único miembro de datos:

private TodoItemContext db = new TodoItemContext();

El TodoItemContext se usa para comunicarse con EF, como se ha descrito anteriormente. Los métodos del controlador implementan las operaciones CRUD. Web API asigna las solicitudes HTTP del cliente a los métodos del controlador, como se indica a continuación:

Solicitud HTTP Método del controlador Descripción
GET /api/todo GetTodoLists Obtiene una colección de listas de tareas pendientes.
GET /api/todo/id GetTodoList Obtiene una lista de tareas pendientes por id.
PUT /api/todo/id PutTodoList Actualiza una lista de tareas pendientes.
POST /api/todo PostTodoList Crea una nueva lista de tareas pendientes.
DELETE /api/todo/id DeleteTodoList Elimina una lista de tareas pendientes.

Observe que los URI de algunas operaciones contienen marcadores de posición para el valor de identificador. Por ejemplo, para eliminar una lista de tareas pendientes con un id. de 42, el URI es /api/todo/42.

Para más información sobre cómo usar Web API para operaciones CRUD, consulte Creación de una API web compatible con operaciones CRUD. El código de este controlador es bastante sencillo. Estos son algunos puntos interesantes:

  • El método GetTodoLists usa una consulta LINQ para filtrar los resultados por el id. del usuario registrado. De este modo, un usuario solo ve los datos que le pertenecen. Además, observe que se usa una instrucción Select para convertir las instancias de ToDoList en instancias de TodoListDto.
  • Los métodos PUT y POST comprueban el estado del modelo antes de modificar la base de datos. Si ModelState.IsValid es falso, estos métodos devuelven HTTP 400, Solicitud incorrecta. Más información sobre la validación de modelos en Web API en Validación de modelos.
  • La clase de controlador también está decorada con el atributo [Authorize]. Este atributo comprueba si la solicitud HTTP está autenticada. Si la solicitud no está autenticada, el cliente recibe HTTP 401, No autorizado. Más información sobre autenticación en Autenticación y autorización en ASP.NET Web API.

La clase TodoController es muy similar a TodoListController. La mayor diferencia es que no define ningún método GET, ya que el cliente obtendrá los elementos pendientes junto con cada lista de tareas pendientes.

Controladores y vistas de MVC

Los controladores de MVC también se encuentran en la carpeta Controllers de la solución. HomeController representa el HTML principal de la aplicación. La vista para el controlador Inicio se define en Views/Home/Index.cshtml. La vista Inicio representa un contenido diferente en función de si el usuario ha iniciado sesión:

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

Cuando los usuarios han iniciado sesión, ven la interfaz de usuario principal. De lo contrario, verán el panel de inicio de sesión. Tenga en cuenta que esta representación condicional se produce en el lado servidor. Nunca intente ocultar contenido confidencial en el lado del cliente: todo lo que envíe en una respuesta HTTP es visible para alguien que esté mirando los mensajes HTTP sin procesar.

JavaScript del lado del cliente y Knockout.js

Ahora pasemos del lado del servidor de la aplicación al cliente. La plantilla de SPA usa una combinación de jQuery y Knockout.js para crear una interfaz de usuario fluida e interactiva. Knockout.js es una biblioteca de JavaScript que facilita el enlace de HTML a datos. Knockout.js usa un patrón llamado "Modelo-Vista-Modelo de vista".

  • El modelo son los datos del dominio (listas de tareas pendientes y elementos pendientes).
  • La vista es el documento HTML.
  • El modelo de vista es un objeto de JavaScript que contiene los datos del modelo. El modelo de vista es una abstracción de código de la interfaz de usuario. No tiene conocimiento de la representación HTML. En su lugar, representa características abstractas de la vista, como "una lista de elementos pendientes".

La vista está vinculada a los datos del modelo de vista. Las actualizaciones del modelo de vista se reflejan automáticamente en la vista. Los enlaces también funcionan en la otra dirección. Los eventos en el DOM (como los clics) están vinculados a funciones en el modelo de vista, que desencadenan llamadas de AJAX.

La plantilla de SPA organiza el JavaScript del lado del cliente en tres capas:

  • todo.datacontext.js: envía solicitudes AJAX.
  • todo.model.js: define los modelos.
  • todo.viewmodel.js: define el modelo de vista.

Diagram that shows an arrow going from Knockout dot j s to View Model to Models to Data Context. The arrow between Knockout dot j s and View Model is labeled Data Binding and points to both items.

Estos archivos de scripts se encuentran en la carpeta Scripts/app de la solución.

Screenshot that shows the subfolder labeled app open.

todo.datacontext se encarga de todas las llamadas de AJAX a los controladores de Web API. (Las llamadas de AJAX para el registro se definen en otro lugar, en ajaxlogin.js.)

todo.model.js define los modelos del lado del cliente (explorador) para las listas de tareas pendientes. Existen dos clases de modelos: todoItem y todoList.

Muchas de las propiedades de las clases de modelo son de tipo "ko.observable". Los observables son la forma en que Knockout hace su magia. De la documentación de Knockout: un observable es un "objeto JavaScript que puede notificar cambios a los suscriptores". Cuando cambia el valor de un observable, Knockout actualiza cualquier elemento HTML que esté vinculado a ese observable. Por ejemplo, todoItem tiene observables para las propiedades title e isDone:

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

También puede suscribirse a un observable en código. Por ejemplo, la clase todoItem se suscribe a los cambios en las propiedades "isDone" y "title":

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

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

Modelo de vista

El modelo de vista se define en todo.viewmodel.js. El modelo de vista es el punto central en el que la aplicación enlaza los elementos de la página HTML con los datos del dominio. En la plantilla de SPA, el modelo de vista contiene una matriz observable de todoLists. El siguiente código en el modelo de vista indica a Knockout que aplique los enlaces:

ko.applyBindings(window.todoApp.todoListViewModel);

HTML y enlace de datos

El HTML principal de la página se define en Views/Home/Index.cshtml. Como estamos usando el enlace de datos, el HTML es solo una plantilla para lo que realmente se representa. Knockout usa enlaces declarativos. Los elementos de página se vinculan a datos añadiendo un atributo "data-bind" al elemento. Este es un ejemplo muy sencillo, tomado de la documentación de Knockout:

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

En este ejemplo, Knockout actualiza el contenido del elemento <span> con el valor de myItems.count(). Cada vez que cambia este valor, Knockout actualiza el documento.

Knockout proporciona varios tipos de enlaces diferentes. Estos son algunos de los enlaces usados en la plantilla de SPA:

  • foreach: permite recorrer en iteración un bucle y aplicar el mismo marcado a cada elemento de la lista. Se usa para representar las listas de tareas y elementos pendientes. Dentro de foreach, los enlaces se aplican a los elementos de la lista.
  • visible: se usa para alternar la visibilidad. Oculte las marcas cuando una colección esté vacía o haga visible el mensaje de error.
  • value: se usa para rellenar los valores del formulario.
  • click: enlaza un evento de clic a una funcionalidad en el modelo de vista.

Protección contra CSRF

La falsificación de solicitud entre sitios (CSRF) es un ataque en el que un sitio malicioso envía una solicitud a un sitio vulnerable en el que el usuario ha iniciado sesión. Para ayudar a prevenir los ataques CSRF, ASP.NET MVC usa tokens antifalsificación, también llamados tokens de verificación de solicitudes. La idea es que el servidor ponga un token generado aleatoriamente en una página web. Cuando el cliente envía datos al servidor, debe incluir este valor en el mensaje de solicitud.

Los tokens antifalsificación funcionan porque la página maliciosa no puede leer los tokens del usuario, debido a las directivas de mismo origen. (Las directivas de mismo origen impiden que los documentos hospedados en dos sitios diferentes accedan al contenido del otro).

ASP.NET MVC proporciona compatibilidad integrada con los tokens antifalsificación, a través de la clase AntiForgery y el atributo [ValidateAntiForgeryToken]. Actualmente, esta funcionalidad no está integrada en Web API. Sin embargo, la plantilla de SPA incluye una implementación personalizada para Web API. Este código está definido en la clase ValidateHttpAntiForgeryTokenAttribute, que se encuentra en la carpeta Filtros de la solución. Para más información sobre la protección contra la falsificación de solicitud entre sitios (CSRF) en Web API, consulte Prevención de los ataques de falsificación de solicitud entre sitios (CSRF).

Conclusión

La plantilla de SPA está diseñada para que pueda empezar a escribir rápidamente aplicaciones web modernas e interactivas. Usa la biblioteca de Knockout.js para separar la presentación (marcado HTML) de los datos y la lógica de la aplicación. Pero Knockout no es la única biblioteca de JavaScript que puede usar para crear una SPA. Si quiere explorar otras opciones, eche un vistazo a las plantillas de SPA creadas por la comunidad.