Академический Документы
Профессиональный Документы
Культура Документы
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
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.
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:
@{ 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.
3.
4.
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")
2.
3.
4.
5.
6.
return View();
}
7.
8.
9.
10.
11.
return true;
}
12. }
Finalmente en el Startup.cs he configurado una tabla de rutas que combine MVC y WebApi:
1.
2.
3.
4.
5.
6.
7.
app.UseMvc(r =>
8.
9.
r.MapRoute(
10.
name: "default",
11.
template: "{controller}/{action}/{id?}",
12.
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.
2.
3.
4.
5.
6.
1.
2.
3.
4.
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.
2.
3.
return true;
4.
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.
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 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
2.
3.
4.
return View();
}
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.
3.
// GET: /<controller>/
4.
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.
2.
3.
4.
5.
6.
7.
_serviceProvider = serviceProvider;
8.
_viewEngine = viewEngine;
9.
10.
11.
12.
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.
2.
3.
4.
5.
6.
7.
8.
_serviceProvider = serviceProvider;
9.
_viewEngine = viewEngine;
10.
_modelMetadataProvider = modelMetadataProvider;
11.
12.
13.
14.
15.
16.
17.
2.
3.
4.
5.
6.
7.
_actionHelper = actionHelper;
8.
_modelMetadataProvider = modelMetadataProvider;
9.
10.
11.
12.
13.
14.
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
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
POST
/api/todos
DELETE
/api/todos/:todo
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
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>