ASP.NET MVC3 – Filtros que no son atributos

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

En el post de ayer comentaba las novedades de ASP.NET MVC3 preview1 y una de las mejoras más interesantes es el soporte de inyección de dependencias para los filtros. Eso se consigue con el uso de una nueva interfaz IFilterProvider y esa interfaz nos trae de regalo, una posibilidad adicional: Tener filtros que no sean atributos.

Veamos un ejemplo y como podríamos usar este nueva capacidad en MVC3.

1. Que queremos?

Queremos poder usar el concepto de filtros de MVC, pero sin usar atributos. Es decir sin tener que decorar los controladores o sus acciones con atributos tipo [Authorization].

Vamos a proporcionar una mini api de configuración, para que pueda configurar los filtros, usando una api fluent:

FluentFilterProvider ffp = new FluentFilterProvider();
ffp.AddForController<HomeController, MyCustomFilter>().ForAction("Index");
ffp.AddForController<HomeController, MyOtherCustomFilter>();

Aquí configuro dos filtros para el controlador Home: Uno para la acción “Index” (MyCustomFilter) y otro para todas sus acciones (MyOtherCustomFilter).

2. Creación del esqueleto de lo que necesitamos

El primer paso es crearnos nuestro propio proveedor de filtros, es decir nuestra clase que implemente la interfaz IFilterProvider. Nada más sencillo que esto:

public class FluentFilterProvider : IFilterProvider
{
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return null;
}
}

3. Creación de la interfaz de configuración fluent

La idea es que nuestro proveedor de filtros tenga una colección de IFilterConfigItem y cada IFilterConfigItem tenga la información para saber cuando instanciar el filtro (es decir a que controlador y acción afecta y cual es la clase que implementa el filtro).

Como no sabemos si eso puede complicarse más, definimos una clase FluentFilterProviderConfig que mantenga esta colección:

class FluentFilterProviderConfig
{
private List<FilterConfigItem> _items;

public FluentFilterProviderConfig()
{
_items = new List<FilterConfigItem>();
}

internal ICollection<FilterConfigItem> Items
{
get { return _items; }
}
}

La colección es de objetos de la clase FilterConfigItem, esta clase es la que implementa IFilterConfigItem. Veamos primero como está definido la interfaz IFilterConfigItem:

public interface IFilterConfigItem
{
void ForAction(string actionName);
}

Un solitario método ForAction que nos permite indicar que dicho filtro se aplica a una acción en concreto.

Y ahora veamos la clase FilterConfigItem:

class FilterConfigItem : IFilterConfigItem
{
private Type _filterType;
private Type _controllerType;
private string _actionName;

internal FilterConfigItem(Type tf, Type controllerType)
{
_filterType = tf;
_controllerType = controllerType;
_actionName = null;
}

internal Type ControllerType { get { return _controllerType; } }
internal Type FilterType { get { return _filterType; } }
internal string ActionName { get { return _actionName; } }

public void ForAction(string actionName)
{
_actionName = actionName;
}
}

Dicha clase guarda toda la información requerida:

  • El tipo de la clase que implementa el filtro (_filterType)
  • El tipo del controlador al que se aplica el filtro (_controllerType)
  • El nombre de la acción a la que se aplica el filtro (_actionName). Este valor es opcional (si vale null el filtro se aplicará a todas las acciones).

Tenemos también propiedades para acceder a esta información, pero fijaos que son internal y no forman parte de la interfaz. Esto es porque esas propiedades sólo son para la implementación de la API, no para su uso público.

Finalmente en la clase FluentFilterProvider vamos a guardar un objeto con la configuración y vamos a proporcionar el punto de entrada a la API fluent: el método AddForController:

public class FluentFilterProvider : IFilterProvider
{
private FluentFilterProviderConfig _cfg;

public FluentFilterProvider()
{
_cfg = new FluentFilterProviderConfig();
}

public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
// Ya veremos que metemos aquí...
}

public IFilterConfigItem AddForController<TC, TF>()
where TC : IController
where TF : class
{
FilterConfigItem fci = new FilterConfigItem(typeof(TF), typeof(TC));
_cfg.Items.Add(fci);
return fci;
}
}

Fijaos que el método AddForController crea un objeto FilterConfigItem, lo añade a la colección y lo devuelve. Esta devolución es lo que permite encadenar las llamadas (característica básica de una interfaz fluent).

4. Creación del filter provider

Bien, sólo nos queda terminar de implementar la clase FluentFilterProvider con la implementación del método GetFilters. El código es muy sencillo:

public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var items = _cfg.Items.Where(x => x.ControllerType == controllerContext.Controller.GetType()
&& (x.ActionName == null || x.ActionName.Equals(actionDescriptor.ActionName)));
foreach (var item in items)
{
var sl = MvcServiceLocator.Current;
var filterInstance = sl.GetInstance(item.FilterType);
yield return new Filter(filterInstance, FilterScope.Action);
}
}

Nos recorremos la colección de FilterConfigItems y por cada uno miramos si aplica al controlador pedido y a la acción indicada (los parámetros controllerContext y actionDescriptor nos dan información sobre el controlador y la acción para la cual debemos encontrar los filtros).

Y por cada elemento FilterConfigItem encontrado:

  1. Creamos la instancia del objeto que implementa el filtro. Fijaos que lo hacemos usando MvcServiceLocator.Current: eso permite aplicar inyección de dependencias.
  2. Creamos un objeto Filter (el objeto que espera el framework), lo rellenamos con los datos y lo devolvemos.

Y listos! Nuestro filter provider está listo para usar… Sólo nos queda crearlo, configurarlo y registrarlo en MVC:

FluentFilterProvider ffp = new FluentFilterProvider();
ffp.AddForController<HomeController, MyCustomFilter>().ForAction("Index");
ffp.AddForController<HomeController, MyOtherCustomFilter>();
FilterProviders.Providers.Add(ffp);

Recordad que las clases MyCustomFilter y MyOtherCustomFilter son las clases que implementan los filtros (son las que implementan IActionFilter, IAuthorizationFilter, IResultFilter o IExceptionFilter).

No se que os parece a vosotros, pero a mi me parece una pasada!!! 🙂

Un saludo!!!!

2 comentarios sobre “ASP.NET MVC3 – Filtros que no son atributos”

  1. Hola, Eduard!

    Así a bote pronto, la solución que ha dado el equipo de MVC a este tema, y en general a todo el soporte para DI, tiene una pinta excelente.

    Tu post, como siempre, magnífico. Un buen ejemplo de lo que hasta ahora era imposible: crear interceptores de forma externa a la propia codificación de las acciones/controladores y, además, controlar su instanciación.

    Saludos!

  2. Muy buenas! 😀

    Para mi el soporte mejorado a DI es la novedad más importante de MVC3… Razor está bien, pero no deja de ser otro view engine como ya hay otros, no es una novedad «intrínseca» del framework.

    Y bueno, aunque el 3 de MVC3 seguramente le queda grande a esta release, los cambios que hay son interesantes y un pasito más a tener cada vez un framework más extensible, configurable y testeable!!

    Un saludo!!! 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *