Custom Action Filters en ASP.NET MVC

Portada Custom Action Filters

Estaba siguiendo el tutorial de MVC Music Store V2.0 hecho por Jon Galloway (muy recomendable para meterse a ASP.NET MVC 3 con Razor) cuando noté una constante repetición de código necesario para un par de dropdowns en varias vistas del StoreManagerController:

ViewBag.Genres = storeDB.Genres.OrderBy(g => g.Name).ToList();
ViewBag.Artists = storeDB.Artists.OrderBy(a => a.Name).ToList();

 

Si bien son sólo dos lineas de código, me parece muy innecesaria la repetición de este en 4 ActionMethods. Así que se me ocurrió usar un Custom Action Filter para agregar dichas propiedades al ViewBag con un simple atributo, aquí la implementación:

using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;

namespace MvcMusicStore
{
    public class IncludeArtistsGenreAttribute : ActionFilterAttribute
    {
        public MusicStoreEntities db = new MusicStoreEntities();

        public override void OnActionExecuting(
            ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            filterContext.Controller.ViewBag.Genres = 
                db.Genres.OrderBy(g => g.Name).ToList();
            filterContext.Controller.ViewBag.Artists = 
                db.Artists.OrderBy(a => a.Name).ToList();
        }
    }
}

Lo primero que necesitamos es heredar de la clase ActionFilterAttribute para poder hacer override de uno de los siguientes métodos virtuales:

  • OnActionExecuting. El cuál se ejecuta antes del Action method correspondiente.
  • OnActionExecuted. Se ejecuta después de que el Action method termina.
  • OnResultExecuting. Se ejecuta justo antes de regresar el ActionResult del método.
  • OnResultExecuted. Se ejecuta una vez que el ActionResult ha sido regresado.

En este caso el único método que no nos sirve es el OnResultExecuted ya que es demasiado tarde para agregar propiedades al ViewBag. Una vez implementado nuestro atributo, simplemente decoramos los Action methods en donde necesitamos estas propiedades (los que tienen vistas con los dropdown de Géneros y Artistas) de esta manera:

//
// GET: /StoreManager/Create

[IncludeArtistsGenre]
public ActionResult Create()
{
    //ViewBag.Genres = db.Genres.OrderBy(g => g.Name).ToList();
    //ViewBag.Artists = db.Artists.OrderBy(a => a.Name).ToList();

    var album = new Album();

    return View(album);
} 

//
// POST: /StoreManager/Create

[HttpPost]
[IncludeArtistsGenre]
public ActionResult Create(Album album)
{
    if (ModelState.IsValid)
    {
        db.Albums.Add(album);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    //ViewBag.Genres = db.Genres.OrderBy(g => g.Name).ToList();
    //ViewBag.Artists = db.Artists.OrderBy(a => a.Name).ToList();

    return View(album);
}

//
// GET: /StoreManager/Edit/5
[IncludeArtistsGenre]
public ActionResult Edit(int id)
{
    //ViewBag.Genres = db.Genres.OrderBy(g => g.Name).ToList();
    //ViewBag.Artists = db.Artists.OrderBy(a => a.Name).ToList();

    var album = db.Albums.Single(a => a.AlbumId == id);
    return View(album);
}

//
// POST: /StoreManager/Edit/5

[HttpPost]
[IncludeArtistsGenre]
public ActionResult Edit(int id, FormCollection collection)
{
    var album = db.Albums.Find(id);
    if (TryUpdateModel(album))
    {
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        //ViewBag.Genres = db.Genres.OrderBy(g => g.Name).ToList();
        //ViewBag.Artists = db.Artists.OrderBy(a => a.Name).ToList();

        return View(album);
    }
}

 

Nótese que dejé las lineas repetitivas comentadas por motivos ilustrativos pero en realidad habría que borrarlas por completo Smile.

MVC nos incluye varios Action Filters que pueden ser utilizados para cosas como autenticación/autorización, manejo de errores, manejo de cache, etc. Para más información puedes checar el artículo correspondiente en MSDN.