Cuando queremos representar datos en nuestras páginas a partir de un template que se repite, lo más recomendable es utilizar un motor de templates como JsRender o JsViews, la pregunta es qué alternativas tenemos en WinJS?
Motor de templates integrado en WinJS
Ya existe un tipo definido para manejar templates dentro de WinJS
new WinJS.Binding.Template();
con él podemos indicar a un ListView cómo renderizar cada ítem, y también podemos generar nuestros templates y utilizarlos sobre un elemento del DOM.
Primer crear el template en HTML
Vamos a crear un proyecto metro en blanco y modificar el HTML por defecto para que se vea así:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application16</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- Application16 references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<p>
<div id="template">
<span data-win-bind="innerText:valor"></span>
</div>
</p>
<div id="container"></div>
</body>
</html
La idea es sencilla, utilizar el div con id template como template y renderizarlo dentro del div con id container
Antes que eso vamos a correr la aplicación y ver qué pasa

Nada, vemos la pantalla en negro, cosa que en principio está bien, pero vamos a ver el DOM Explorer a ver qué es lo que realmente tiene el navegador en memoria


Vemos que el div template se está renderizando cosa que no es deseable, sin embargo no nos preocupamos, vamos a default.js y hacemos una pequeña modificación para decirle a WinJS que ese elemento debe ser tratado como un template.
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
WinJS.UI.processAll().then(function () {
//recuperamos el elemento del DOM
var templateElement = document.querySelector("#template");
//creamos un template a partir del elemento recuperado
var template = new WinJS.Binding.Template(templateElement);
});
};
app.start();
})();
Simplemente recuperamos el elemento del DOM y creamos un objeto del tipo WinJS.Binding.Template a partir de él, corremos la aplicación y vemos el DOM Explorer nuevamente

Vemos que WinJS agregó al elemento que definimos como template “display: none” para que no se vea en la página, entonces primer tema solucionado.
Renderizando datos a partir del tempalte
Ahora vamos a crear un set de datos y utilizar el template recién definido
(function () {
"use strict";
//creamos un objeto con una propiedad
var data = {valor:"primer valor del set de datos"};
var app = WinJS.Application;
app.onactivated = function (eventObject) {
WinJS.UI.processAll().then(function () {
//recuperamos el elemento del DOM
var templateElement = document.querySelector("#template");
//creamos un template a partir del elemento recuperado
var template = new WinJS.Binding.Template(templateElement);
//recuperamos el elemento sobre el cual vamos a renderizar los datos
var target = document.querySelector("#container");
//renderizamos los datos sobre el destino
template.render(data, target);
});
};
app.start();
})();
creamos una variable en la que colocamos un objeto que será nuestro origen de datos, luego utlizamos la función render del template para utlizar esos datos y renderizar el resultado sobre el destino.

Perfecto, qué pasa si queremos renderizar un array de datos, entonces tenermos que usar un bucle del siguiente modo:
(function () {
"use strict";
//creamos un objeto con una propiedad
var data = [{ valor: "primer valor del set de datos" }, { valor: "segundo valor del set de datos" }, { valor: "tercer valor del set de datos" }];
var app = WinJS.Application;
app.onactivated = function (eventObject) {
WinJS.UI.processAll().then(function () {
//recuperamos el elemento del DOM
var templateElement = document.querySelector("#template");
//creamos un template a partir del elemento recuperado
var template = new WinJS.Binding.Template(templateElement);
//recuperamos el elemento sobre el cual vamos a renderizar los datos
var target = document.querySelector("#container");
//renderizamos los datos sobre el destino
for (var i = 0; i < data.length; i++) {
template.render(data[i], target);
}
});
};
app.start();
})();
y con eso solucionamos el problema, o podemos utilizar la función foreach que agrega WinJS a los array, del siguiente modo:
(function () {
"use strict";
//creamos un objeto con una propiedad
var data = [{ valor: "primer valor del set de datos" }, { valor: "segundo valor del set de datos" }, { valor: "tercer valor del set de datos" }];
var app = WinJS.Application;
app.onactivated = function (eventObject) {
WinJS.UI.processAll().then(function () {
//recuperamos el elemento del DOM
var templateElement = document.querySelector("#template");
//creamos un template a partir del elemento recuperado
var template = new WinJS.Binding.Template(templateElement);
//recuperamos el elemento sobre el cual vamos a renderizar los datos
var target = document.querySelector("#container");
//renderizamos los datos sobre el destino
data.forEach(function (item) {
template.render(item, target);
});
});
};
app.start();
})();
y listo:

Utilizando templates remotos
Una opción interesante es la capacidad de poder utilizar un template procedente de otro archivo, esto es bien sencillo, creamos un nuevo archivo template.html y colocamos todo el contenido de nuestro template dentro.
<div id="template">
<p>
<span data-win-bind="innerText:valor" ></span>
</p>
</div>
y modificamos el código javascript para utilizar el template externo
(function () {
"use strict";
//creamos un objeto con una propiedad
var data = [{ valor: "primer valor del set de datos" }, { valor: "segundo valor del set de datos" }];
var app = WinJS.Application;
app.onactivated = function (eventObject) {
WinJS.UI.processAll().then(function () {
//recuperamos el elemento del DOM
var templateElement = document.querySelector("#template");
//creamos un template a partir del archio remoto
var template = new WinJS.Binding.Template(null, { href: "/html/template.html" });
//recuperamos el elemento sobre el cual vamos a renderizar los datos
var target = document.querySelector("#container");
//renderizamos los datos sobre el destino
data.forEach(function (item) {
template.render(item, target);
});
});
};
app.start();
})();
simplemente cuando creamos el objeto template le indicamos el parámetro href apuntando al archivo externo, el resto es igual.
Hasta la próxima.
Una buena costumbre al desarrollar aplicaciones metro con Javascript es tener nuestro código en módulos separados utilizando funciones autoejecutables y así tener un scope acotado.
Existe un pequeño problema, al ser un archivo separado no tenemos intellisense de las librerías de WinJS (ni de ningúna otra), algo así:

Este problema se incrementa ya que no tenemos referencias físicas en el proyecto de los archivos base de WinJS, sino que accedemos a ellos a través de un path especial

Sin embargo podemos hacer algo para tener intellisense del WinJS en nuestro módulo del siguiente modo:

Simplemente agregamos los tags reference con el atributo path apuntando a los path especiales de las librerías de WinJS y tenemos intellisense.
Hasta la próxima.
UPDATE 2012.04.12
Se aplazó la VAN para el Viernes 27 de Abril a las 18:00hs GMT

Una vez más voy a tener la suerte de participar de una VAN (Virtual Alt.Net), para la comunidad de Alt.Net Hispano, en esta oportunidad será sobre Aplicaciones Metro con Javascript.

El tema
La idea hablar de Windows 8 y las aplicaciones metro, comenzando con un poco de teoría sobre la plataforma y qué son y cuáles los lineamientos de las aplicaciones Metro.
Vamos a tratar de ir contando un poco el modelo de programación y cómo nos ayuda WinJS para todo esto
No voy a estar sólo
En la VAN me va a acompañar Rubén Altman del equipo de Kinetica y tal vez algunas sorpresas.
La fecha
Será el viernes 13 de Abril a las 18hs GMT, las 15hs de Argentina.
Cómo asistir?
Para participar de la VAN es necesario tener instalado Microsoft Office Live Meeting 2007 client
El Link de acceso es acá
Pueden dejar comentarios e inquietudes en la lista de correo
Para más información acerca de las VAN pueden acceder acá
Entonces, hecha la invitación nos vemos el 13.
Leonardo.

Durante las primeras semanas de Abril estaré en el Grupo de Usuarios de Microsoft dando un nuevo taller sobre MVC, en este caso actualizado a la versión 3 y hablando un poco sobre las novedades de MVC 4.
Contenido
Por supuesto que debido a la cantidad de temas y la duración del taller el nivel será introductorio, la idea es dar un repaso sobre los conceptos más sobresalientes y no dejar de nombrar algunos detalles de implementación en situaciones reales, el temario será el siguiente:
Introducción a MVC
- Teoría
- Separación de conceptos
- Escalabilidad
- Implementaciones: ASP.NET MVC, Monorail.
- Diferencias entre MVC y WebForms / ASP clásico
- Colaboración
- Legibilidad
- Testeo
- Por qué necesitamos MVC?
Ruteo
- Modelos de ruteo
- SEO
- Indización de buscadores
- Configuración personalizada
- Áreas
El modelo
- Responsabilidad del modelo
- Separación
- Testeo
El controlador
- Responsabilidades
- Action Filters
- Model Binders
- ActionResult
- FileResult
- JSONResult
- JavaScriptResult
La vista
- Responsabilidad
- Motores de renderización
- HTMLHelpers
- ViewData
- Vistas tipadas
- Validaciones
- Testeo
Aspectos avanzados
- Ajax
- jQuery y MVC
- MVC 4
- Mejoras en Razor
- ASP.NET Web API
El taller se desarrollará íntegramente con PC para cada asistente para poder experimentar con los conceptos que vamos repasando.
Fechas y duración
Serán cuatro encuentros:
- Lunes 16 de Abril de 2012
- Martes 17
- Lunes 23
- Martes 24
el horarios será de 18:00 a 22:00 hs. El lugar es en Rivadavia 1479 - Piso 1 - Oficina A
Ciudad Autónoma de Bs.As.
Registración
Dejo el link para registrarse, el cupo es limitado y es con costo.
Nos vemos, Leonardo.
En el post anterior sobre Metro vimos cómo se puede hacer para “bindear” el modelo a la vista, existe una particularidad en el ejemplo que pasó casi inadvertida y tiene que ver con la forma de organizar las aplicaciones Metro Javascript.
El patrón module en Javascript
En este post vimos cómo se pueden utilizar funciones auto-ejecutable para tener un ámbito de visibilidad limitado y que nuestro código no interfiera con el resto de la aplicación, la utilización de esta técnica se conoce como el patrón module.
El inconveniente de utilizarlo es que lo que definamos dentro de la función no podrás ser visto desde afuera, es por eso que yo definí el modelo como una propiedad del objeto document el cual es visible globalmente, sin embargo, existe una alternativa a esto.
Namespaces en Metro
Podemos crear un objeto global y generar un espacio de nombres y allí poner los orígenes de datos, ya vimos cómo podemos hacerlo, sin embargo WinJS ya trae consigo un mecanismo para crear espacios de nombre.
La idea es definir dentro de un módulo nuestro origen de datos pero dentro de un espacio de nombres que pueda ser visto globalmente para poder referenciarlo desde la vista con su nombre completo y el WinJS se ocupe del binding. Entonces manos a la obra>
Un ejemplo vale más de mil palabras
Creamos una nueva aplicación Metro de Javascript y vamos directo al archivo default.js
// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
WinJS.UI.processAll();
}
};
app.oncheckpoint = function (eventObject) {
// TODO: This application is about to be suspended. Save any state
// that needs to persist across suspensions here. You might use the
// WinJS.Application.sessionState object, which is automatically
// saved and restored across suspension. If you need to complete an
// asynchronous operation before your application is suspended, call
// eventObject.setPromise().
};
app.start();
})();
Inicialmente tenemos esto, vamos a generar un origen de datos y colocarlo como propiedad del objeto document
(function () {
"use strict";
//creamos el origen de datos
var dataSource = { nombre: 'leonardo', apellido: 'micheloni' };
//lo asignamos como una propiedad del document para tener visibilidad fuera de
//este módulo
document.dataSource = dataSource;
var app = WinJS.Application;
app.onactivated = function (eventObject) {
//le decimos a WinJS que ejecute los binding
WinJS.Binding.processAll();
};
app.start();
})();
Modificamos el HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application16</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- Application16 references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<div data-win-bindsource="document.dataSource">
<p data-win-bind="innerText: nombre"></p>
<p data-win-bind="innerText: apellido"></p>
</div>
</body>
</html>
y funciona muy bien

Utilizando la función namespace de WinJS
Ahora, vamos a poner nuestro origen de datos dentro de otro archivo js y agregarlo a un namespace y luego referenciarlo desde la vista
default.js
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
//le decimos a WinJS que ejecute los binding
WinJS.Binding.processAll();
};
app.start();
})();
data.js
(function () {
'use strict';
//creamos el origen de datos
var dataSource = { nombre: 'leonardo', apellido: 'micheloni' };
//definimos el namespace "datos" y colocamos nuestro origen dentro
WinJS.Namespace.define("datos", dataSource);
})();
default.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Application16</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
<!-- referenciamos el archivo donde definimos nuestro origen de datos-->
<script src="/js/data.js"></script>
</head>
<body>
<div data-win-bindsource="document.dataSource">
<p data-win-bind="innerText: nombre"></p>
<p data-win-bind="innerText: apellido"></p>
</div>
</body>
</html>
y el resultado es igual al anterior

De más está decir que son pocas las situaciones en la cuales no deberíamos colocar nuestro código en un módulo separado y referenciarlo utilizando namespaces.
Hasta la próxima, Leonardo.
Si bien Javascript no provee un mecanismo propio para la creación de espacios de nombres es posible lograr este tipo de organización de nuestro código con algunas técnicas.
Primer intento
Podemos crear un objeto y utilizarlo como raíz del namespace y agregar los objetos que queramos dentro del namespace como propiedades de la raíz.
<html>
<head>
<script type="text/javascript">
//creamos nuestro namespace
var MiEspacio = {};
//agregamos un objeto "dentro" del namespace
MiEspacio.Objeto1 = function(){};
//entonces podemos hacer
MiEspacio.Objeto1.propiedad1 = "hola";
//y comprobamos que se pueda leer la propiedad
console.log(MiEspacio.Objeto1.propiedad1);
</script>
</head>
<body></body>
</html>
podemos repetir esto para tener un namespace con la cantidad de niveles que nos sea necesario
<html>
<head>
<script type="text/javascript">
//creamos nuestro namespace
var MiEspacio = {};
//agregamos un objeto "dentro" del namespace y del módulo
MiEspacio.modulo1.Objeto1 = function(){};
//entonces podemos hacer
MiEspacio.modulo1.Objeto1.propiedad1 = "hola";
//y comprobamos que se pueda leer la propiedad
console.log(MiEspacio.modulo1.Objeto1.propiedad1);
</script>
</head>
<body></body>
</html>
por supuesto que para tenet una visibilidad alta deberíamos definir el objeto raíz lo más global posible.
Creando una función para generar namespaces
Vamos a crear una función que nos permita generar namespaces de una manera sencilla y mejor aún, que verifique que no se creen dos veces los mismo, o peor aún, que se sobre-escriban elementos ya definidos.
La idea es hacer algo así:
UTIL = {}; //definimos el objeto UTIL
RAIZ = {}; //definimos el objeto raíz
UTIL.namespace = function (expression, members){
//dividimos la expresion en cada punto
var partes = expression.split('.');
//el objeto raíz es el primer elemento
var raiz = eval(partes[0]);
//quitamos de la expresion la raíz
partes = partes.slice(1);
for(i = 0;i < partes.length;i++){
//en caso que no está ya definido es módulo
//se crea
if(typeof raiz[partes[i]] === 'undefined'){
//es el último elemento agregamos los miembros
if(i==partes.length-1){
raiz[partes[i]] = members;
}else{
raiz[partes[i]] = {};
}
}
}
};
UTIL.namespace("RAIZ.pp", {objeto1: {propiedad1: "valor"}});
y listo

por supuesto que esta función es muy mejorable, por ejemplo podría crear el objeto raíz si no existe, pero sirve de ejemplo.
Nos leemos.
Leonardo.
Las aplicaciones metro con Javascript utilizar al patron MVVM, WinJS se encarga de gestionar el funcionamiento del mismo.
Model View-View Model
La idea de este patrón es tener un modelo asociado a una vista de la aplicación, es decir: si tenemos una vista que muestra un listado de productos ese listado es el modelo de la vista, cuando para nuestro dominio el producto es parte del modelo y el listado no existe como tal.
Algo adicional es que la asociación del modelo de la vista con los elementos de la vista (en este caso elementos HTML) es automática y no tenemos que poner sintaxis tipo MVC y llamadas a helpers para que se muestren los datos sino que es un mecanismo más transparente
Atributos data-algo
HTML5 permite definir como válidos atributos para cualquier elemento con el nombre data-nombre-subnombre para utilizarlos de manera personalizada, justamente WinJS se basa en esto para asociar el modelo con la la vista.
Manos a la obra
Vamos a hacer una aplicación Metro bien sencilla para demostrar cómo funciona esto, vamos al Visual Studio y creamos una aplicación Metro Javascript en blanco.

La aplicación es bien simple
Vamos al archivo default.js y vemos lo siguiente
// For an introduction to the Blank template, see the following documentation:
// http://go.microsoft.com/fwlink/?LinkId=232509
(function () {
"use strict";
var app = WinJS.Application;
app.onactivated = function (eventObject) {
if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
if (eventObject.detail.previousExecutionState !== Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) {
// TODO: This application has been newly launched. Initialize
// your application here.
} else {
// TODO: This application has been reactivated from suspension.
// Restore application state here.
}
WinJS.UI.processAll();
}
};
app.oncheckpoint = function (eventObject) {
// TODO: This application is about to be suspended. Save any state
// that needs to persist across suspensions here. You might use the
// WinJS.Application.sessionState object, which is automatically
// saved and restored across suspension. If you need to complete an
// asynchronous operation before your application is suspended, call
// eventObject.setPromise().
};
app.start();
})();
En principio este js no tiene más que los eventos de la aplicación asociados, vamos a modificar levemente el código para crear un objeto que será nuestro modelo y pasarlo a la vista
(function () {
"use strict";
var app = WinJS.Application;
//colocamos nuestro código para que se ejecute cuando la aplicación se activa
app.onactivated = function (eventObject) {
//creamos nuestro modelo
var modelo = { nombre: "leonardo", apellido: "micheloni" };
//le decimos a WinJS que procese los bindings y le especificamos cuál
//el primer parámetro es el objeto DOM desde el cual se ejecuta el binding
//en caso de poner null se utiliza document
//el segundo parámetro es el modelo
WinJS.Binding.processAll(null, modelo);
};
//iniciamos la aplicación
app.start();
})();
con eso alcanza, quité el código que no va a hacer falta, y agregué dos líneas, en la primer defino un objeto que será el modelo, con WinJS.Binding.processAll ejecutamos los bindings, ahora vamos a modificar la vista, que en principio es así:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ejemploBinding</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- ejemploBinding references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<p>Content goes here</p>
</body>
</html>
la modificamos para que quede así:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ejemploBinding</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- ejemploBinding references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<div>
<p data-win-bind="innerText: nombre"></p>
<p data-win-bind="innerText: apellido"></p>
</div>
</body>
</html>
la propiedad data-win-bind permite indicar una propiedad del elemento HTML (en este caso innerText) y asignarlo un valor proveniente desde el modelo, en este caso nombre, el resultado

Un poco más declarativo
Podemos hacer lo mismo sin necesidad de indicar por código la asociación del modelo con la vista, modificamos primero el js
(function () {
"use strict";
var app = WinJS.Application;
//colocamos nuestro modelo en el document, para poder verlo globalmente
document.modelo = { nombre: "leonardo", apellido: "micheloni" };
app.onactivated = function (eventObject) {
WinJS.Binding.processAll();
};
app.start();
})();
Simplemente declaramos el modelo como una propiedad del objeto document, no es lo más feliz tal vez sin embargo sin usar namespaces (que será tema de otro post) es lo que tenemos más a mano. Vamos a ver la vista.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ejemploBinding</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.0.6/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.0.6/js/base.js"></script>
<script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
<!-- ejemploBinding references -->
<link href="/css/default.css" rel="stylesheet">
<script src="/js/default.js"></script>
</head>
<body>
<div data-win-bindsource="document.modelo" >
<p data-win-bind="innerText: nombre"></p>
<p data-win-bind="innerText: apellido"></p>
</div>
</body>
</html>
nótese que en declaramos el data-win-bindsource en el div que contiene a los párrafos, esta es otro de las propiedades de WinJS para manipular los datos de la vista.
Lo ejecutamos y obtenemos el mismo resultado

Mágico, nos leemos.
Leonardo.
Ya hemos hablado acerca de cómo subir archivos con ASP.NET MVC y algunos detalles sobre formularios y demás, en esta oportunidad vamos a ver cómo podemos lograr lo mismo pero usando AJAX.
Primero el lado del cliente
Del lado del cliente podemos acceder al objeto File controlado por el usuario, simplemente haciendo esto (usando jQuery claro)
$(function () {
var file = $("#file1");
});
Uno pensaría (como lo hacía yo hasta éste momento) que no es posible acceder a un archivo desde Javascript, sin embargo el sandbox no nos impide acceder a un objeto creado y manejado por el usuario, por lo tanto podemos acceder al contenido del archivos una vez que el usuario lo haya seleccionado, modificamos el código para tal fin
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
<script src="@Url.Content("~/Scripts/jquery-1.5.1.js")" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#frm :submit").click(function () {
var file = $("#file1").get();
return false;
});
});
</script>
</head>
<body>
<div>
<form id="frm" action="@Url.Action("Upload")" method="post">
<input type="file" id="file1" />
<input type="submit" value="enviar" />
</form>
</div>
</body>
</html>
Entonces atrapamos el evento click del botón submit del formulario para que cuando se ejecute recuperar el objeto File, retornamos false para que no se ejecute el evento por defecto, es decir, que no se envíe el formulario.
Enviando el contenido del objeto File por Ajax
Para enviar el contenido del File por Ajax vamos a tener que utilizar el la función AJAX de jQuery directamente ya que necesitamos más control que el que nos brinda $.post. para lograrlo modificamos el código nuevamente.
$(function () {
$("#frm :submit").click(function () {
var file = $("#file1").get();
$.ajax({
type: 'POST',
url: $("#frm").attr("action"),
data: file,
dataType: "application/json",
contentType: "application/octet-stream"
});
return false;
});
});
nótese que ponemos en el Content-Type el tipo octet-stream para poder enviar el archivo como un stream y leerlo fácilmente desde C#
Leyendo el contenido desde ASP.NET MVC
Lo que hacemos es enviar por AJAX utilizando POST el contenido del objeto File, esto está muy bien, vamos a ver cómo lo recibimos desde el lado de ASP.NET MVC
using System.Web.Mvc;
using System.IO;
using System.Text;
namespace MvcApplication6.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Upload()
{
var file = new StreamReader(this.HttpContext.Request.InputStream, Encoding.UTF8).ReadToEnd();
return Json(new { contenidoRecibido = file });
}
}
}
Para leer el archivo enviado tenemos que acceder el InputStream ya que estamos enviando el contenido como parte del cuerpo de HTTP. Un detalle más es que por defecto viaja con Encoding UTF-8 es por eso que para leerlo tenemos que especificarlo en el StreamReader. Por supuesto que este ejemplo sirve sólo para archivos de texto, no es difícil modificarlo para cualquier tipo de contenido.

y listo, faltaría poner código del lado del cliente para que ante la respuesta correcta (no la que puse que es un ejemplo) de feedback al usuario.
Nos leemos, Leonardo.
Desde el pasado 29 de Febrero ya se encuentra disponible para la descarga la versión 11 de Visual Studio. Así que vamos a ver un poquito de lo que trae.
Instalación
Podemos instalar Visual Studio 11 en Windows 7, o en Windows 8 Consumer Preview que se puede descargar desde acá, yo instalé la versión Professional en Windows 7 si bien se pueden descargar varias versiones incluso la Ultimate

y luego de un buen rato (en mi PC un i5 con 8GB 10min)

Iniciamos Visual Studio 11 y ya vemos cambios
Una pequeña vuelta por las novedades de la IDE
Muchas cosas han cambiado en la IDE, vamos a ver algunas de ellas para ir saboreando lo que se viene en nuestra herramienta de todos los días

Cambios en el aspecto

Lo que vemos es un diseño totalmente renovado que incluye el aspecto, la iconografía y la ubicación de los comandos
Compatibilidad hacia atrás
Hay muy buenas noticias, con Visual Studio 11 podemos crear proyectos para cualquier framework desde el 2.0 en adelante

Y las aplicaciones Metro?
Bien, si no estamos en Windows 8 no tenemos la posibilidad de crear Metro Apps si estamos en Windows 8 por supuesto que si.
Mejoras en el Solution Explorer
Dentro del Solution explorer tenemos varias consultas sobre los elementos que forman parte de nuestra solución
Búsqueda por nombre

Tipos base y tipos derivados



Miembros

Desde acá también podemos navegar directo al código del miembro seleccionando el botón Preview Selected Item

Múltiples instancias del Solution Explorer
A partir de cualquier elemento podemos crear una nueva vista del Solution Explorer y tener múltiples instancias con el comando New View

O directamente posicionar la vista a partir de un punto con el comando Scope To This


Page Inspector
Si trabajamos en Web cuando queramos ejecutar nuestra aplicación tenemos dos novedades, primero poder seleccionar el navegador directo desde la barra de herramientas por defecto

La otra es una herramienta nueva, el Page Inspector, una suerte de Internet Explorer Developer Toolbar, Firebug o Dragonfly si prefieren, integrado en Visual Studio

Bueno, como un primer vistazo es más que interesante, hay muchas cosas más con relación a la navegación de elementos y la forma de manejar los tabs abiertos, además la versión Professional tiene muchas más herramientas que antes, por ejemplo:
- Nueva ventana Unit Test Explorer
- Métricas de código
- Nuget
- Análisis de código (Stylecop)
- Análisis de performance / profiler
En definitiva, muchas de las cosas que eran exclusivas de la versión Ultimate un poco cara para los programadores de a pié como un servidor
en la versión Professional.
Hasta la próxima, Leonardo.
ASP.NET Web API es una nueva capacidad agregada a ASP.NET MVC desde el developer preview de ASP.NET MVC 4, la idea es proveer un mecanismo pensado desde el principio para cubrir todas las interacciones no-humanas de nuestra aplicación, esto es principalmente llamadas Ajax.
Comenzando a usar la Web API
Una vez instalada la developer preview de MVC 4 tenemos el template de Web API disponible en Visual Studio 2011.
Abrimos Visual Studio y seleccionamos el template de MVC 4

Y ahora viene la parte diferentes, nos aparecen otras opciones desde la cuales podemos crear un proyecto Web API

Perfecto, una vez hecho esto tenemos la estructura del proyecto creada, si miramos lo que genera el template por defecto vemos que existen dos controladores:
- HomeController
- ValuesController

HomeController es un controlador tal cual lo conocemos
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace webApiTest.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
Hasta ahora nada raro, sin embargo si vemos el código del ValuesController nos encontramos con algunas novedades
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace webApiTest.Controllers
{
public class ValuesController : ApiController
{
// GET /api/values
public IEnumerable Get()
{
return new string[] { "value1", "value2" };
}
// GET /api/values/5
public string Get(int id)
{
return "value";
}
// POST /api/values
public void Post(string value)
{
}
// PUT /api/values/5
public void Put(int id, string value)
{
}
// DELETE /api/values/5
public void Delete(int id)
{
}
}
}
Vemos dos cosas que nos llaman la atención
- Hereda de ApiController en lugar de Controller
- Las acciones retornan void o string el lugar de ActionResult
Otra cosa no menos es que las acciones tiene nombres REST-friendly como:
Vamos a ver qué dice el global.asax sobre las rutas
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace webApiTest
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class WebApiApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
BundleTable.Bundles.RegisterTemplateBundles();
}
}
}
Además de la clásica ruta Controlador/Acción, vemos una ruta (antes de ésta) que dice:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
justamente en MapHttpRoute está la magia, pero qué es lo qué nos permite hacer
- Consultas tipo REST, relacionando los nombres de las acciones con los verbos HTTP
- Negociar, tipos de retorno, por ejemplo a través de headers HTTP nos devuelve XML o JSON
Vamos a modificar un poco el código por defecto para ver si esto es verdad
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
namespace webApiTest.Controllers
{
public class ValuesController : ApiController
{
private static Dictionary values;
public ValuesController()
{
if (values == null)
{
values = new Dictionary();
values.Add(1, "valor1");
values.Add(2, "valor2");
}
}
public IDictionary Get()
{
return values;
}
public string Get(int id)
{
return values.First(v => v.Key == id).Value;
}
public void Post(string value)
{
var newId = ((int)values.Max(i=>i.Key)+1);
this.Put(newId, value);
}
public void Put(int id, string value)
{
this.Delete(id);
values.Add(id, value);
}
public void Delete(int id)
{
values.Remove(id);
}
}
}
Perfecto modificamos un poco para tener todos los verbos REST implementados y que funcione como debe, ahora para probarlo vamos al Fiddler, primer prueba
GET
En la solapa de “Compose” creamos un request utilizando GET a la Url configurada en global.asax

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 14:27:08 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 89
[{"Key":1,"Value":"valor1"},{"Key":3,"Value":"valor3"},{"Key":4,"Value":"valor por put"}]
Nos responde como esperamos, en formato JSON, esto es porque pusimos (a propósito) el header Content-Type:application/json, podemos “negociar” que nos devuelve XML sin mucho esfuerzo, cambiando simplemente este header
Negociando el tipo de respuesta
Cambiamos en nuestro request el Content-Type y voilá
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 16:23:05 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/xml; charset=utf-8
Connection: Close
Content-Length: 262
<?xml version="1.0" encoding="utf-8"?><....
Mapeo de verbos automático
Bien, en nuestro código cambiamos las acciones para que funcionen como si fuera un ABM, asociamos cada acción de controlador con alta, baja, modificación, consulta..veámos
Consulta con GET
GET http://localhost:51757/api/values HTTP/1.1
Host: localhost:51757
Content-Type: application/json
Respuesta
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 16:43:59 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 55
[{"Key":1,"Value":"valor1"},{"Key":2,"Value":"valor2"}]
Agregar con POST
POST http://localhost:51757/api/values HTTP/1.1
Host: localhost:51757
Content-Type: application/json
Content-Length: 44
{"id":"4","value":"valor agregado con POST"}
Y volvemos a consultar
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 18:17:42 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 99
[{"Key":1,"Value":"valor1"},{"Key":2,"Value":"valor2"},{"Key":3,"Value":"valor agregado con POST"}]
Modificación con PUT
PUT http://localhost:51757/api/values HTTP/1.1
Host: localhost:51757
Content-Type: application/json
Content-Length: 52
{"id":"2","value":"el elemeto 2 modificado con PUT"}
Volvemos a consultar
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 18:20:44 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 124
[{"Key":1,"Value":"valor1"},{"Key":2,"Value":"el elemeto 2 modificado con PUT"},{"Key":3,"Value":"valor agregado con POST"}]
y por último borrar con DELETE
DELETE http://localhost:51757/api/values/1 HTTP/1.1
Host: localhost:51757
Content-Type: application/json
y el resultado
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Sun, 19 Feb 2012 19:42:16 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Connection: Close
Content-Length: 97
[{"Key":2,"Value":"el elemeto 2 modificado con PUT"},{"Key":3,"Value":"valor agregado con POST"}]
Notemos que en el caso de DELETE al no soportar enviar contenido en el body se utiliza la URL para identificar el recurso, como se declaró en la ruta del global.asax.
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
En resumen, ASP.NET Web API nos brinda un mecanismo para las llamas que no tiene un resultado visible para el humano, y nos evitar poner en nuestros controladores estos métodos o hacer un servicio WCF con WebHttpBinding, además está pensada para trabajar como REST.
La próxima vez vamos a ver cómo integrar esto con jQuery, hasta entonces.