Aplicación de una sola página: Plantilla KnockoutJS
La plantilla de Knockout MVC forma parte de 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.
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.
En el asistente para Nuevo proyecto, seleccione Aplicación de página única.
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.
Haga clic en el vínculo "Regístrese" y cree un nuevo usuario.
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.
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).
Arquitectura de la plantilla de SPA
Este diagrama muestra los principales bloques de compilación de la aplicación.
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).
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.
TodoController
: controla las solicitudes HTTP para los elementos pendientesTodoListController
: 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 deToDoList
en instancias deTodoListDto
. - 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.
Estos archivos de scripts se encuentran en la carpeta Scripts/app de la solución.
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.