Este artículo proviene de un motor de traducción automática.

Filtros de MVC

Agregue fácilmente contadores de rendimiento a su aplicación de MVC

Ben Grover

Trabajando en la empresa aplicaciones Web normalmente implica una gran cantidad de código adicional para ayudar con la supervisión y el funcionamiento de las aplicaciones. En este artículo explicaré cómo estoy utilizando filtros modelo vista controlador (MVC) para limpiar y reemplazar el código repetido, confuso que se extendió a lo largo de numerosos métodos en una aplicación.

Los gerentes de operaciones suelen fijarse hasta Microsoft Operations Manager (MOM) para supervisar la salud de un sitio Web (o servicio) y utilizar contadores de rendimiento para activar alarmas basadas en valores de umbral. Estas alarmas a garantizar que experiencias degradados en el sitio Web se encuentran rápidamente.

Código de problema

Estoy trabajando en un proyecto relacionado (utilizando ASP.NET Framework MVC) donde son los requisitos agregar contadores de rendimiento para las páginas Web y servicios Web para el equipo de operaciones de ayuda. El equipo de operaciones requiere contadores para cada página: solicitud de latencia, Total de solicitudes por segundo y la proporción de fracaso.

Problemas siempre parecen surgir en la aplicación de tales requisitos. Me puse a buscar en la implementación actual de estos contadores de desarrolladores más diligentes que añadió como parte de los hitos de codificación anteriores. Me ha decepcionado. Estoy seguro de que todos has estado allí, mira el código y mortifican. ¿Lo que veo? Repite código rociado a través de cadamétodo, con pocos cambios a un nombre de variable aquí y allá. Yo no estaba feliz con la aplicación actual.

Puede ver el código que me cringe en figura 1.

Figura 1 código ese hecho Me Cringe

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();
}

Mirando este código, sabía que quería quitar de cada uno de los métodos de acción del proyecto. Este tipo de patrón hace extremadamente difícil ver dónde está el verdadero código de todo el código superfluo para supervisar el rendimiento. Estaba buscando una manera inteligente de refactorizar este código para que no camada cada uno de los métodos de acción. Introducir filtros MVC.

Filtros de MVC

Filtros MVC son atributos personalizados que pones en acción métodos (o controladores) para agregar funcionalidad común. MVC filtros le permiten añadir comportamientos anteriores y posterior. La lista de filtros incorporados de MVC se puede encontrar aquí: bit.ly/jSaD5N. Lo había utilizado algunos de los filtros incorporados, como OutputCache, pero yo sabía que MVC filtros tenían mucho poder oculto que yo nunca había aprovechado (para obtener más información acerca del atributo de filtro, consulte bit.ly/kMPBYB).

Así, empecé a pensar a mí mismo: ¿y si pude encapsular toda la lógica de estos contadores de rendimiento en un atributo de filtro MVC? Nació una idea! Yo pude cumplir los requisitos de cada contador de rendimiento mencionadas anteriormente con las siguientes acciones:

  1. Total de solicitudes por segundo
    1. Aplicar IActionFilter, que tiene dos métodos: OnActionExecuting y OnActionExecuted
    2. Incrementa el contador en el OnActionExecuting
  2. Solicitud de latencia
    1. Aplicar IResultFilter, que tiene dos métodos: OnResultExecuting y OnResultExecuted
    2. Inicie el temporizador en el OnActionExecuting y grabar la latencia durante OnResultExecuted
  3. Proporción de fallo
    1. Aplicar IExceptionFilter, que tiene el método OnException

El proceso se ilustra en la figura 2.

MVC Filter-Processing Pipeline

Figura 2 canalización de proceso de filtro de MVC

Analizaré el uso de cada filtro como se muestra en figura 2.

IActionFilter OnActionExecuting (línea 2a) se ejecuta antes de ejecuta el método de acción. OnActionExecuted (línea 2b) se ejecuta después de ejecuta el método de acción, pero antes de ejecuta el resultado.

IResultFilter OnResultExecuting (línea 4a) se ejecuta antes de que el resultado de la acción se ejecuta (por ejemplo, para representar una vista). OnResultExecuted (línea 4b) se ejecuta después de que el resultado de la acción se ejecuta.

IExceptionFilter OnException (no se muestra en la figura 2 para mayor claridad) ejecuta siempre una excepción se inicia (y no controlada).

IAuthorizationFilter OnAuthorization (no incluido en figura 2, ni se usa en este artículo) se llama cuando se requiere autorización.

Administrar los contadores

Sin embargo, si he utilizado este atributo de filtro de contador de rendimiento, tendría un problema: ¿cómo podría obtener el contador de rendimiento (para cada acción) en cada uno de estos filtros en tiempo de ejecución? No quería tener una clase de atributo de filtro separado para cada acción. En ese caso, tendría que incluir el nombre de contador de rendimiento en el atributo. Podría causar una explosión en el número de nombres de clase para implementar la solución. Refleja sobre una tecnología que utiliza cuando primero había trabajado con Microsoft.NET Framework: reflexión (juego de palabras destinado!). Reflexión es muy aprovechada por MVC framework también. Puede aprender más sobre la reflexión aquí: bit.ly/iPHdHz.

Mi idea era crear dos clases:

  1. WebCounterAttribute
    1. Implementa el MVC filtrar interfaces (IExceptionFilter, IActionFilter y IResultFilter)
    2. Contadores de incremento almacenados en WebCounterManager
  2. WebCounterManager
    1. Implementa el código de reflexión para la carga de la WebCounterAttributes de cada acción de MVC
    2. Almacena un mapa para facilitar la búsqueda de los objetos de contador de rendimiento
    3. Proporciona métodos para incrementar los contadores almacenados en ese mapa

Implementar el diseño

Tener esas clases, pude decorar WebCounterAttribute en cada uno de los métodos de acción en la que quería contadores de rendimiento de aplicación, como se muestra aquí:

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

Éste es un método de acción muestra:

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

A continuación, pude leer en estos atributos durante el método Application_Start mediante la reflexión y crear un contador para cada una de estas acciones, como se muestra en figura 3. (Tenga en cuenta que los contadores están registrados en el sistema en un programa de instalación, pero se crean instancias de los contadores en el código).

Figura 3 reflejando sobre ensamblados

/// <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 la línea importante donde se rellena el mapa:

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

Se crea una asignación entre la instancia concreta de un WebCounterAttribute en una acción, incluido el tipo de contador y asigna a la instancia creada de PerformanceCounter.

A continuación, podría escribir el código que me permite utilizar esta asignación para buscar la instancia PerformanceCounter (e incrementarlo) para una determinada instancia de la WebCounterAttribute (véase 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();
  }
}

Entonces pude grabo los datos del contador de rendimiento cuando se ejecutan estos filtros. Por ejemplo, en figura 5, verá una aplicación de grabación de latencia de rendimiento.

Figura 5 la WebCounterAttribute invocando la WebCounterManager RecordLatency

/// <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;
}

Observará en esta convocatoria que me estoy poniendo el WebCounterManager de estado de la aplicación. Para que esto funcione, debe agregar código a la global.asax.cs:

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

Conclusión, filtros MVC ofrecen una solución elegante a patrones de código muy repetidos. Ayudaremos a Refactorizar código común que hará que el código más limpio y más fácil de mantener. Obviamente, tiene un equilibrio entre la elegancia y facilidad de implementación. En mi caso, tuve que agregar contadores de rendimiento a cerca de 50 páginas Web. Los ahorros en términos de claridad y legibilidad del código fue definitivamente vale la pena el esfuerzo adicional.

Filtros MVC son un buen método para agregar comportamientos sin ser molestas, por lo que si usted está tratando con contadores de rendimiento, registro o auditoría, encontrará posibilidades ilimitadas para aplicación limpia de la lógica necesaria.

Ben Grover es un programador de Microsoft en Redmond, Washington, donde ha trabajado en varios equipos, de intercambio a Lync para Windows.

Gracias a los siguiente experto técnico para revisar este artículo: eilon Lipton