本文章是由機器翻譯。

MVC 篩選器

輕鬆在 MVP 應用程式中新增效能計數器

本 · 格羅弗

工作在企業的 Web 應用程式通常涉及的額外的代碼來説明監控主機和操作的應用程式。在本文中,我將解釋我如何清理和替換重複、 混亂遍佈許多方法在應用程式中的代碼使用模型視圖控制器 (MVC) 的篩選器。

業務經理經常設置了 Microsoft 操作管理器 (MOM) 監視 Web 網站 (或服務) 的健康狀況和觸發警報閾值的值基於使用效能計數器。這些警報説明確保 Web 網站上的退化的經驗快速找到。

問題的代碼

我的工作相關的專案 (使用 ASP。NET MVC 框架) 要求將效能計數器添加到 Web 頁面和 Web 服務,以説明運營團隊在哪裡。運營團隊需要為每個頁的計數器: 請求延遲、 故障率每秒的請求總數。

問題似乎總是出現的這種要求的執行情況。我開始看更勤奮的開發人員已將其添加為以前的編碼里程碑的一部分從這些計數器的當前實現。我感到很失望。我敢肯定你都已經有 — — 你看代碼和丟得一乾二淨。我看到了什麼?重複代碼通過灑每個方法,在這裡和那裡的變數名的幾個更改。我並不滿意當前執行。

讓我感到害怕的代碼可以看到在圖 1

圖 1代碼,使我害怕

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

這段代碼來看,我知道我想要刪除的每個專案的操作方法。這種類型的模式,使極難看到真正的方法代碼,是因為考慮性能監測到的所有多餘的代碼。我在尋找聰明的方式,此代碼的重構,這樣它就不會亂拋垃圾每個操作方法。輸入 MVC 的篩選器。

MVC 篩選器

MVC 篩選器是你放到行動上的自訂屬性方法 (或控制器) 添加常用功能。MVC 篩選使您得以添加預處理和後處理的行為。可以在這裡找到的內置的 MVC 篩選器清單:bit.ly/jSaD5N。我用一些內置的篩選器,如 OutputCache,但我知道 MVC 篩選器有很多隱藏的力量,我就永遠不會拍成 (閱讀更多有關篩選器屬性,請參閱bit.ly/kMPBYB)。

於是,我開始對自己的思考: 如果我可以封裝的所有這些效能計數器在 MVC 篩選器屬性的邏輯嗎?一個想法誕生了 !我能滿足前面列出的以下操作的每個效能計數器:

  1. 每秒的請求總數
    1. 實施 IActionFilter,有兩個方法: OnActionExecuting 和 OnActionExecuted
    2. 增加 OnActionExecuting 中的計數器
  2. 請求延遲
    1. 實施 IResultFilter,有兩個方法: OnResultExecuting 和 OnResultExecuted
    2. 啟動計時器在 OnActionExecuting 和 OnResultExecuted 過程記錄延遲
  3. 故障率
    1. 實施 IExceptionFilter,有方法 OnException

過程所示圖 2

圖 2MVC 篩檢程式處理管道

我將討論每個篩選器的使用,如圖所示,在圖 2

IActionFilterOnActionExecuting (線 2a) 執行之前執行的操作方法。OnActionExecuted (行 2b) 執行操作方法執行後,但之前執行結果。

IResultFilterOnResultExecuting (線 4a) 執行之前操作結果執行 (例如,渲染視圖)。OnResultExecuted (線 4b) 執行之後執行操作的結果。

IExceptionFilter OnException (不顯示在 圖 2為清晰起見) 執行時異常是引發 (和未處理)。

IAuthorizationFilter OnAuthorization (不包含在 圖 2,也在這篇文章中使用) 授權時需要調用。

管理計數器

但是,如果我使用此效能計數器的篩選器屬性,我會有一個問題: 我怎會效能計數器 (每個操作) 到每個這些篩選器在運行時?我不想有一個單獨的篩選器屬性類的每個操作。在這種情況下,我將不得不進行硬編碼屬性中的效能計數器名稱。實現解決方案所需的類名稱的數量,這會爆炸。我回來在技術上反映我用我第一次工作與微軟。NET 框架: 反射 (雙關 !)。反射大量利用以及 MVC 框架。您可以瞭解更多有關反射在這裡:bit.ly/iPHdHz

我的想法是創建兩個類:

  1. WebCounterAttribute
    1. 實現 MVC 篩選介面 (IExceptionFilter、 IActionFilter 和 IResultFilter)
    2. 存儲在 WebCounterManager 中的增量計數器
  2. WebCounterManager
    1. 實現從每個 MVC 操作載入 WebCounterAttributes 反射代碼
    2. 存儲,以便查找的效能計數器物件的地圖
    3. 提供遞增存儲在該圖中的計數器的方法

實現設計

有這些類,我可以裝飾 WebCounterAttribute 對每個操作方法的我想要實施,效能計數器,如下所示:

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

下麵是示例操作方法:

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

然後我可以使用反射的 Application_Start 方法在這些屬性中讀取和創建的計數器的每項操作,如中所示圖 3。 (注意計數器系統中註冊的在安裝程式中,但在代碼中創建的計數器的實例)。

圖 3程式集的反思

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

注意,在填充地圖的重要線:

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

它創建的行動,包括計數器類型中,WebCounterAttribute 的特定實例之間的映射,並將其映射到創建效能計數器-實例。

我然後可以編寫代碼,使我看起來沒有任何實例 (和增加它),使用這種映射的特定實例的 WebCounterAttribute (請參見圖 4)。

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

然後我可以記錄效能計數器資料,這些篩選器在運行時。 例如,在圖 5,您看到的記錄性能延遲執行。

圖 5調用 WebCounterManager RecordLatency WebCounterAttribute

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

您會注意到在此調用中我從應用程式狀態變 WebCounterManager。 為了使這工作,您需要將代碼添加到您的 global.asax.cs:

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

總結,MVC 篩選器提供大量的重複代碼模式優雅的解決方案。他們會説明您將使代碼更易於維護和清潔的常見代碼重構。很明顯,平衡了兩者之間的優雅和易於實現。對我來說,我不得不向約 50 Web 頁添加效能計數器。節省的代碼可讀性和清晰度絕對是值得這些額外的工作。

MVC 篩選器是邏輯的一種不被侵擾性,所以是否您處理的效能計數器,日誌記錄或審核,您可以找到無限清潔執行必要的情況下添加行為的絕佳方式。

Ben Grover  是一個程式師在微軟在雷德蒙德,華盛頓,他曾在多個團隊,從交易所 Lync 到 Windows。

感謝至下列技術專家檢閱這份文件:Eilon Lipton