Freigeben über


Dieser Artikel wurde maschinell übersetzt.

MVC-Filter

Bequemes Hinzufügen von Leistungsindikatoren zur MVC-Anwendung

Ben Grover

Arbeiten auf Enterprise-Webanwendungen in der Regel umfasst eine Vielzahl von zusätzlichen Code zu helfen, mit der Überwachung und Betrieb der apps. In diesem Artikel erkläre ich, wie ich bin benutze Model-View-Controller (MVC) Filter zu bereinigen, und ersetzen wiederholte, verwirrende Code, die über zahlreiche Methoden in einer Anwendung verteilt wurde.

Operative Managern festgelegt oft bis Microsoft Operations Manager (MOM) zur Überwachung des Status von einer Website (oder Dienst) und verwenden Leistungsindikatoren, basierend auf Schwellenwerten Alarme auslösen. Diese Alarme sicherstellen, dass degradierten Erfahrungen auf der Website schnell gefunden werden.

Problemcode

Ich arbeite an einem verwandten Projekt (mithilfe der ASP.NET MVC Framework) wo die Anforderungen sind Web-Seiten und Web-Services zur Unterstützung der Betriebsteam Leistungsindikatoren hinzu. Das Betriebsteam erfordert Indikatoren für jede Seite: anfordern Latenz, Anforderungen pro Sekunde und Fehler-Verhältnis insgesamt.

Probleme scheinen immer entstehen bei der Umsetzung solcher Anforderungen. Ich begann, Blick auf die aktuelle Implementierung dieser Marken von mehr fleißig Entwickler, die sie als Teil des vorherigen Codierung Meilensteine hinzugefügt hatte. Ich war enttäuscht. Ich bin sicher, Sie habe alle es — Sie Blick auf den Code und die Lunge. Was sehe ich? Wiederholt Code bestreut durch jedesMethode mit nur ein paar Änderungen an einen Variablennamen hier und da. Ich war nicht glücklich mit der aktuellen Implementierung.

Der Code, der mich erschaudern ließ findet sich in Abbildung 1.

Abbildung 1 Code, dass gemacht mich erschaudern

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

Blick auf diesen Code, wusste ich, dass ich es aus meinem Projekt Aktion Methoden entfernen wollte. Diese Art von Muster macht es extrem schwer zu sehen, wo der Code der wahre durch alle überflüssigen Code um die Leistungsüberwachung zu berücksichtigen ist. Ich war auf der Suche für ein cleverer Weg, um diesen Code umzugestalten, so dass es würde nicht jede Aktion-Methoden Wurf. Geben Sie ein MVC-Filter.

MVC-Filter

MVC-Filter sind benutzerdefinierte Attribute, die Sie auf Aktion Methoden (oder Controller), gemeinsame Funktionen hinzuzufügen. MVC-Filter können Sie vor- und Nachverarbeitung Verhalten hinzufügen. Die Liste der integrierten MVC-Filter finden Sie hier: bit.ly/jSaD5N. Ich hatte verwendet einige der integrierten Filter, wie z. B. OutputCache, aber ich wusste, dass MVC-Filter viel Kraft versteckt hatte, die ich nie in angezapft hatte (Lesen Sie mehr über die Filter-Attribut finden Sie unter bit.ly/kMPBYB).

So begann ich denken zu mir: Was passiert, wenn ich all die Logik für diese Leistungsindikatoren in einem MVC-Filter-Attribut Kapseln könnte? Eine Idee war geboren! Ich konnte die Anforderungen jeder Leistungsindikator zuvor aufgeführten mit die folgenden Aktionen:

  1. -Anforderungen pro Sekunde insgesamt
    1. Implementieren von IActionFilter, die über zwei Methoden verfügt: OnActionExecuting und OnActionExecuted
    2. Erhöht den Zähler in der OnActionExecuting
  2. Latenz anfordern
    1. Implementieren von IResultFilter, die über zwei Methoden verfügt: OnResultExecuting und OnResultExecuted
    2. Starten Sie des Timers in der OnActionExecuting und notieren Sie die Wartezeit während OnResultExecuted
  3. Fehler-Verhältnis
    1. Implementieren von IExceptionFilter, die hat die OnException-Methode

Der Prozess wird in veranschaulicht Abbildung 2.

MVC Filter-Processing Pipeline

Abbildung 2 MVC-Filter - Verarbeitungspipeline

Ich werde die Verwendung der einzelnen Filter zu erörtern, wie in dargestellt Abbildung 2.

IActionFilter OnActionExecuting (Linie 2a) ausgeführt wird, bevor die Aktion-Methode ausgeführt wird. OnActionExecuted (Linie 2 b) ausgeführt wird, nachdem die Aktion-Methode ausgeführt wird, jedoch bevor das Ergebnis ausgeführt wird.

IResultFilter OnResultExecuting (Linie 4a) ausgeführt wird, bevor das Ergebnis Aktion ausgeführt wird (z. B. Wiedergabe einer Ansicht). OnResultExecuted (Linie 4 b) führt, nachdem das Ergebnis Aktion ausgeführt wird.

IExceptionFilter OnException (nicht gezeigt, Abbildung 2 für Klarheit) ausgeführt wird, wenn eine Ausnahme ausgelöst (und nicht behandelte ist).

IAuthorizationFilter OnAuthorization (nicht enthalten Abbildung 2, noch in diesem Artikel verwendeten) wird aufgerufen, wenn die Autorisierung erforderlich ist.

Verwalten von Leistungsindikatoren

Jedoch, wenn ich diese Leistung Leistungsindikator-Filter-Attribut verwendet, würde ich ein Problem: wie würde man den Leistungsindikator (für jede Aktion) in jeden von diesen Filtern zur Laufzeit? Ich wollte eine separate Filter-Attributklasse für jede Aktion haben. In diesem Fall würde ich hartcodieren Leistungsindikatornamens im Attribut haben. Die Zahl der Klassennamen erforderlich, um die Lösung zu implementieren würde, die eine Explosion verursachen. Zurück auf einer Technologie dachte ich, dass ich verwendete, als ich zuerst mit dem Microsoft gearbeitet hatte.NET Framework: Reflexion (Wortspiel beabsichtigt!). Reflektion ist stark von dem MVC-Framework auch genutzt. Sie können erfahren Sie mehr über Reflektion hier: bit.ly/iPHdHz.

Meine Vorstellung war, zwei Klassen erstellen:

  1. WebCounterAttribute
    1. Implementiert die MVC-filter-Schnittstellen (IExceptionFilter, IActionFilter und IResultFilter)
    2. Inkrement-Leistungsindikatoren, die in WebCounterManager gespeichert
  2. WebCounterManager
    1. Implementiert den Reflexion-Code für das Laden der WebCounterAttributes aus jeder MVC-Aktion
    2. Speichert eine Karte zu erleichtern die Suche nach der Leistungsindikatorobjekte
    3. Stellt Methoden zum Inkrementieren der Zähler in dieser Karte gespeichert

Entwurf implementieren

Mit diesen Klassen, konnte ich WebCounterAttribute auf der Aktion-Methoden dekorieren auf dem ich Leistungsindikatoren umgesetzt werden, wollte, wie hier gezeigt:

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

Hier ist eine Aktion-Methode:

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

Dann könnte ich in diese Attribute während der Application_Start-Methode mithilfe von Reflektion lesen und einen Zähler für jede dieser Aktionen erstellen, wie in gezeigt Abbildung 3. (Beachten Sie, dass Leistungsindikatoren mit dem System in ein Setup-Programm registriert sind, aber der Zähler-Instanzen werden im Code erstellt).

Abbildung 3 reflektieren über Assemblys

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

Beachten Sie die wichtige Linie, wo die Karte gefüllt wird:

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

Es erstellt eine Zuordnung zwischen die bestimmte Instanz einer WebCounterAttribute auf eine Aktion, einschließlich den Indikatortyp und ordnet sie die erstellte PerformanceCounter-Instanz.

Ich könnte dann schreiben Sie den Code, der ermöglicht mir, verwenden diese Zuordnung zum Nachschlagen der PerformanceCounter-Instanz (und es erhöhen) für eine bestimmte Instanz von der WebCounterAttribute (siehe Abbildung 4).

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

Dann könnte ich die Leistungsindikatordaten erfassen, wann diese Filter ausgeführt werden. Z. B. in Abbildung 5, Sie sehen eine Implementierung der Aufnahme Leistung Latenz.

Abbildung 5 die WebCounterAttribute Aufrufen der 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;
}

Sie werden in dieser Aufforderung feststellen, dass ich die WebCounterManager vom Zustand Anwendung bekommen bin. Damit dies funktioniert müssen Sie Ihre global.asax.cs Code hinzu:

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

Nachbereitung, bieten MVC-Filter eine elegante Lösung für stark wiederholten Codemustern. Sie helfen Ihnen umgestalten gemeinsamen Code, die Ihren Code machen sauberer und einfacher zu verwalten. Offensichtlich hat ein Gleichgewicht zu finden zwischen Eleganz und einfache Implementierung. In meinem Fall hatte ich etwa 50 Webseiten-Leistungsindikatoren hinzu. Die Einsparungen an Lesbarkeit des Codes und Klarheit war definitiv die zusätzliche Mühe Wert.

MVC-Filter sind eine gute Möglichkeit, ohne aufdringlich, so ob Sie mit Leistungsindikatoren zu tun haben, anmelden oder Überwachung, grenzenlose Möglichkeiten für saubere Durchführung der notwendigen Logik Sie finden Verhalten hinzufügen.

Ben Grover ist ein Programmierer bei Microsoft in Redmond, Washington, wo er auf mehrere Teams von Exchange für Lync zu Windows arbeitete.

Dank der folgenden technischen Experten für die Überprüfung dieses Artikels: Eilon Lipton