Una sinfonía en C#

Un pequeño aporte a la comunidad de habla hispana.

Cómo subir archivos con ASP.NET MVC + AJAX

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.

image

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.

Visual Studio 11 Beta disponible!

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

image

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

image

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

image

Cambios en el aspecto

image

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

image

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

image

Tipos base y tipos derivados

image

image

image

Miembros

image

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

image

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

image

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

image

image

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

image

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

image

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 Sonrisa en la versión Professional.

Hasta la próxima, Leonardo.

ASP.NET Web API, o cómo crear servicios REST sin recurrir a WCF?

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

image

 

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

image

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

image

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:

  • Get
  • Post
  • Put
  • Delete

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

image

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.

Compilador como servicio, el proyecto Roslyn

Nunca hiciste una aplicación de consola para probar un método del framework? nunca quisiste ver qué resultado arrojaba a la evaluación de un operador sin tener que recurrir a la venta de inspección o algo similar? si es así lo que estabas pidiendo era un compilador como servicio.

Compilador como servicio

La idea de un compilador como servicio es un servicio (obviamente) al cual le puedo pedir que vaya evaluando y compilando expresiones, por ejemplo desde una ventana interactiva. En octubre de 2011 Microsoft dio a conocer el proyecto Roslyn un compilador como servicio que soporta C# y Visual Basic en versión CTP.

Roslyn, requisitos

Para poder tener Roslyn en nuestras máquina necesitamos contar con algunos requisitos previos:

Ventana interactiva en Visual Studio 2010

Bien, vamos a empezar a jugar un poco con Roslyn, abrimos el Visual Studio 2010 y vamos al menú View > other windows > C# interactive window

image

Y entonces vemos la ventana.

image

Qué podemos hacer con esto? prácticamente cualquier cosa que podemos hacer un programa normal, lo primero que se me ocurre aclarar es que cada expresión que escribimos es evaluada en el momento que el analizador determina que se encuentra completa, hagamos una prueba simple, escribimos lo siguiente:

Loading context from 'CSharpInteractive.rsp'.
> var s = "hola mundo"
. 

vemos que si no ponemos el ; al final de la expresión el interprete nos muestra un . indicando que no detecta que la expresión ha finalizado ni puede hacer nada con ella, apretemos el botón de arriba a la izquierda “Reset” y comencemos de nuevo

Resetting execution engine
Loading context from 'CSharpInteractive.rsp'.
> var s = "hola mundo";
> 

En esta oportunidad escribimos el ; y el interprete nos devuelve nuevamente el cursor >, ahora podemos hacer:

Resetting execution engine
Loading context from 'CSharpInteractive.rsp'.
> var s = "hola mundo";
> s
"hola mundo"
> 

cuando Rolsyn nos devuelve el cursor simplemente escribimos s sin ; y presionamos Enter y el interprete nos muestra su valor, si bien esto no es válido dentro del editor del IDE en el interprete línea a línea podemos preguntar por el valor de un objeto de esta manera y seguir con el código, ya que del modo que lo escribimos es una expresión que se puede evaluar así como está.

Presionemos Reset nuevamente y veamos algo más complicado

Resetting execution engine
Loading context from 'CSharpInteractive.rsp'.
> var s = System.IO.File.ReadAllText(@"C:\setup.log");
> s
"[InstallShield Silent]
Version=v7.00
File=Log File
[ResponseResult]
ResultCode=0
[Application]
Name=Integrated Camera Driver Installer Package Ver.1.1.0.1147
Version=1.1.0.1147
Company=RI ...
> 

En este ejemplo leo el contenido de un driver en mi PC, lo dejo en una variable y lo imprimo.

Hagamos algo un poco más complicado:

Resetting execution engine
Loading context from 'CSharpInteractive.rsp'.
> var numeros = new List();
> numeros.Add(2);
> numeros.Add(1);
> numeros.Add(5);
> numeros.Add(3);
> numeros.Add(4);
> foreach (var par in numeros.Where(n => n % 2 == 0))
. {
.     Console.WriteLine(par);
. }
2
4
> 

Muy interesante, si bien algunas cosas no funcionan como la declaración de objetos con inicializador y la declaración implícita de arrays, otras muy copadas como la expresiones lambda sí.

El futuro

He escuchado rumores que la versión final de Rolsyn va a estar disponible en Visual Studio 2012 junto con la creación de aplicaciones Metro para Windows 8 no son más que rumores, veremos qué pasa.

hasta la próxima.

Filtrar una array con jQuery

Un truco simple pero algo desconocido, cómo hacer para filtrar el contenido de un array de cualquier tipo para que sólo conserve los elementos que cumplen con un criterio en particular, al mejor estilo Where de Linq, bien, jQuery provee una funcionalidad para tal fin

jQuery.grep utilidad para filtrar colecciones

Grep es una pequeña función que hace algo simple pero útil: a partir de un array dado, nos pide un callback en el cual evaluamos el valor de cada ítem del array y le indicamos si cumple con nuestro criterio de aceptación (un predicado por decirlo así) como resultado nos da un array sólo con los elementos que cumplen dicho criterio, veamos un ejemplo.

Ejemplo de filtrar los string que tienen contenido

var arraySucio = ['','a','e','','i','o','u','','',];

var arrayLimpio = $.grep(arraySucio, function(element){
	return element.length>0;
});

console.log("=====>Array original");
$.each(arraySucio, function(){
	console.log(this);
});
console.log("=====>Array filtrado");
$.each(arrayLimpio, function(){
	console.log(this);
});

lo ejecutamos y magia

image

por supuesto que el criterio puede contemplar un objeto complejo, por ejemplo filtrar las personas mayores de 18 años

Ejemplo con objetos complejos

var arraySucio = [{nombre:'juan', edad:20},{nombre:'pablo', edad:12},{nombre:'mateo', edad:24},{nombre:'pedro', edad:6}];

var arrayLimpio = $.grep(arraySucio, function(element){
	return element.edad>18;
});

console.log("=====>Array original");
$.each(arraySucio, function(){
	console.log(this.nombre + " " + this.edad);
});
console.log("=====>Array filtrado");
$.each(arrayLimpio, function(){
	console.log(this.nombre + " " + this.edad);
});

image

Hasta la próxima, Leonardo.