ASP.NET MVC 3 – Preview 1

Publicado 28/7/2010 19:03 por Eduard Tomàs i Avellana

Ayer saltaba la noticia en el blog de scottgu: la preview 1 de ASP.NET MVC3 ya está disponible para descargar. En fin, podríamos discutir largo y tendido sobre la política de actualizaciones a lo bestia de las APIs que está realizando microsoft desde hace algún tiempo, pero como cada uno tendría su opinión, mejor vamos a ver las novedades que trae esa preview 1. Antes que nada, podéis instalarla sin miedo: se instala side by side con MVC2 y además los proyectos que ya teníais no se ven afectados.

Una vez instalado MVC3, aparecen tres nuevos tipos de proyectos en VS2010: ASP.NET MVC 3 Web Application (ASPX), ASP.NET MVC 3 Web Application (Razor) y ASP.NET MVC 3 Emtpy Web Application. No hay soporte para VS2008 puesto que MVC3 usa el .NET Framework 4. Vamos a ver las novedades que tiene esta preview1 de MVC3 :)

1. Razor View Engine

El nuevo Razor viene incluído en MVC3. Razor no es nada más que otro ViewEngine para MVC. Ya se ha hablado largo y tendido de Razor porque se incluye también en WebMatrix. Si bien en WebMatrix, Razor venía acompañado del concepto de ASP.NET WebPage, en ASP.NET MVC dicho concepto carece de sentido y Razor es simplemente otro ViewEngine más.

Razor ofrece una sintaxis alternativa a la sintaxis clásica de <% %> tan típica de ASP.NET MVC, pero no ofrece nada nuevo que no ofrezcan el ViewEngine de .aspx u otros como Nhaml o Spark.

Mi “decepción” ha sido que de hecho, tanto si usamos el ViewEngine de .aspx como si usamos Razor, las clases de las vistas derivan de System.Web.Mvc.ViewPage<> (yo tenía la esperanza de que Razor ofreciera un framework mucho más ligero, puesto que ViewPage deriva de System.Web.UI.Page que tiene muchas propiedades y métodos que tienen sentido en WebForms pero sólo añaden ruído en MVC).

P.ej. en Razor para mostrar una lista de elementos (FooItem) que tuviesen dos propiedades llamadas First y Second usaríamos:

@inherits System.Web.Mvc.WebViewPage<IEnumerable<MvcApplication1.Models.FooItem>>

@{
View.Title = "TableView";
LayoutPage = "~/Views/Shared/_Layout.cshtml";
}

<h2>TableView</h2>

<ul>
@foreach (var item in Model)
{
<li>@item.First - @item.Second</li>
}
</ul>

Este es el código entero de la vista: como veis es mucho más compacto que su equivalente en .aspx. ¿Cual preferís? Es una cuestión de gustos, pero según comenta Scott en su post, será posible probar templates de Razor de forma individual sin necesidad de tener un servidor web, lo que ayudará a la generación de pruebas… Por lo que parece esto no está previsto para el ViewEngine de .aspx.

Las vistas en Razor (usando c#) tienen la extensión .cshtml y de momento el soporte en vs2010 de Razor es muy limitado en esta preview1: Ni sintaxis coloreada, ni intellisense… :)

2. Mejor soporte para DI

MVC2 ya tenía un soporte más que decente para dependency injection, pero en MVC3 lo han mejorado: no sólo han simplificado la tarea de usar DI sinó que ahora también se soporta la inyección de dependencias en los filtros.

El soporte para contenedores IoC se basa en la interfaz IServiceLocator y como es uno de los puntos más interesantes a mi parecer, dejadme que me extienda un poco :)

Primero, antes que nada, en la preview1, la interfaz IServiceLocator está definida dentro del propio assembly de System.Web.Mvc.dll, en lugar de usar el del ensamblado Microsoft.Practices.ServiceLocation.dll. Esto genera algunos problemas y es algo que está previsto que cambie en futuras previews.

P.ej. si usamos Unity 2.0, para obtener el IServiceLocator nos basta con hacer:

IUnityContainer iuc = new UnityContainer();
Microsoft.Practices.ServiceLocation.IServiceLocator ul = new UnityServiceLocator(iuc);

La idea es el IServiceLocator que hemos obtenido (ul) lo podamos usar en MVC3, pero por ahora es imposible. La razón es la que comentaba: MVC3 define su propio IServiceLocator en lugar de utilizar el que viene en Microsoft.Practices.ServiceLocation.dll. Así pues, de momento, estamos obligados a implementarnos nuestro “propio” IServiceLocator:

public class MvcUnityServiceLocation : IServiceLocator
{
private readonly IUnityContainer _container;

public MvcUnityServiceLocation(IUnityContainer ctr)
{
_container = ctr;
}


public IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.ResolveAll(serviceType);
}

public IEnumerable<TService> GetAllInstances<TService>()
{
return _container.ResolveAll<TService>();
}

public object GetInstance(Type serviceType, string key)
{
return _container.Resolve(serviceType, key);
}

public object GetInstance(Type serviceType)
{
return _container.Resolve(serviceType);
}

public TService GetInstance<TService>(string key)
{
return _container.Resolve<TService>(key);
}

public TService GetInstance<TService>()
{
return _container.Resolve<TService>();
}

public object GetService(Type serviceType)
{
return _container.Resolve(serviceType);
}
}

La interfaz que estamos implementando NO ES Microsoft.Practices.IServiceLocator sino System.Web.Mvc.IServiceLocator. Como digo eso es algo que se supone cambiará en futuras previews.

Ahora ya podemos registrar este IServiceLocator, usando la clase MvcServiceLocator (esto se suele hacer en el Application_Start):

IUnityContainer iuc = new UnityContainer();
System.Web.Mvc.IServiceLocator ul = new MvcUnityServiceLocation(iuc);
MvcServiceLocator.SetCurrent(ul);

Bueno… Con eso establecemos cual va a ser el ServiceLocator que usará MVC para instanciar sus objetos. Ahora debemos configurarlo. Una cosa que me he encontrado es que debemos establecer la factoría de controladores explicitamente en el contenedor de IoC:

iuc.RegisterType<IControllerFactory, DefaultControllerFactory>
(new ContainerControlledLifetimeManager());

Aquí estoy diciendo a MVC que use DefaultControllerFactory como factoría de controladores, y la forma de hacerlo es simplemente establecer un mapping entre IControllerFactory y DefaultControllerFactory en mi contenedor IoC.

Y aquí viene la mejora: DefaultControllerFactory ya está preparada para usar el IServiceLocator, por lo que ya tenemos DI en los controladores. Sin hacer nada más (antes uno debía crearse su propia IControllerFactory). Podemos establecer un mapping cualquiera en nuestro contenedor:

iuc.RegisterType<IFoo, Foo>();

Y ya podemos usar IFoo sin ningún problema en nuestros controladores:

public class HomeController : Controller
{
public HomeController(IFoo foo)
{
// ...
}
}

Cada vez que se cree un HomeController se le inyectará el parámetro IFoo.

Antes he comentado que una de las novedades de MVC3 es el soporte de inyección de dependencias para los filtros… Los que conozcais un poco ASP.NET MVC seguramente os estareis preguntando cómo, dado que los filtros son clases que heredan de Attribute y por lo tanto es el propio runtime de .NET quien los crea, sin que el contenedor de IoC pueda intervenir…

Para ayudar a la inyección de dependencias en filtros, en MVC3 se han inventado el concepto del proveedor de filtros, representado por la interfaz IFilterProvider y que es el responsable de obtener instancias de todos los filtros que necesite el framework. Nosotros ahora podemos crear nuestra propia clase que implemente IFilterProvider y que aplique la inyección de dependencias. Veamos primero como está definido IFilterProvider:

public interface IFilterProvider
{
IEnumerable<Filter> GetFilters(ControllerContext controllerContext,
ActionDescriptor actionDescriptor);
}

Un solo método GetFilters que es el encargado de devolver el conjunto de filtros necesarios cada vez. La clase Filter no es el filtro en sí, sinó una clase de metadatos que contiene una referencia al objeto que implementa el filtro (en nuestro caso el atributo), además de información sobre el orden (que ya existía en MVC2) y el àmbito del filtro (p.ej. si es un filtro que se aplica a un controlador, a una acción,…). Los filtros se ejecutan unos antes de otros en función de su ámbito y de su orden.

Bien, si queremos implementar filtros usando atributos podemos hacerlo igual que en MVC2 derivando de FilterAttribute:

public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
}
}

Nada nuevo aquí, lo nuevo viene ahora: Podemos crear un IFilterProvider que nos inyecte las dependencias a los filtros. Este IFilterProvider, obviamente, no creará los filtros (puesto que son atributos y los crea el runtime de .NET). Entonces que hace? Pues tres cosas:

  1. Recoge los objetos que implementan los filtros (creados por el runtime)
  2. Por cada objeto le inyecta las dependencias. Para ello necesitamos que el contenedor de DI soporte inyectar dependencias a objetos ya existentes y usar inyección de propiedades.
  3. Finalmente crea el objeto Filter que contendrá el objeto con las dependencias creadas.

Un ejemplo, usando Unity:

public class UnityFilterAttributeFilterProvider : FilterAttributeFilterProvider
{
private IUnityContainer _container;

public UnityFilterAttributeFilterProvider(IUnityContainer container)
{
_container = container;
}
protected override IEnumerable<FilterAttribute> GetControllerAttributes(
ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var attributes = base.GetControllerAttributes(controllerContext, actionDescriptor);
foreach (var attribute in attributes)
{
_container.BuildUp(attribute.GetType(), attribute);
}
return attributes;
}

protected override IEnumerable<FilterAttribute> GetActionAttributes(
ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var attributes = base.GetActionAttributes(controllerContext, actionDescriptor);
foreach (var attribute in attributes)
{
_container.BuildUp(attribute.GetType(), attribute);
}
return attributes;
}
}

Derivamos de FilterAttributeFilterProvider (que implementa IFilterProvider) y usamos Unity para inyectar las dependencias (BuildUp) a los objetos que implementan los filtros.

Y ahora? Pues como siempre: registrar este FilterAttributeProvider en el framework a través del Applicaton_Start de global.asax:

var provider = new UnityFilterAttributeFilterProvider(container);  
FilterProviders.Providers.Add(provider);

Por defecto hay 3 IFilterProviders instalados en el framework (en la colección Providers):

  1.  
    1. Un filter provider que se encarga de devolver los controladores (sí, los controladores son filtros también (desde siempre)).
    2. Un filter provider que se encarga de devolver los filtros que son atributos
    3. Un filter provider que se encarga de devolver los filtros globales

Dado que nosotros estamos añadiendo otro filter provider que sustituye al que viene por defecto al que se encarga de devolver los filtros que son atributos, no estaría de más eliminar el que viene por defecto antes de registrar el nuestro (aunque funciona si no lo hacemos):

FilterProviders.Providers.Remove(FilterProviders.Providers.FirstOrDefault(x => x is FilterAttributeFilterProvider));

Y listos! Ahora ya tenemos inyección de dependencias en nuestros filtros! Recordad que debemos usar inyección de propiedades, dado que los filtros son creados por el runtime de .NET. Es decir algo como:

public class CustomActionFilterAttribute : ActionFilterAttribute
{
// Esta propiedad la inyecta Unity...
[Dependency]
public IFoo Foo { get; set; }
// ...
}

Que os parece? No es mucho trabajo para el beneficio que obtenemos!

Otra ventaja del uso de filter providers es poder tener filtros que no sean atributos, algo que no estaba permitido en MVC2: cualquier “cosa” que el filter provider devuelva será considerado un filtro para el framework.

3. Otras mejoras menores

Ahora listo brevemente un conjunto de “mejoras menores” que pese a no suponer un avance brutal sirven para simplificarnos el trabajo o mejorar el código:

  1. Soporte para objetos JSON en POST: MVC3 es capaz de tratar datos que están en POST en formato JSON y usarlos cuando hacemos binding del viewmodel. MVC2 no era capaz, aunque no era muy complejo añadir un custom value provider que diera esa capacidad. De hecho en MvcFutures ya estaba y es lógico que haya entrado en esta versión.
  2. Nueva propiedad ViewModel: ViewModel es una propiedad declarada como dynamic que permite usar la antigua ViewData de forma tipada. Es decir en lugar de hacer ViewData[“Foo”] = new Bar(); podemos hacer ViewModel.Foo = new Bar(); y el resultado es equivalente. Esto mejora la legibilidad y facilita refactorings.
  3. Soporte del interfaaz IValidatableObject: Esta interfaz (propia de .NET 4) tiene un sólo método (Validate) que determina si un objeto es “válido”. El model binder por defecto ahora usa ese método si el viewmodel implementa dicha interfaz.
  4. Nuevos ActionResults: Para devolver un 404 (HttpNotFoundResult), o un código específico de HTTP (HttpStatusCodeResult).
  5. Filtros globales: Filtros que se aplican a todos los controladores de nuestra aplicación de forma automática.

Bueno… como podeis ver MVC3 viene con un buen puñado de novedades, muchas de ellas pequeñitas pero sin duda alguna interesantes… larga, larga, larga vida a MVC!!! :)

Referencias

El post de ScottGu explicando MVC3

El post de Brad Wilson explicando la DI en filtros

Archivado en:
Comparte este post:

Comentarios

# ASP.NET MVC3 – Filtros que no son atributos

Thursday, July 29, 2010 12:03 PM by Burbujas en .NET

Antes que nada una nota: Este post está basado en la preview 1 de ASP.NET MVC3. Todo lo que comento puede

# ASP.NET MVC3 Beta: Mis impresiones

Thursday, October 7, 2010 2:20 PM by Burbujas en .NET

Buenooo… ayer fue un día movidito en Microsoft: anunciaron de golpe la beta 2 de WebMatrix, la beta de