Вы находитесь на странице: 1из 18

Filtros asncronos en MVC 6

martes, 16 de diciembre de 2014


1
Como ya hemos visto por aqu en alguna ocasin, hace tiempo que MVC soportacontroladores
asncronos, permitiendo la implementacin de acciones muy eficientes desde el punto de vista de la
utilizacin de los recursos del servidor.
Sin embargo, en cuanto pretendamos llevar a los action filters la misma filosofa nos encontrbamos
con que la infraestructura no estaba preparada, es decir, no tenamos una forma clara de introducir
llamadas asncronas en el cuerpo de los filtros. Como consecuencia, todas las tareas que se realizaban
en su interior eran puramente sncronas y dejaban bloqueados los hilos destinados a procesar
peticiones mientras se completaban las operaciones, lo cual es especialmente un despilfarro cuando se
trata de tareas de entrada/salida.
Afortunadamente esto parece que va a terminar con MVC 6, que soportar ya esta solicitada
caracterstica. Pero ojo, que MVC 6 est an en desarrollo, por lo que todo lo que cuento a
continuacin puede cambiar.
Pero empecemos desde el principio, viendo qu ha cambiado por abajo para que sea posible crear
filtros con cdigo asncrono en su interior. Quizs sea una lectura un poco densa, pero creo que es
interesante para comprender cmo funcionan las cosas por dentro.
Abriendo el cap
Hasta MVC 5, era posible crear filtros personalizados tomando como base varias
clases abstractas e interfaces, ofreciendo cada una de ellas un conjunto de mtodos a implementar o
sobrescribir para introducir nuestro cdigo personalizado.
Por ejemplo, la implementacin de la clase abstracta ActionFilterAttribute, una base usada con mucha
frecuencia para crear filtros personalizados, era de la siguiente forma:
?
1
public abstract class ActionFilterAttribute :
2
FilterAttribute, IActionFilter, IResultFilter
3
{
4
public virtual void OnActionExecuting(ActionExecutingContext filterContext) { }
5
public virtual void OnActionExecuted(ActionExecutedContext filterContext) { }
6
public virtual void OnResultExecuting(ResultExecutingContext filterContext) { }
7
public virtual void OnResultExecuted(ResultExecutedContext filterContext) { }
8
}
Como se puede observar, no hay rastro de las marcas que indican la presencia de asincrona, como el
uso de las clases Task o Task<T>, o de las palabras clave async y await. Los
mtodos OnXXX() presentes en la signatura eran invocados de forma sncrona antes y despus de la
ejecucin de las acciones y del resultado (objetos ActionResult) retornado por stas. Dado que estos
mtodos son virtuales, la creacin de filtros personalizados consista normalmente en heredar
de ActionFilterAttribute y sobreescribir uno o varios de ellos.
Pues fijaos ahora en la implementacin de esta misma clase en MVC 6:
?
1
public abstract class ActionFilterAttribute: Attribute, IOrderedFilter,
2
IActionFilter, IAsyncActionFilter,
3
IResultFilter, IAsyncResultFilter
4
{
5
public int Order { get; set; }
6
7
public virtual void OnActionExecuting(ActionExecutingContext context) { }
8
public virtual void OnActionExecuted(ActionExecutedContext context) { }
9
10
public virtual async Task OnActionExecutionAsync(
11
ActionExecutingContext context, ActionExecutionDelegate next)
12
{
13
OnActionExecuting(context);
14
if (context.Result == null)
15
{
16
OnActionExecuted(await next());

17
}
18
}
19
20
public virtual void OnResultExecuting(ResultExecutingContext context) { }
21
public virtual void OnResultExecuted(ResultExecutedContext context) { }
22
23
public virtual async Task OnResultExecutionAsync(
24
ResultExecutingContext context, ResultExecutionDelegate next)
25
{
26
OnResultExecuting(context);
27
if (!context.Cancel)
28
{
29
OnResultExecuted(await next());
30
}
31
}
32
}
Esto ya es otra cosa! Siguen existiendo los mismos mtodos sncronos OnXXX() que tenamos en las
versiones anteriores, pero ahora se han implementado un par de mtodos
nuevos, OnActionExecutionAsync() yOnResultExecutionAsync(), ambos definidos en sus respectivos
interfaces IAsyncActionFilter yIAsyncResultFilter, que brindan la posibilidad de introducir cdigo
asncrono de forma sencilla.
Acerqumonos ahora a la implementacin de estos mtodos. Son muy similares, por lo que nos
centraremos en un principio en OnActionExecutionAsync():
?
1
public virtual async Task OnActionExecutionAsync(
2
ActionExecutingContext context, ActionExecutionDelegate next)
3
{
4
OnActionExecuting(context);
5
if (context.Result == null)
6
{
7
OnActionExecuted(await next());
8
}
9
}
Como podemos observar, en su interior se encuentra en primer lugar una llamada sncrona
aOnActionExecuting(). Esto lo pone sencillo para los escenarios en los que no sea necesario asincrona,
manteniendo retrocompatibilidad con filtros escritos para versiones anteriores del framework.
Slo si no se ha cortocircuitado la ejecucin de la accin estableciendo un resultado precocinado
desde el propio filtro, es cuando se ejecuta la accin realmente mediante la invocacin al
delegado next(), y su retorno ser enviado como parmetro al mtodo sncrono OnActionExecuted().
Exactamente lo mismo ocurre con OnResultExecutionAsync(); se encargar de invocar a los
tradicionales mtodos sncronos OnResultExecuting() y OnResultExecuted() antes y despus de la
ejecucin del resultado, proporcionando una interfaz sncrona para aquellos escenarios donde no sea
necesario la asincrona.
Pero en cualquier caso, lo interesante de esto es que tenemos un punto asncrono de entrada a
nuestros filtros que, como ya os habris dado cuenta, son mtodos virtuales, es decir, fcilmente
reemplazables por cdigo propio en nuestros filtros personalizados.
Creando filtros asncronos personalizados
Si habis llegado hasta este punto, probablemente la creacin de un filtro asncrono os resultar
totalmente trivial. Basta con heredar de ActionFilterAttribute y
sobrescribir OnActionExecutionAsync() uOnResultExecutionAsync() dependiendo de si queremos tomar
el control durante la ejecucin de la accin o del resultado, respectivamente.
El siguiente ejemplo muestra un sencillo profiler implementado en forma de filtro que enva de forma
asncrona los tiempos de ejecucin de la accin y del resultado a un logger que podra estar
almacenando esta informacin en disco o en una base de datos:
?
1
public class ProfilerAttribute : ActionFilterAttribute
2
{
3
private ILogger _logger = new Logger();

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

public override async Task OnActionExecutionAsync(


ActionExecutingContext context, ActionExecutionDelegate next)
{
var stopWatch = Stopwatch.StartNew();
await base.OnActionExecutionAsync(context, next);
stopWatch.Stop();
var message = FormatProfilerInfo("Action", context, stopWatch);
await _logger.Log(message);
}
public override async Task OnResultExecutionAsync(
ResultExecutingContext context, ResultExecutionDelegate next)
{
var stopWatch = Stopwatch.StartNew();
await base.OnResultExecutionAsync(context, next);
stopWatch.Stop();

var message = FormatProfilerInfo("Result", context, stopWatch);


await _logger.Log(message);

private string FormatProfilerInfo(


string what, FilterContext context, Stopwatch stopWatch)
{
return string.Format("{0}.{1} >> {2} execution: {3}ms",
context.RouteData.Values["Controller"],
context.RouteData.Values["Action"],
what,
stopWatch.ElapsedMilliseconds);
}
}

Por cierto, seguro que ms de uno ha pensado que el filtro anterior usa una forma algo chunga para
obtener la referencia a la clase Logger. Pues s, pero est hecho aposta ;) La forma en que se consigue
lainyeccin de dependencias en filtros de MVC 6 es muy interesante, pero para no desviarnos
mucho lo dejar para un post futuro :)
ASP.NET MVC6 (vNext)ViewComponents
En ASP.NET vNext se unifican MVC y WebApi en una nueva API llamada MVC6. Aunque MVC6 se
parece a MVC5 no es compatible con ella, del mismo modo que WebApi se parece a MVC pero por
debajo son muy distintas.
Ya hemos viso algunas de las novedades o cambios que trae MVC6 (temas de model
binding, controladores POCO, ) y en este post vamos a explorar uno ms: los ViewComponents.
Resumiendo: los ViewComponents sustituyen a las vistas parciales. Ya no existe este concepto en
MVC6. De hecho, tampoco nos engaemos, desde razor la diferencia entre vistas parciales y vistas
normales (a nivel del archivo .cshtml) es muy pequea: se puede usar una vista parcial como vista
normal tan solo cambiando el return PartialView() por un return View() (o viceversa). En el motor de
vistas de ASPX eso no era as, ya que las vistas eran archivos .aspx y las vistas parciales eran
archivos .ascx.
En Razor la nica diferencia actual entre una vista parcial y una normal es que en la segunda se
procesa el archivo de Layout (usualmente _Layout.cshtml) y en la primera no. Pero no es el archivo
.cshtml quien determina si es vista normal o parcial. Es el ActionResult devuelto. Si
devuelves un ViewResult el archivo .cshtml se procesar como vista normal. Si devuelves un
PartialViewResult el archivo .cshtml se procesar como vista parcial.
El cdigo clsico en MVC5 para tener una vista parcial era algo como:
1.

public ActionResult Child()

2.

3.
4.

return PartialView();
}

El problema con este enfoque es que esta accin es enrutable, por lo que cualquiera puede ir a la URL
que enrute esa accin (p. ej. Home/Child si suponemos HomeController) y recibir el contenido HTML
de la vista parcial. Eso, generalmente, no se desea (para algo la vista es parcial).
Para solventar esto, en MVC4 se aadi el atributo [ChildActionOnly] que evitaba que una accin se
enrutase. As si decoramos la accin con dicho atributo cuando el usuario navega a la URL
que debera enrutar dicha accin recibir un error:

La accin se puede invocar a travs del helper Html.RenderAction:


1.

@{ Html.RenderAction("Child", "Home"); }

Nota: Se puede usar Html.Partial o Html.RenderPartial para renderizar una vista parcial directamente
(sin pasar por un controlador). Eso es til en el caso de que no haya lgica asociada a dicha vista
parcial (si la hay, lo suyo es colocarla en la accin y usar Html.RenderAction).
Bueno as tenemos las cosas hoy en da: bsicamente colocamos las acciones hijas en un
controlador (porque esdonde podemos colocar lgica) pero luego las quitamos del sistema de
enrutamiento (con [ChildActionOnly]) y las llamamos indicando directamente que accin y que
controlador es.
Realmente tiene sentido que las acciones hijas estn en un controlador? No. Porque la
responsabilidad del controlador es, bsicamente, responder a peticiones del navegador y eso no es
una peticin del navegador.
As en MVC6 se elimina el concepto de vista parcial y el PartialViewResult, y se sustituye por el
concepto de ViewComponent. Ahora lo que antes eran acciones hijas son clases propias que
derivan de ViewComponent:
1.

[ViewComponent(Name = "Child")]

2.

public class ChildComponent : ViewComponent

3.

4.

public async Task<IViewComponentResult> InvokeAsync()

5.

6.

return View();

7.
8.

}
}

El atributo [ViewComponent] nos permite especificar el nombre que damos al componente. El siguiente
paso es definir el mtodo InvokeAsync que devuelve una Task<IViewComponentResult> con el
resultado. La clase ViewComponent nos define el mtodo View() que devuelve la vista asociada a dicho
componente (de forma anloga al mtodo View() de un controlador).
La ubicacin por defecto de la vista asociada a un componente es /Views/Shared/Components/
[NombreComponente]/Default.cshtml. Es decir en mi caso tengo el fichero Default.cshtml en
/Views/Shared/Components/Child:

Por supuesto ahora tengo un sitio donde colocar la lgica (si la hubiera) de dicho componente: la propia
clase ChildComponent.
Finalmente nos queda ver como renderizamos el componente. Ya no tenemos Html.RenderAction, si no
que en su lugar usamos la propiedad Component que tienen las vistas de MVC6:
1.

@await Component.InvokeAsync("Child")

Simplemente le pasamos el nombre del componente (el mismo definido en el atributo


[ViewComponent].
Y listos :)
ASP.NET vNextModel Binding
Bien, en el post anterior comentamos cuatro cosillas sobre el model binding en ASP.NET MVC y WebApi,
sus semejanzas y sus diferencias. En ASP.NET vNext ambos frameworks se unifican as que es de
esperar que el model binding tambin lo haga Veamos como funciona el model binding de vNext.
Nota: Este post est realizado con la versin de ASP.NET vNext que viene con el VS14 CTP2. La mejor
manera de probar dicha CTP es usando una VM en Azure creada a partir de una plantilla que ya la
contiene instalada. Por supuesto todo lo dicho aqu puede contener cambios en la versin final :)
Pruebas de caja negra
Antes que nada he intentado hacer unas pruebas de caja negra para ver si el comportamiento era
ms parecido al de WebApi o al de MVC. He empezado con un proyecto web vNext vaco, y en el
project.json he agregado la referencia a Microsoft.AspNet.Mvc. Luego me he creado un controlador
como el siguiente:
1.

public class HomeController : Controller

2.

3.

public IActionResult Index(Product product, Customer customer)

4.

5.
6.

return View();
}

7.
8.

public bool Post(Product product, Customer customer)

9.

10.
11.

return true;
}

12. }
Finalmente en el Startup.cs he configurado una tabla de rutas que combine MVC y WebApi:

1.

public class Startup

2.

3.

public void Configure(IBuilder app)

4.

5.

app.UseServices(s => s.AddMvc());

6.
7.

app.UseMvc(r =>

8.

9.

r.MapRoute(

10.

name: "default",

11.

template: "{controller}/{action}/{id?}",

12.

defaults: new { controller = "Home", action = "Index" });

13.

r.MapRoute(

14.

name: "second",

15.

template: "api/{Controller}/{id?}"

16.

);

17.

});

18.

19. }
Con esa tabla de rutas un POST a /Home/Index debe enrutarme por la primera accin del controlador
(al igual que un GET). Mientras que un POST a /api/Home debe enrutarme por la segunda accin del
controlador (mientras que un GET a /api/Home debe devolverme un 404). Para ms informacin echa
un vistazo a mi post sobre el routing en vNext.
Las clases Customer y Product contienen simplemente propiedades:
1.

public class Customer

2.

3.

public int Id { get; set; }

4.

public string Name { get; set; }

5.

public string Gender { get; set; }

6.

1.

public class Product

2.

3.

public int Id { get; set; }

4.

public string Name { get; set; }

5.

Luego he usado cURL para realizar unos posts y ver que es lo que tena:
curl --data "Id=1&Name=eiximenis&Gender=Male" http://localhost:49228/ --header "Contenttype:application/x-www-form-urlencoded"
Con esto simulo un post a que contenga los datos Id, Name y Gender y eso es lo que recibo en el
controlador (en el mtodo Index):

Este comportamiento es el mismo que en ASP.NET MVC. Ahora cambio la peticin de cURL para enviar
la misma peticin pero a /api/Home para que se me enrute al mtodo Post (estilo WebApi). Mi idea era
ver si para enrutamiento tipo MVC se usaba un binding parecido a MVC y para enrutamiento tipo
WebApi (sin accin y basado en verbo HTTP) se usaba un binding parecido al de WebApi:
curl data "Id=1&Name=eiximenis&Gender=Male" http://localhost:49228/api/Home --header "Contenttype:application/x-www-form-urlencoded"
El resultado es que se me llama al mtodo Post del controlador pero recibo exactamente los
mismos valores que antes. Recordad que en WebApi eso NO era as. As a simple vista parece que se
ha elegido el modelo de model binding de ASP.NET MVC antes que el de web api.
Otra prueba ha sido realizar un POST contra /api/Home/10 (el parmetro 10 se corresponde al route
value id) y dado que estamos pasando el id por URL quitarlo del cuerpo de la peticin:
curl --data "Name=eiximenis&Gender=Male" http://localhost:49228/api/Home/10 --header "Contenttype:application/x-www-form-urlencoded"
El resultado es el mismo que en el caso anterior (y coincide con ASP.NET MVC donde el model binder ni
se preocupa de donde vienen los datos).
Por lo tanto estas pruebas parecen sugerir que en vNext el model binding que se sigue es el de
ASP.NET MVC.
Claro que cuando uno pruebas de caja negra debe tener presente el mximo nmero de opciones
Porque resulta que si hago algo parecido a:
curl data "{'Name':'eiximenis','Gender':'Male'}"http://localhost:49228/api/Home --header "Contenttype:application/json"
Entonces resulta que ambos parmetros son null. Parece ser que vNext no enlaza por defecto datos
en JSON, solo en www-form-urlencoded. Adems mandar datos en JSON hace que los parmetros
no se enlacen. Aunque mande datos a travs de la URL (p. ej. como route values) esos no se usan.
Por supuesto vNext soporta JSON, pero es que nos falta probar una cosilla
Atributo [FromBody]
De momento en vNext existe el atributo [FromBody] (pero no existe el [FromUri]). Ni corto ni perezoso
he aplicado el FromBody a uno de los parmetros del controlador:
1.

public bool Post(Product product, [FromBody] Customer customer)

2.

3.

return true;

4.

Y he repetido la ltima peticin (el POST a /api/Home/10). Y el resultado ha sido un error:


System.InvalidOperationException: 415: Unsupported content type
Microsoft.AspNet.Mvc.ModelBinding.ContentTypeHeaderValue
He modificado la peticin cURL para usar JSON en lugar de form-urlencoded:
curl --data "{'Name':'eiximenis','Gender':'Male'}" http://localhost:49228/api/Home/10 --header
"Content-type:application/json"
Y el resultado ha sido muy interesante:

El parmetro customer se ha enlazado a partir de los datos en JSON del cuerpo (el Id est a 0 porque es
un route value y no est en el cuerpo de la peticin) pero el parmetro product est a null. Por lo
tanto el uso de [FromBody] modifica el model binding a un modelo ms parecido al de
WebApi.
WebApi solo permite un solo parmetro enlazado desde el cuerpo de la peticin. Mi duda ahora era si
vNext tiene la misma restriccin. Mirando el cdigo fuente de la clase JsonInputFormatter intua que
s y efectivamente. Aunque a diferencia de WebApi no da error si no que tan solo enlaza el primer
parmetro. As si tengo el mtodo:
1.

public bool Post([FromBody] Product product, [FromBody] Customer customer)

Y repito la llamada cURL anterior, los datos recibidos son:

El parmetro product (el primero) se ha enlazado a partir del cuerpo de la peticin y el segundo
vale null.
Y como funciona todo (ms o menos)?
Recordad que ASP.NET vNext es open source y que nos podemos bajar libremente el cdigo de su
repositorio de GitHub. Con este vistazo al cdigo he visto algunas cosillas.
El mtodo interesante es el mtodo GetActionArguments de la clase ReflectedActionInvoker. Dicho
mtodo es el encargado de obtener los argumentos de la accin (por tanto de todo el proceso de model
binding). Dicho mtodo hace lo siguiente:

Obtiene el BindingContext. El BindingContext es un objeto que tiene varias propiedades, entre


ellas 3 que nos interesan:
1. El InputFormatterProvider a usar
2. El ModelBinder a usar
3. Los Value providers a usar

Obtiene los parmetros de la accin. Cada parametro viene representado por un objeto
ParameterDescriptor. Si el controlador acepta dos parmtetros (customer y product) existen dos
objetos ParameterDescriptor, uno representando a cada parmetro de la accin. Dicha clase

tiene una propiedad llamada BodyParameterInfo. Si el valor de dicha propiedad es null se


usa un binding ms tipo MVC (basado en value providers y model binders). Si el valor
no es null se usa un binding ms tipo WebApi (basado en InputFormatters).
Por defecto vNext viene con los siguientes Value Providers:
1. Uno para query string (se crea siempre)
2. Uno para form data (se crea solo si el content type es application/x-www-form-urlencoded
3. Otro para route values (se crea siempre)
La clave est en el uso del atributo [FromBody] cuando tenemos un parmetro enlazado mediante este
atributo entonces no se usan los value providers si no los InputFormatters. Pueden haber dado de alta
varios InputFormatters pero solo se aplicar uno (basado en el content-type). Por defecto vNext incluye
un solo InputFormatter para application/json.
Ahora bien qu pasa si tengo un controlador como el siguiente:
1.

public IActionResult Index([FromBody] Customer customer, Product product)

2.

3.
4.

return View();
}

Y hago la siguiente peticin?


C:\Users\etomas\Desktop\curl>curl data
"{'Name':'eiximenis','Gender':'Male'}"http://localhost:38820/Home/Index/100?Name=pepe --header
"Content-type:application/json"
Pues el valor de los parmetros ser como sigue:

Se puede ver como el parmetro enlazado con el [FromBody] se enlaza con los parmetros del cuerpo
(en JSON) mientras que el parmetro enlazado sin [FromBody] se enlaza con el resto de parmetros (de
la URL, routevalues y querystring). En vNext el [FromUri] no es necesario: si hay un [FromBody] el resto
de elementos deben ser enlazados desde la URL. Si no hay [FromBody] los elementos sern enlazados
desde cualquier parte de la request.
Bueno en este post hemos visto un poco el funcionamiento de ASP.NET vNext en cuanto a model
binding. El resumen es que estamos ante un modelo mixto del de ASP.NET MVC y WebApi.
En futuros posts veremos como podemos aadir InputFormatters y ValueProviders para configurar el
sistema de model binding de vNext.
Saludos!
ASP.NET MVC vNext Controladores POCO
Una de las novedades que presenta ASP.NET MVC6 (integrada dentro de vNext) es la posibilidad de que
los controladores ya no deban heredar de ninguna clase base.
De hecho la clase Controller en MVC clsico (MVC5 y anteriores) proporcionaba bsicamente dos cosas:
1. Un conjunto de mtodos de para devolver action results (p. ej. el mtodo View() para devolver
un ViewResult o el mtodo Json para devolver un JsonResult).

2. Acceso a algunas propiedades para contexto (ControllerContext, ModelState y ViewBag


bsicamente).
Los mtdos para devolver action results no son estrctamente necesarios (aunque ayudan) pero pueden
encapsularse en alguna clase aparte y los objetos de contexto pueden aadirse por inyeccin de
dependencias (ASP.NET vNext est montando desde la base usando inyeccin de dependencias).
As en MVC6 podemos crear un controlador como el siguiente:
1.

public class HomeController

2.

3.

// GET: /<controller>/

4.

public IActionResult Index()

5.

6.

return null;

7.
8.

}
}

Si (asumiendo la tabla de rutas tradicional) navegamos hacia /Home/Index veremos como se nos invoca
dicho mtodo. Por supuesto ahora hemos de ver como crear el action result necesario. P. ej.
supongamos que queremos devolver la vista (lo que sera un return View() en un controlador
tradicional). Vemos que el constructor del ViewResult nos pide dos parmetros:

Como he dicho antes ASP.NET vNext est montado basado en inyeccin de dependencias as que deja
que el propio framework te inyecte estos parmetros:
1.

public class HomeController

2.

3.

private readonly IServiceProvider _serviceProvider;

4.

private readonly IViewEngine _viewEngine;

5.

public HomeController(IServiceProvider serviceProvider, IViewEngine viewEngine)

6.

7.

_serviceProvider = serviceProvider;

8.

_viewEngine = viewEngine;

9.

10.

public IActionResult Index()

11.

12.

return new ViewResult(_serviceProvider, _viewEngine);

13.

14. }
Si ahora ejecutas y colocas un breakpoint en el constructor vers que ambos parmetros han sido
inicializados por el framework de ASP.NET vNext:

Vers como efectivamente esto devuelve la vista Index.cshtml localizada en Views/Home (exactamente
lo mismo que hace return View()).
Pasar un modelo a la vista tampoco es excesivamente complicado:
1.

public class HomeController

2.

3.

private readonly IServiceProvider _serviceProvider;

4.

private readonly IViewEngine _viewEngine;

5.

private readonly IModelMetadataProvider _modelMetadataProvider;

6.

7.

public HomeController(IServiceProvider serviceProvider, IViewEngine viewEngine, IModelMeta


dataProvidermodelMetadataProvider)
{

8.

_serviceProvider = serviceProvider;

9.

_viewEngine = viewEngine;

10.

_modelMetadataProvider = modelMetadataProvider;

11.

12.

public IActionResult Index()

13.

14.

var viewdata = new ViewDataDictionary<FooModel>(_modelMetadataProvider);

15.

viewdata.Model = new FooModel();

16.

return new ViewResult(_serviceProvider, _viewEngine) { ViewData = viewdata };

17.

Necesitamos un IModelMetadataProvider (que recibimos tambin por inyeccin de dependencias) ya


que lo necesitamos para la construccin del ViewDataDictionary que pasamos a la vista.
Para evitar que nuestro controlador POCO deba tomar demasiadas depenencias en el constructor
(dependencias que son requeridas bsicamente para construir los action results), el equipo de ASP.NET
ha creado la interfaz IActionResultHelper. Dicha interfaz contiene mtodos para ayudarnos a crear ms
fcilmente los action results. Por supuesto, en el controlador recibimos un IActionResultHelper por
inyeccin de dependencias. As podemos modificar nuestro controlador para que quede de la siguiente
forma:
1.

public class HomeController

2.

3.

private readonly IActionResultHelper _actionHelper;

4.

private readonly IModelMetadataProvider _modelMetadataProvider;

5.

public HomeController(IActionResultHelper actionHelper, IModelMetadataProvider modelMetad


ataProvider)

6.

7.

_actionHelper = actionHelper;

8.

_modelMetadataProvider = modelMetadataProvider;

9.

10.

public IActionResult Index()

11.

12.

var viewdata = new ViewDataDictionary<FooModel>(_modelMetadataProvider);

13.

viewdata.Model = new FooModel();

14.

return _actionHelper.View("Index", viewdata);

15.

16. }
Ahora el controlador solo toma una dependencia contra el IActionResultHelper y el
IModelMetadataProvider. Las dependencias contra el IServiceProvider y el IViewEngine (que eran solo
para crear el ViewResult) son gestionadas ahora por el IActionResultHelper.
Y listos! Hemos visto como podemos crear controladores POCO (que no hereden de Controller) y como
a travs de la inyeccin de dependencias recibimos las dependencias necesarias de forma automtica!
Saludos!

Sin duda este va a ser el ao del despegue de AngularJS y hay que ponerse las pilas.
Por ello, siguiendo los pasos de ste tutorial de Scotch.io, Este tutorial nos
servir para empezar con algo sencillo pero que toca bastantes aspectos de Angular
en la parte Frontend de una Single Page Application, aadiendo un API con Node y
Mongo para la parte Backend y completando as el Stack tecnolgico de moda, MEAN.
Vamos a ello!
La estructura de archivos va a ser muy sencilla, no vamos a modularizar ni
aadirtareas con Grunt para enfocarnos en los conceptos de Angular. Estos son los
ficheros que tendremos
- public
----- index.html
----- main.js
server.js
package.json

main.js contend toda la lgica del frontend, es donde tendremos los


controladores de Angular JS y llamaremos via AJAX al API para pedir
contenido, borrarlo, etc..

index.html ser nuestro nico fichero html y por tanto nuestra nica
pgina, toda la funcionalidad ser en ella.
server.js es nuestro fichero Node donde estar la configuracin del
servidor y las rutas a nuestro API.
package.json es el fichero donde estn los datos de la aplicacin y las
dependencias utlizadas, como toda aplicacin Node.
Empezaremos por package.json para indicar que dependencias vamos a necesitar,
que simplemente sern Express y Mongoose:
{

"name": "angular-todo",
"version": "0.0.1",
"description": "Simple Angular TODO app based in MEAN stack",
"main": "server.js",
"dependencies": {
"express": "~3.x",
"mongoose": "latest"
}

Despus de esto, en una terminal ejecutamos npm install y se nos instalarn las
dependencias para poder empezar a utilizarlas.
Ahora pasaremos al archivo server.js que ser el fichero donde est la configuracin
del servidor, as como la conexin a la base de datos y las rutas de nuestro API.
En los comentarios del cdigo he explicado a grandes rasgos que hace cada lnea.
Como todo fichero de servidor de Node, primero aadimos las libreras que
necesitamos (express y mongoose).
//server.js
var express
= require('express');
var app
= express();
var mongoose
= require('mongoose');
// Conexin con la base de datos
mongoose.connect('mongodb://localhost:27017/angular-todo');
// Configuracin
app.configure(function() {
// Localizacin de los ficheros estticos
app.use(express.static(__dirname + '/public'));
// Muestra un log de todos los request en la consola
app.use(express.logger('dev'));
// Permite cambiar el HTML con el mtodo POST
app.use(express.bodyParser());
// Simula DELETE y PUT
app.use(express.methodOverride());
});
// Escucha en el puerto 8080 y corre el server
app.listen(8080, function() {
console.log('App listening on port 8080');
});

El siguiente paso es construir el modelo de la base de datos que modele las tareas o
ToDos. Esto lo hacemos con Mongoose y nuestro modelo ser muy sencillo ya que

solo cuenta con un atributo Text que define la tarea. Este cdigo lo insertamos
en server.js antes de la lnea donde se inicia el servidor con app.listen
// Definicin de modelos
var Todo = mongoose.model('Todo', {
text: String
});

Tras esto nos queda construir las rutas que llamarn a nuestro API y que utilizaremos
desde el frontend. En esta tabla se muestran las 3 llamadas que vamos a
implementar y que definirn nuestra API:
HTTP

URL

Descripcin

GET

/api/todos

Devuelve todas las tareas de la BD

POST

/api/todos

Crea una tarea

DELETE

/api/todos/:todo

Borra una tarea

Y antes de seguir con el cdigo, veamos un diagrama visual del flujo que va a seguir
aplicacin con las tecnologas que empleamos en cada parte:

Desde el frontend, con Angular hacemos llamadas AJAX a nuestra API en el servidor
Node. Este consulta a la base de datos (Mongo) dependiendo de la llamada realizada.
La BD devuelve el objeto como respuesta a Node y este lo sirve como JSON a Angular
que lo muestra en el frontend sin necesidad de recargar la pgina, creando as
una Single Page Application.
Veamos las rutas. Estas irn tambin en el archivo server.js, justo antes de cuando se
inicia el servidor y escucha en el puerto con app.listen

// Rutas de nuestro API


// GET de todos los TODOs
app.get('/api/todos', function(req, res) {
Todo.find(function(err, todos) {
if(err) {
res.send(err);
}
res.json(todos);
});
});
// POST que crea un TODO y devuelve todos tras la creacin
app.post('/api/todos', function(req, res) {
Todo.create({
text: req.body.text,
done: false
}, function(err, todo){
if(err) {
res.send(err);
}
Todo.find(function(err, todos) {
if(err){
res.send(err);
}
res.json(todos);
});
});
});
// DELETE un TODO especfico y devuelve todos tras borrarlo.
app.delete('/api/todos/:todo', function(req, res) {
Todo.remove({
_id: req.params.todo
}, function(err, todo) {
if(err){
res.send(err);
}
Todo.find(function(err, todos) {
if(err){
res.send(err);
}
res.json(todos);
});
})
});
// Carga una vista HTML simple donde ir nuestra Single App Page
// Angular Manejar el Frontend
app.get('*', function(req, res) {
res.sendfile('./public/index.html');
});

Gracias a Mongoose podemos buscar(find), borrar (remove) y crear(create) de una


manera muy sencilla. La ltima ruta no corresponde al API, si no que ser la
encargada de mostrar el html donde ejecutaremos toda la lgica del Frontend.
Todo esto que hemos hecho corresponde al Backend de la aplicacin. Ahora
empezaremos con lo que de verdad importa, el desarrollo frontend con Angular.
Tendremos toda la lgica en el fichero main.js, primero crearemos un modulo que
ser el que defina toda nuestra aplicacin
angular.module('angularTodo', []);

y seguidamente la funcin mainController que ser el controlador de la aplicacin


function mainController($scope, $http) {
$scope.formData = {};
// Cuando se cargue la pgina, pide del API todos los TODOs
$http.get('/api/todos')
.success(function(data) {
$scope.todos = data;
console.log(data)
})
.error(function(data) {
console.log('Error: ' + data);
});
// Cuando se aade un nuevo TODO, manda el texto a la API
$scope.createTodo = function(){
$http.post('/api/todos', $scope.formData)
.success(function(data) {
$scope.formData = {};
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error:' + data);
});
};
// Borra un TODO despues de checkearlo como acabado
$scope.deleteTodo = function(id) {
$http.delete('/api/todos/' + id)
.success(function(data) {
$scope.todos = data;
console.log(data);
})
.error(function(data) {
console.log('Error:' + data);
});
};
}

Pasemos a explicar algunos conceptos de esta parte.


En el objeto $scope se almacenan todas las variables dentro del mbito del
controlador. En el HTML, todo lo que se encuentre dentro de la directiva ngcontroller=mainController es controlable desde el objeto $scope.
Y el objeto $http es el que hace toda la magia, ya que nos permite hacer llamadas
AJAX a nuestro API con pocas lneas de cdigo.
Con estos dos objetos creamos las 3 funciones que hacen las 3 peticiones que acepta
nuestra API, el GET de todas las tareas almacenadas, el POST de creacin de una
nueva tarea y el DELETE de una tarea.
Y por ltimo nos queda el HTML en el que maquetaremos los resultados que nos trae
el API.
Necesitamos indicar que parte de la pgina corresponde a la aplicacin Angular, eso
lo hacemos con la directiva ng-app. En nuestro caso lo hemos puesto en el
tag<html> ya que todo el HTML, es la aplicacin.
<html lang="en" ng-app="angularTodo">...

Una aplicacin Angular puede tener varios controladores, en este ejemplo solo
tenemos uno, el mainController, y debemos decir en el HTML que parte es la
corresponde a esta funcin, eso lo hacemos con la directiva ng-controller. En nuestro
caso la hemos puesto en el body porque no hay ms. Si tuviesemos ms
controladores, se pueden poner en otros section, article o div y tener varios en la
pgina.
<body ng-controller="mainController">...

Para mostrar la lista de tareas que devuelve el GET, utilizaremos la directiva ngrepeat que nos permite crear una iteracin al estilo de un for
<div class="checkbox" ng-repeat="todo in todos">
<label>
<input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
</label>
</div>

Con esto creamos un input de tipo checkbox por cada objeto que nos devuelve la
llamada al API. Y con la directiva ng-click creamos un evento que escucha cuando
marquemos el checkbox para llamar a la funcin deleteTodo() a la cual se le pasa
como parmetro el id de la tarea para que llame al DELETE del API.
Por ltimo, tenemos un formulario con un input de tipo texto donde escribimos tareas
nuevas y las mandamos por POST al API. Aqu usamos una nueva directiva,ng-model,
que es la que controla el Modelo en este caso la tarea y su texto, y de nuevo ngclick en el botn de submit para llamar a la funcin createTodo() del controlador que
hace el POST al API y a la Base de datos.
<form>
<div class="form-group">
<input type="text" class="form-control input-lg text-center" placeholder="Inserta una tarea nueva" ngmodel="formData.text">
</div>
<button class="btn btn-primary btn-lg" ng-click="createTodo()">
Aadir
</button>
</form>

Con esto estara todo. Solo nos queda incluir las libreras de jQuery y Angular como
scripts al final de la pgina y y tambin una hoja de estilos para que no sea tan fea la
aplicacin. Hemos usado un CDN para ello y as no tenemos que preocuparnos en
bajarnos la libreria y adirla al proyecto, para centrarnos en entender los conceptos
El cdigo HTML completo sera as:
<!doctype html>
<html lang="en" ng-app="angularTodo">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Angular TODO app</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
</head>
<body ng-controller="mainController">
<div class="container">
<!--Cabecera-->
<div class="jumbotron text-center">
<h1>Angular TODO List <span class="label label-info">{{ todos.length }}</span></h1>
</div>

<!--Lista de Todos-->
<div id="todo-list" class="row">
<div class="col-sm-4 col-sm-offset-4">
<div class="checkbox" ng-repeat="todo in todos">
<label>
<input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}
</label>
</div>
</div>
</div>
<!--Formulario para insertar nuevos Todo-->
<div id="todo-form" class="row">
<div class="col-sm-8 col-sm-offset-2 text-center">
<form>
<div class="form-group">
<input type="text" class="form-control input-lg text-center" placeholder="Inserta una tarea
nueva" ng-model="formData.text">
</div>
<button class="btn btn-primary btn-lg" ng-click="createTodo()">Aadir</button>
</form>
</div>
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> <script
src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script> <script
src="main.js"></script>
</body>
</html>

Y ya tenemos nuestra aplicacin de ToDos. Solo tenemos que correr el servidor en un


terminal con node server.js e ir a un navegador a la URLhttp://localhost:8080 y
tendremos algo como esto

Вам также может понравиться