Este artigo foi traduzido por máquina.

Filtros MVC

Adicione contadores de desempenho com facilidade ao seu aplicativo MVC.

Ben Grover

Trabalhando em empresa de aplicativos da Web geralmente envolve uma série de código adicional para ajudar com monitoramento e operação dos aplicativos. Neste artigo, vou explicar como eu estou usando filtros Model-View-Controller (MVC) para limpar e substituir o código repetido e confuso que foi espalhado por vários métodos em um aplicativo.

Os gerentes de operações geralmente definido até o Microsoft Operations Manager (MOM) para monitorar a integridade de um Web site (ou serviço) e usar os contadores de desempenho para disparar alarmes com base em valores de limite. Estes alarmes ajudam a garantir que experiências degradadas no Web site são encontradas rapidamente.

Código do problema

Eu estou trabalhando em um projeto relacionado (usando o ASP.NET MVC Framework) onde estão os requisitos adicionar contadores de desempenho para páginas da Web e Web services para ajudar a equipe de operações. A equipe de operações requer contadores para cada página: Total de solicitações por segundo e a taxa de falha de solicitação latência.

Problemas sempre parecem surgir com a aplicação de tais requisitos. Comecei a olhar para a implementação atual desses contadores de desenvolvedores mais diligentes que tinham adicionado-los como parte de Marcos codificação anteriores. Fiquei decepcionado. Eu sou certo que você foi lá — você olha o código e cringe. O que eu vi? Repetido código polvilhado por meio de cadaMétodo, com apenas algumas alterações para um nome de variável aqui e ali. Eu não estava feliz com a implementação atual.

O código que me fez encolher pode ser visto em Figura 1.

Figura 1 código que fez eu tremo

public ActionResult AccountProfileInformation()
{
  try
  {
    totalRequestsAccountInfoCounter.Increment();
    // Start counter for latency
    long startTime = Stopwatch.GetTimestamp();
 
    // Perform some operations action here
    long stopTime = Stopwatch.GetTimestamp();
    latencyAccountInfoCounter.IncrementBy((stopTime - startTime) / 
      Stopwatch.Frequency);
    latencyAccountInfoBaseCounter.Increment();
  }
  catch (Exception e)
  {
    failureAccountInfoCounterCounter.Increment();
  }
  return View();
}

Olhando para este código, eu sabia que queria removê-lo da cada um dos métodos de ação do meu projeto. Este tipo de padrão torna extremamente difícil ver onde o código de verdade é por causa de todo o código supérfluo para contabilizar o monitoramento de desempenho. Eu estava procurando uma maneira inteligente de Refatorar esse código para que ele não iria ninhada cada um dos métodos de ação. Digite filtros MVC.

Filtros MVC

Filtros MVC são atributos Personalizars que você colocar em ação Métodos (ou controladores) para adicionar funcionalidade comum. MVC filtros permitem que você adicionar comportamentos de pré e pós-processamento. A lista de filtros MVC internas pode ser encontrada aqui: bit.ly/jSaD5N. Eu tinha usado alguns dos filtros incorporados, tais como OutputCache, mas eu sabia que MVC filtros tinham um monte de poder oculto que eu nunca tinha batido (para ler mais sobre o atributo de filtro, consulte bit.ly/kMPBYB).

Então, comecei a pensar para mim mesmo: o que acontece se eu poderia encapsular toda a lógica para esses contadores de desempenho em um atributo de filtro MVC? Uma idéia nasceu! Eu poderia atender aos requisitos de cada contador de desempenho listados anteriormente com as seguintes ações:

  1. Total de solicitações por segundo
    1. Implementar IActionFilter, que tem dois métodos: OnActionExecuting e OnActionExecuted
    2. Incrementar o contador no OnActionExecuting
  2. Solicitação de latência
    1. Implementar IResultFilter, que tem dois métodos: OnResultExecuting e OnResultExecuted
    2. Iniciar o temporizador na OnActionExecuting e gravar a latência durante OnResultExecuted
  3. Taxa de falha
    1. Implementar IExceptionFilter, que tem o OnException método.

O processo é ilustrado na Figura 2.

MVC Filter-Processing Pipeline

Figura 2 Pipeline de processamento do filtro MVC

Falarei sobre o uso de cada filtro conforme mostrado na Figura 2.

IActionFilter OnActionExecuting (linha 2) executa antes que o método de ação é executado. OnActionExecuted (linha 2b) é executado após o método de ação é executado, mas antes o resultado é executado.

IResultFilter OnResultExecuting (linha 4) executa antes o resultado de ação é executado (tais como um modo de renderização). OnResultExecuted (linha 4b) é executado após o resultado de ação é executado.

IExceptionFilter OnException (não mostrado na Figura 2 para clareza) é executado sempre que uma exceção é descartada (e sem tratamento).

IAuthorizationFilter OnAuthorization (não incluído no Figura 2, nem usados neste artigo) é chamado quando a autorização é necessária.

Gerenciando os contadores

No entanto, se eu usei esse atributo de filtro do contador de desempenho, eu teria um problema: como eu receberia o contador de desempenho (para cada ação) em cada um desses filtros em tempo de execução? Eu não queria ter uma classe de atributo de filtro separado para cada ação. Nesse caso, eu tenho que codificar o nome de contador de desempenho no atributo. Isso causaria uma explosão do número de nomes de classe necessários para implementar a solução. Refleti volta em uma tecnologia que usei quando eu tinha trabalhado pela primeira vez com a Microsoft.NET Framework: reflexão (trocadilhos!). Reflexão fortemente é aproveitado pela MVC framework também. Você pode aprender mais sobre reflexão aqui: bit.ly/iPHdHz.

Minha idéia foi criar duas classes:

  1. WebCounterAttribute
    1. Implementa o MVC filtrar interfaces (IExceptionFilter, IActionFilter e IResultFilter)
    2. Contadores de incremento armazenados em WebCounterManager
  2. WebCounterManager
    1. Implementa o código de reflexão para carregar o WebCounterAttributes de cada ação de MVC
    2. Armazena um mapa para facilitar a pesquisa de objetos de contador de desempenho
    3. Fornece métodos para incrementar os contadores armazenados nesse mapa

Implementar o Design

Tendo essas classes, eu podia decorar WebCounterAttribute em cada um dos métodos de ação em que eu queria que os contadores de desempenho para ser implementado, conforme mostrado aqui:

public sealed class WebCounterAttribute : FilterAttribute, IActionFilter, IExceptionFilter, IResultFilter
{
  /// Interface implementations not shown
}

Aqui é um método de ação de amostra:

[WebCounter("Contoso Site", "AccountProfileInformation")]
public ActionResult AccountProfileInformation()
{
  // Some model loading
  return View();
}

Então eu poderia ler esses atributos durante o método Application_Start usando reflexo e criar um contador para cada uma dessas ações, conforme mostrado na Figura 3. (Observe que contadores são registrados com o sistema em um programa de instalação, mas instâncias dos contadores são criadas no código).

Figura 3 refletindo sobre Assemblies

/// <summary>
/// This method reflects over the given assembly(ies) in a given path 
/// and creates the base operations required  perf counters
/// </summary>
/// <param name="assemblyPath"></param>
/// <param name="assemblyFilter"></param>
public void Create(string assemblyPath, string assemblyFilter)
{
  counterMap = new Dictionary<string, PerformanceCounter>();
            
  foreach (string assemblyName in Directory.EnumerateFileSystemEntries(
    assemblyPath, assemblyFilter)) 
  {
    Type[] allTypes = Assembly.LoadFrom(assemblyName).GetTypes();
 
    foreach (Type t in allTypes)
    {
      if (typeof(IController).IsAssignableFrom(t))
      {
        MemberInfo[] infos = Type.GetType(t.AssemblyQualifiedName).GetMembers();
 
        foreach (MemberInfo memberInfo in infos)
        {
          foreach (object info in memberInfo.GetCustomAttributes(
            typeof(WebCounterAttribute), true))
          {
            WebCounterAttribute webPerfCounter = info as WebCounterAttribute;
            string category = webPerfCounter.Category;
            string instance = webPerfCounter.Instance;
            // Create total rollup instances, if they don't exist
            foreach (string type in CounterTypeNames)
            {
              if (!counterMap.ContainsKey(KeyBuilder(Total, type)))
              {
                counterMap.Add(KeyBuilder(Total, type), 
                  CreateInstance(category, type, Total));
              }
            }
            // Create performance counters
            foreach (string type in CounterTypeNames)
            {
              counterMap.Add(KeyBuilder(instance, type), 
                CreateInstance(category, type, instance));
            }
          }
        }
      }
    }
  }
}

Observe a linha importante que o mapa é preenchido:

(counterMap.Add(KeyBuilder(instance, type), CreateInstance(category, type, instance));),

Ele cria um mapeamento entre a instância específica de um WebCounterAttribute em uma ação, incluindo o tipo de contador e mapeia para a instância criada PerformanceCounter.

Então eu poderia escrever o código que me permite usar esse mapeamento para procurar a instância PerformanceCounter (e incrementá-lo) para uma determinada instância da WebCounterAttribute (consulte Figura 4).

Figura 4 webcountermanager recordlatency

/// <summary>
/// Record the latency for a given instance name
/// </summary>
/// <param name="instance"></param>
/// <param name="latency"></param>
public void RecordLatency(string instance, long latency)
{
  if (counterMap.ContainsKey(KeyBuilder(instance,   
    CounterTypeNames[(int)CounterTypes.AverageLatency]))
    && counterMap.ContainsKey(KeyBuilder(instance,   
    CounterTypeNames[(int)CounterTypes.AverageLatencyBase])))
  {
    counterMap[KeyBuilder(instance, 
      CounterTypeNames[(int)CounterTypes.AverageLatency])].IncrementBy(latency);
    counterMap[KeyBuilder(Total, 
      CounterTypeNames[(int)CounterTypes.AverageLatency])].IncrementBy(latency);
    counterMap[KeyBuilder(instance, 
      CounterTypeNames[(int)CounterTypes.AverageLatencyBase])].Increment();
    counterMap[KeyBuilder(Total, 
      CounterTypeNames[(int)CounterTypes.AverageLatencyBase])].Increment();
  }
}

Então eu poderia gravar os dados do contador de desempenho quando esses filtros são executados. Por exemplo, em Figura 5, você vê uma implementação de latência de desempenho de gravação.

Figura 5 WebCounterAttribute chamando o RecordLatency de WebCounterManager

/// <summary>
/// This method occurs when the result has been executed (this is just 
/// before the response is returned).
/// This method records the latency from request begin to response return.
/// </summary>
/// <param name="filterContext"></param>
public void  OnResultExecuted(ResultExecutedContext filterContext)
{
  // Stop counter for latency
  long time = Stopwatch.GetTimestamp() - startTime;
  WebCounterManager countManager = GetWebCounterManager(filterContext.HttpContext);
  if (countManager != null)
  {
    countManager.RecordLatency(Instance, time);
    ...
}
}
private WebCounterManager GetWebCounterManager(HttpContextBase context)
{
  WebCounterManager manager =  
    context.Application[WebCounterManager.WebCounterManagerApplicationKey] 
    as WebCounterManager;
  return manager;
}

Você observará que esta chamada que estou recebendo o WebCounterManager do Estado do aplicativo. Para que isso funcione, você precisará adicionar código ao seu global.asax.cs:

WebCounterManager webCounterMgr = new WebCounterManager();
webCounterMgr.Create(Server.Map("~/bin"), "*.dll");
Application[WebCounterManager.WebCounterManagerApplicationKey] = webCounterMgr;

Resumindo, filtros de MVC oferecem uma solução elegante para padrões de código muito repetidas. Eles vão ajudar você refatorar código comum que vai tornar seu código mais limpo e mais fácil de manter. Obviamente, tem um equilíbrio entre a elegância e a facilidade de implementação. No meu caso, tive de adicionar contadores de desempenho para cerca de 50 páginas da Web. As poupanças em termos de legibilidade do código e clareza foi definitivamente vale a pena o esforço adicional.

Filtros MVC são uma ótima maneira de adicionar comportamentos sem ser indiscreto, portanto, se você está lidando com os contadores de desempenho, registro ou auditoria, você vai encontrar possibilidades ilimitadas para implementação limpa de lógica necessária.

Ben Grover é um programador na Microsoft em Redmond, Washington, onde ele trabalhou em várias equipes, de intercâmbio de Lync para Windows.

Graças aos seguinte perito técnico para revisão deste artigo: eilon Lipton