Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Acceso a datos sin complicaciones en JavaScript. Sí, en JavaScript
Descargar el ejemplo de código
Aunque me veo a mí misma como una especie de fontanera del desarrollo de software, también realizo una cantidad considerable de desarrollo del lado cliente. Incluso en esta columna, me aventuro en las aplicaciones cliente cuando estas se cruzan con el acceso a datos. Pero cuando el lado cliente es JavaScript, las cosas nunca fueron agradables: desafortunadamente, mis conocimientos de JavaScript todavía son deficientes y cada etapa de aprendizaje trae complicaciones inmensas para mí (y para mis seguidores en Twitter, que deben lidiar con mis desahogos). Pero siempre vale la pena cuando supero las dificultades y alcanzo el éxito. Y cualquier cosa que facilite el trabajo con JavaScript es algo que recibo con los brazos abiertos. Así que me interesó muchísimo cuando, durante una presentación en el Grupo de usuarios Vermont.NET sobre aplicaciones de una sola página, Ward Bell, de IdeaBlade, nos ofreció que echáramos una mirada a una API de acceso a datos de código abierto para JavaScript que ha estado inventando junto con su equipo. Desde el punto de vista de mi experiencia con Entity Framework, lo que vi se parecía a EF para un desarrollo web del lado cliente. La API se llama Breeze y se encuentra en estado beta a la hora de redactar este artículo. Bell fue muy generoso con su tiempo cuando me enseñó más sobre Breeze para este artículo. Puede descargar el software en breezejs.com, donde también encontrará una cantidad impresionante de documentación, vídeos y ejemplos.
Mi artículo de junio de 2012 de Puntos de datos, “Enlaces de datos OData en aplicaciones web con Knockout.js” (msdn.microsoft.com/magazine/jj133816), se concentró en el uso de Knockout.js para facilitar el enlace de datos del lado cliente. Como Breeze funciona a la perfección con Knockout, retomaré el ejemplo del artículo de junio. Mi intención es ver cómo el uso de Breeze podría simplificar la codificación en el caso del flujo de trabajo del ejemplo:
- Obtener datos del servidor.
- Enlazar y presentar los datos.
- Insertar los cambios nuevamente en el servidor.
Recorreré las partes críticas de una solución actualizada para mostrar cómo se combinan las diferentes partes. Si quiere seguir con una solución configurada correctamente y realizar pruebas, puede descargar la solución completa en archive.msdn.microsoft.com/mag201212DataPoints.
El ejemplo original
Estos son los pasos claves de la solución anterior:
- En el lado cliente definí una clase Person que le sirve a Knockout para el enlace de datos.
function PersonViewModel(model) {
model = model || {};
var self = this;
self.FirstName = ko.observable(model.Name || ' ');
self.LastName = ko.observable(model.Name || ' ');
}
- Los datos provenían de un servicio de datos OData, así que para acceder a los datos usé datajs, un kit de herramientas para consumir datos en OData desde JavaScript.
- Tomé los resultados de la consulta (que se devolvían en formato JSON) y con los valores creé una instancia de PersonViewModel.
- Mi aplicación luego dejó que Knockout se encargara del enlace de datos y que también coordinara los cambios realizados por el usuario.
- Tomé la instancia modificada de PersonViewModel y actualicé el objeto de JSON a partir de los valores.
- Por último, pasé el objeto de JSON a datajs para volver a guardar los datos en el servidor a través de OData.
Ni siquiera me preocupé de los datos relacionados, ya que esto habría elevado demasiado la complejidad de este pequeño ejemplo.
Un servicio actualizado con ASP.NET Web API
Con Breeze, puedo realizar llamadas HTTP a mi servicio OData o a un servicio definido por ASP.NET Web API (asp.net/web-api). Cambié el servicio a ASP.NET Web API, que funciona con el mismo modelo de EF que empleé anteriormente, con una diferencia. En el ejemplo anterior solo se exponían los datos de Person. Ahora tengo datos relacionados en forma de una clase Device, ya que todos los desarrolladores que conozco tienen una pequeña colección de dispositivos personales. Las funciones pertinentes expuestas por mi ASP.NET Web API son un GET, que devuelve los datos de Person; otro GET para los datos de Device; y un solo POST para guardar los cambios. También uso una función Metadata para exponer el esquema de los datos, como se aprecia en la figura 1. Breeze empleará estos metadatos para entender el modelo.
Figura 1 Ingredientes claves del servicio de Web API
readonly EFContextProvider<PersonModelContext> _contextProvider =
new EFContextProvider<PersonModelContext>();
[AcceptVerbs("GET")]
public IQueryable<Person> People()
{
return _contextProvider.Context.People;
}
[AcceptVerbs("GET")]
public IQueryable<Device> Devices()
{
return _contextProvider.Context.Devices;
}
[AcceptVerbs("POST")]
public SaveResult SaveChanges(JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
[AcceptVerbs("GET")]
public string Metadata()
{
return _contextProvider.Metadata();
}
Breeze.NET en el servidor
Tome nota de la variable _contextProvider que se emplea en estos métodos. No llamo los métodos de DbContext de EF (PersonModelContext) en forma directa. En lugar de esto, lo encapsulé con EFContextProvider de Breeze. Este es el origen de _contextProvider.Metadata, además de la firma de SaveChanges, que acepta un parámetro saveBundle. Con saveBundle, Breeze me permitirá enviar un conjunto de modificaciones de los datos desde mi aplicación, que los pasará a DbContext para conservarlos en la base de datos.
Como le puse el nombre “BreezyDevices” a la aplicación ASP.NET Web API, ahora puedo solicitar el esquema mediante http://localhost:19428/api/breezydevices/metadata. Y para solicitar datos puedo especificar uno de los métodos GET: http://localhost:19428/api/breezydevices/people.
Dado que en el lado cliente Breeze solicitará y almacenará los datos en un servicio remoto ASP.NET, puedo eliminar datajs de la aplicación cliente.
Cómo me servirá Breeze en el ejemplo
Dentro del alcance de este ejemplo, emplearé Breeze para concentrarme en tres puntos complicados:
- Mi servicio devuelve y acepta JSON tal cual, pero para realizar el enlace de datos a la interfaz de usuario tengo que trabajar con objetos de JavaScript con propiedades observables por Knockout.
- Quiero incluir datos relacionados, pero esto resulta difícil en el cliente.
- Tengo que enviar un conjunto de cambios al servidor para almacenarlos.
Con Breeze, puedo realizar directamente el enlace de datos a los datos resultantes. Configuraré Breeze para que emplee Knockout y, en respuesta, creará para mí las propiedades observables por Knockout. Esto significa que el trabajo con los datos relacionados se simplifica enormemente, ya que no debo traducirlos de JSON a objetos enlazables y no tengo que realizar el esfuerzo extra de redefinir grafos en el lado cliente al usar los resultados de las consultas.
Para usar Breeze, hay que realizar algunas configuraciones en el lado servidor. Para los pormenores lo remitiré a la documentación de Breeze; así podré concentrarme en la parte del acceso a datos del lado cliente del ejemplo. Como Breeze es mucho más amplio que lo que vamos a emplear en este ejemplo, una vez que le tome el gustillo podrá visitar breezejs.com para obtener más información.
En la figura 2 vemos dónde se inserta Breeze en el flujo de trabajo del lado servidor y del lado cliente.
Figura 2 La API de Breeze.NET sirve en el lado servidor, mientras que la API de BreezeJS sirve en el cliente
Realización de consultas desde Breeze
Gracias a la experiencia que tengo con OData y Entity Framework, las consultas con Breeze me resultan familiares. Trabajaré con la clase EntityManager de Breeze. EntityManager es capaz de leer el modelo de datos proporcionado por los metadatos de nuestro servicio y producir objetos de “entidad” en JavaScript por su propia cuenta, sin que tengamos que definir clases de entidad ni escribir mapeadores.
También hay que realizar un poco de configuración del lado cliente. El siguiente fragmento de código, por ejemplo, crea accesos directos a algunos espacios de nombres de Breeze y luego configura Breeze para que use Knockout y ASP.NET Web API:
var core = breeze.core,
entityModel = breeze.entityModel;
core.config.setProperties({
trackingImplementation: entityModel.entityTracking_ko,
remoteAccessImplementation: entityModel.remoteAccess_webApi
});
Breeze se puede configurar para que use diferentes marcos de enlace (como Backbone.js o la Biblioteca de Windows para JavaScript) y tecnologías de acceso a datos (como OData) alternativos.
A continuación, creo una instancia de EntityManager que conoce el identificador URI de mi servicio. EntityManager es comparable con un contexto de Entity Framework u OData. Actúa como la puerta de enlace hacia Breeze y almacena los datos en caché:
var manager = new entityModel.EntityManager('api/breezydevices');
Ahora puedo definir una consulta y dejar que EntityManager la ejecute para mí. Este código no es demasiado diferente a usar Entity Framework y LINQ to Entities, ni a trabajar con cualquiera de las API cliente para OData, así que fue mi parte favorita cuando aprendí a usar Breeze:
function getAllPersons(peopleArray) {
var query = new entityModel.EntityQuery()
.from("People")
.orderBy("FirstName, LastName");
return manager
.executeQuery(query)
.then(function (data) {
processResults(data,peopleArray); })
.fail(queryFailed);
};
Hago esto del lado cliente y puedo realizar la ejecución de las consultas en forma asincrónica. Por esto, el método executeQuery me permite definir lo que hay que hacer en caso de una consulta correcta (.then) y en caso de error (.fail).
Observe que paso una matriz (que, como veremos en seguida, es una matriz observable de Knockout) a getAllPersons. Si la consulta se ejecuta correctamente, paso esta matriz al método processResults, que vaciará la matriz y la rellenará con los datos del servicio. Previamente habría tenido que iterar por los resultados y crear cada instancia de PersonViewModel manualmente. Con Breeze, puedo usar directamente los datos devueltos:
function processResults(data, peopleArray) {
var persons = data.results;
peopleArray.removeAll();
persons.forEach(function (person) {
peopleArray.push(person);
});
}
Esto me entrega una matriz con los objetos Person que presentaré en la vista.
La función getAllPersons se encuentra dentro de un objeto que llamé dataservice. Emplearé dataservice en las siguientes líneas de código.
Un modelo de vista que se rellena solo
En el ejemplo del artículo de junio sobre Knockout, la consulta y los resultados estaban separados de la clase PersonViewModel que empleé para el enlace de datos dentro de la vista. Por lo tanto, ejecutaba la consulta y traducía los resultados a una instancia de PersonViewModel con código de asignación que yo tuve que escribir. Como con Breeze no necesito código de asignación ni la clase PersonViewModel, esta vez puedo hacer que mi aplicación sea un poco más inteligente y que muestre una matriz de objetos Person que se capturan desde la base de datos por medio de dataservice. Para reflejar esto, ahora tengo un objeto llamado PeopleViewModel. Esto expone la propiedad people que definí como matriz observable de Knockout y que relleno mediante dataservice.getAllPersons:
(function (root) {
var app = root.app;
var dataservice = app.dataservice;
var vm = {
people: ko.observableArray([]),
}
};
dataservice.getAllPersons(vm.people);
app.peopleViewModel = vm;
}(window));
En el ejemplo de la descarga encontrará un archivo llamado main.js, que es el punto de partida de la lógica de la aplicación. Contiene la siguiente línea de código, que llama el método applyBindings de Knockout:
ko.applyBindings(app.peopleViewModel, $("content").get(0));
El método applyBindings conecta las propiedades y métodos del modelo de vista con la interfaz de usuario en HTML mediante los enlaces de datos declarados en la vista.
En este caso, la vista es un trozo pequeño de HTML dentro de index.cshtml. Preste atención en el marcado del enlace de datos de Knockout que enlaza y presenta el nombre y apellido de cada objeto Person en la matriz people:
<ul data-bind="foreach: people">
<li class="person" >
<label data-bind="text: FirstName"></label>
<label data-bind="text: LastName"></label>
</li>
</ul>
Cuando ejecuto la aplicación, obtengo una vista de solo lectura con los datos de las personas, tal como se aprecia en la figura 3.
Figura 3 Breeze con Knockout permite consumir fácilmente los datos en JavaScript
Ajuste de JavaScript y Knockout para permitir ediciones
Como podrá recordar del artículo de junio, Knockout facilita el enlace de datos para permitir la edición. En conjunto con Breeze, esto es una combinación excelente para editar los datos fácilmente y conservarlos en el servidor.
Primero, agrego una función en el objeto dataservice que llama al método manager.saveChanges de Breeze. Al llamarlo, EntityManager de Breeze agrupa los cambios pendientes y los transmite por POST al servicio de Web API:
function saveChanges() {
manager.saveChanges();
}
Luego, expondré la nueva función saveChanges como una característica de dataservice:
var dataservice = {
getAllPersons: getAllPersons,
saveChanges: saveChanges,
};
Ahora, el objeto PeopleViewModel debe exponer su propio método save para realizar el enlace a la vista; la función del modelo de vista se delega al método saveChanges de dataservice. Aquí empleo una “función anónima” de JavaScript para definir el método save del modelo de vista:
var vm = {
people: ko.observableArray([]),
save: function () {
dataservice.saveChanges();
},
};
Luego, reemplazo las etiquetas con elementos de entrada (cuadros de texto), para que el usuario pueda editar los objetos Person. Debo cambiar de “text” a la palabra clave “value” de Knockout para habilitar un enlace bidireccional con la entrada del usuario. También agrego una imagen con un evento clic enlazado al método PeopleViewModel.save:
<img src="../../Images/save.png"
data-bind="click: save" title="Save Changes" />
<ul data-bind="foreach: people">
<li class="person" >
<form>
<label>First: </label><input
data-bind="value: FirstName" />
<label>Last: </label> <input
data-bind="value: LastName" />
</form>
</li>
</ul>
Eso es todo. ¡Breeze y Knockout se encargan del resto! En la figura 4 puede apreciar cómo aparecen los datos para la edición.
Figura 4 Uso de Breeze para guardar datos con JavaScript
Puedo editar todos estos campos y hacer clic en el botón Guardar. EntityManager de Breeze recogerá todos los cambios de los datos y los insertará en el servidor que, a su vez, los enviará a Entity Framework para actualizar la base de datos. Aunque no extenderé este ejemplo para incluir inserciones ni eliminaciones, Breeze también es capaz, desde luego, de realizar estas modificaciones.
Y para el gran final: datos relacionados
Para muchos desarrolladores, esta es la parte más temida de cualquier aplicación en JavaScript; y es precisamente la razón que me motivó a escribir este artículo.
Realizaré un cambio pequeño en mi script y agregaré un poco de marcado en el formulario para convertir cada persona en un formulario maestro y detalles editable para la persona.
El cambio lo realizaré en el objeto dataservice, donde modifico la consulta al agregar el método de expansión de consultas de Breeze para realizar una carga diligente de los dispositivos de cada persona junto con esta. Es posible que usted reconozca el término expand de OData o NHibernate; se parece a Include de Entity Framework (de hecho, Breeze también permite cargar datos relacionados fácilmente a posteriori):
var query = new entityModel.EntityQuery()
.from("People")
.expand("Devices")
.orderBy("FirstName, LastName");
Luego modifico la vista para que esta sepa cómo debe presentar los datos de los dispositivos, como se muestra en la figura 5.
Figura 5 Modificación de la vista para presentar los datos de los dispositivos
<ul data-bind="foreach: people">
<li class="person" >
<form>
<label>First: </label><input
data-bind="value: FirstName" />
<label>Last: </label> <input
data-bind="value: LastName" />
<br/>
<label>Devices: </label>
<ul class="device" data-bind="foreach: Devices">
<li>
<input data-bind="value: DeviceName"/>
</li>
</ul>
</form>
</li>
</ul>
Y ya está. Como puede apreciar en la figura 6, Breeze se encarga de la carga diligente y de la creación de los grafos en el lado cliente. También coordina los datos que se envían de vuelta al servicio para su actualización. En el lado servidor, EFContextProvider de Breeze pone en orden todos los cambios de los datos que recibe y se asegura de que Entity Framework reciba la información necesaria para conservar los datos en la base de datos.
Figura 6 Consumo y almacenamiento de los datos relacionados
Aunque esto fue muy fácil con una relación de uno a varios, a la hora de escribir esto, la versión beta de Breeze no admite relaciones de varios a varios.
Acceso a datos del lado cliente sin complicaciones
Bell me cuenta que la inspiración para Breeze le vino de su propia experiencia llena de complicaciones en un proyecto con mucho JavaScript y con una gran cantidad de acceso a datos. Su empresa IdeaBlade siempre se enfocó en la creación de soluciones para los problemas de manipulación de datos inconexos, y los desarrolladores pudieron aportar una gran cantidad de experiencia a este proyecto de código abierto. Siempre he sido reacia a embarcarme en proyectos que empleen mucho JavaScript, ya que, de entrada, mis conocimientos son deficientes y porque sé que las partes de acceso a datos me dejarían descontenta. Breeze me interesó muchísimo, nada más verlo. Y a pesar de no profundizar mucho, el último tema que toqué en este artículo, lo fácil que resulta consumir los datos relacionados, realmente me conquistó.
Julie Lerman ha recibido el premio al Profesional más valioso (MVP) de Microsoft, es consultora y mentor de .NET, y vive en las colinas de Vermont. Realiza presentaciones sobre acceso a datos y otros temas de Microsoft .NET en grupos de usuarios y congresos en todo el mundo. Mantiene un blog en thedatafarm.com/blog y es la autora de “Programming Entity Framework” (2010), además de una edición para Code First (2011) y una para DbContext (2012), todos de O’Reilly Media. También puede seguir a Julie en Twitter (twitter.com/julielerman).
Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Ward Bell