Estou dando uma olhada no ASP.Net MVC Preview 4, e estou agora entrando em contato com uma feature que existe desde o Preview 2: os ActionFilters.

Com eles você pode colocar ações que acontecem antes das ações de um controlador, e antes da execução do ActionResult retornado pela ação. Você pode pegar erros, criar cache, e até cancelar uma ação (entre várias outras coisas).

Observação: Se você não sabe o que são ActionResults, por enquanto pense assim: são ações encapsuladas (seguem o Design Pattern Command – mais informações aqui e aqui). Quando alguém chama uma ação de um controlador, ele retorna um ActionResult para ser executado. O ActionResult mais comum é o ViewResult, que retorna uma ação de renderizar uma View, ou mais claramente, uma página. Podem existir outros tipos de ActionResult, para baixar arquivos, por exemplo.

Pois bem, criei um logger, bem simples, só para exibir o que está se passando, e criei uma View para exibí-lo. Vejam como foi.

Criei um projeto com Preview 4, e criei um ActionFilterAttribute (uma classe que herde de ActionFilterAttribute). Esse ActionFilterAttribute permite que você crie os tais dos filtros de ação. No meu caso o filtro chamou-se GiggioLogAttribute:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.ActionFilters
{
    public class GiggioLogAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
            filterContext.Log("OnActionExecuted");
        }
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            filterContext.Log("OnActionExecuting");
        }
        public override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            base.OnResultExecuted(filterContext);
            filterContext.Log("OnResultExecuted");
        }
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            base.OnResultExecuting(filterContext);
            filterContext.Log("OnResultExecuting");
        }
    }
    static class LoggerHelper
    {
        public static void Log(this ControllerContext filterContext, string where)
        {
            string routeDataValues = string.Empty;
            foreach (var item in filterContext.RouteData.Values)
            {
                routeDataValues += "\r\n" + item.Key + ": " + item.Value.ToString();
            }

            Models.ActionFilterEventLogger.GetLogs().Add(new Models.ActionFilterEventLog()
            {
                ControllerName = filterContext.Controller.ToString(),
                EventName = where,
                RouteDataValues = filterContext.RouteData.Values
            });
        }
    }
}

Notem como estou sobrescrevendo as 4 ações, de antes da ação do controlador (OnActionExecuting), depois (OnActionExecuted), e de antes da execução do resultado da View (OnResultExecuting) e depois (OnResultingExecuted). Pego o contexto de filtro e passo para um método de extensão chamado Log, onde criou um objeto ActionFilterEventLog e adiciono à uma coleção de logs que fica guardada em uma variável de aplicação escondida atrás da função ActionFilterEventLogger.GetLogs(). Com isso, tenho uma coleção em memória do que aconteceu. Para aplicar é fácil. Vejam como ficou com o atributo decorando a ação About do controller Home:

        [ActionFilters.GiggioLog()]
        public ActionResult About()
        {
            ViewData["Title"] = "About Page";

            return View();
        }

Ao chamar o about, nada muda, mas as ações do meu logger são executadas.

Já para exibir, criei uma view, e usei a mesma coleção como modelo. Vejam o controlador, chamado Logger.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
    public class LoggerController : Controller
    {
        public ActionResult Index()
        {
            return View(Models.ActionFilterEventLogger.GetLogs());
        }
    }
}

Super simples, certo? Simplesmente pega a coleção de logs e repassa à View. No caso, a View não foi nomeada, então segue o mesmo nome da ação: Index.

Vejam a View agora. Primeiro o code behind, onde seto o tipo de modelo:

public partial class Index : ViewPage <List<Models.ActionFilterEventLog>>
{
}

E abaixo a aspx para exibir os dados. Basicamente o que eu estou fazendo é montando uma tabelinha com os dados salvos pelo logger, iterando pela coleção.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Logger.Index" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Logs:</h2>
    <table>        
        <tr>            
            <td><strong>Event Name</strong></td>
            <td><strong>Controller Name</strong></td>
            <td><strong>Route Data</strong></td>
        </tr>
        <% foreach (ActionFilterEventLog log in this.ViewData.Model) %>
        <% { %>
        <tr>
            <td><% =log.EventName %></td>
            <td><% =log.ControllerName %></td>
            <td>
                <% foreach (var item in log.RouteDataValues) %>
                <%{ %>
                    <strong><% =item.Key %>:</strong>&nbsp;<% =item.Value.ToString() %>
                <%} %>
            </td>
        </tr>
        <% } %>
    </table>    
</asp:Content>

Ficou assim:

Logger

Eu gostei bastante da tecnologia. Fácil de usar, rapidíssimo para implementar. Entre a leitura e o projeto final não passei de 40 minutos. Excelente! Em que outras opções vocês acham que esse tipo de técnica ficaria legal?

Quem quiser baixar o código fonte, pegue aqui. Vai rodar em qualquer máquina com ASP.Net 3.5 instalado. As dlls do MVC já vem juntas, então você deve conseguir rodar no Visual Studio 2008 sem o Preview 4 instalado.

Giovanni Bassi

Arquiteto e desenvolvedor, agilista, escalador, provocador. É fundador e CSA da Lambda3. Programa porque gosta. Acredita que pessoas autogerenciadas funcionam melhor e por acreditar que heterarquia é mais eficiente que hierarquia. Foi reconhecido Microsoft MVP há mais de dez anos, dos mais de vinte que atua no mercado. Já palestrou sobre .NET, Rust, microsserviços, JavaScript, TypeScript, Ruby, Node.js, Frontend e Backend, Agile, etc, no Brasil, e no exterior. Liderou grupos de usuários em assuntos como arquitetura de software, Docker, e .NET.