https://d226lax1qjow5r.cloudfront.net/blog/blogposts/send-sms-notifications-on-application-performance-using-c/send-notifications_app-proformance_1200x600.png

SMS-Benachrichtigungen über die Leistung von Applications mit C# senden

Zuletzt aktualisiert am August 2, 2021

Lesedauer: 6 Minuten

Wenn Sie an Anwendungen arbeiten, möchten Sie oft aussagekräftige Daten protokollieren, z. B. wo Ihre Anwendung fehlgeschlagen ist, die CPU-Nutzung und die Speichernutzung. Auf diese Weise erhalten Sie verschiedene wichtige Einblicke, die Ihrem Unternehmen Auftrieb geben und helfen, Fehler schnell zu erkennen. Damit Sie schnell reagieren können, können Sie Benachrichtigungen verwenden, die eine Zusammenfassung der Leistungsstatistiken oder Details über einen Fehler in der Produktion enthalten.

In diesem Artikel erfahren Sie, wie Sie mit C# Leistungsstatistiken einer ASP.NET Core 5-Webanwendung abrufen und dann die Vorteile der Vonage SMS API nutzen können, um in vorkonfigurierten Zeitabständen Textbenachrichtigungen zu versenden.

Voraussetzungen

  • Visual Studio 2019.

  • Windows-Betriebssystem

Außerdem müssen Sie die folgenden NuGet-Pakete in Ihrem Projekt installieren:

  • NLog

  • Quarz

  • Vonage

Sie können sie über den NuGet Package Manager installieren, während Sie sich in Visual Studio befinden.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

Das Starter-Projekt

Ein Einstiegsprojekt finden Sie auf GitHub im Ordner starter. Das Starterprojekt enthält ein ASP.NET Core MVC 5-Projekt mit der Möglichkeit, Informationen zur CPU- und Speichernutzung zu lesen. In diesem Tutorial werden Sie die SMS-Benachrichtigungsfunktionalität hinzufügen.

Verwendung von Quartz.NET für Hintergrundaufträge

Quartz.NET ist eine kostenlose und quelloffene .NET-Version des beliebten Java Task Scheduling Frameworks Quartz. Es bietet hervorragende Unterstützung für den Umgang mit Cron-Ausdrücken und wird weithin für die Erstellung von Applications empfohlen, die Job Scheduling-Funktionen benötigen.

Die Kernkomponenten von Quartz.NET

Quartz.NET setzt sich aus drei grundlegenden Konzepten zusammen:

Ein Job - Dieser repräsentiert die Hintergrundaufgaben, die Sie ausführen möchten, während Sie andere Dinge tun. Eine Job-Klasse erweitert die IJob-Schnittstelle und implementiert ihre Mitglieder.

Ein Auslöser - Ein Auslöser bestimmt, wann eine Aufgabe ausgeführt wird, und er wird normalerweise so eingestellt, dass er nach einem vorher festgelegten Zeitplan ausgeführt wird. Mit anderen Worten: Sie können einen Auslöser verwenden, um den Zeitplan für einen Auftrag festzulegen.

Ein Scheduler - Dies ist eine Komponente, die für die Koordinierung der Aufträge und der Auslöser sowie für die Ausführung der Aufträge auf der Grundlage vordefinierter Zeitpläne zuständig ist.

Der Blog wird in Kürze mehr über diese Komponenten berichten.

Die Komponenten der Anwendung Benachrichtigung senden

In der Beispielanwendung werden Sie die Vonage SMS API verwenden, um Benachrichtigungen zu versenden, sobald die CPU- oder Speichernutzung einen vordefinierten Schwellenwert überschreitet. Daher würde unsere ASP.NET Core MVC 5-Anwendung aus den folgenden Komponenten bestehen:

  • Benutzerdefinierter Scheduler - Sie werden Quartz verwenden, um einen benutzerdefinierten Scheduler zu erstellen, der den Speicher- und CPU-Verbrauch im Computersystem im Auge behält. Er überprüft diese Werte in vordefinierten Zeitintervallen im Hintergrund, während Sie mit der Anwendung im Front-End arbeiten.

  • Benutzerdefinierter Logger - Sie werden auch NLog verwenden, um Daten über die Anwendungsleistung in dieser Anwendung zu protokollieren. Während die Benachrichtigungen nur gesendet werden, wenn der CPU- oder Speicherverbrauch den vordefinierten Schwellenwert überschreitet, würde das Protokoll CPU- und Speicherverbrauchsdaten enthalten, die alle 5 oder 10 Sekunden (basierend auf dem vordefinierten Intervall) gesammelt und in einer Textdatei gespeichert werden.

Für dieses Projekt werden Sie die folgenden Klassen in der Anwendung erstellen:

  • PerformanceHelper - Diese Klasse wird verwendet, um die CPU- und Speichernutzung abzurufen.

  • NotificationManager - Diese Klasse kapselt die Funktionalität zum Versenden von Benachrichtigungen.

  • NotificationJob - Dies ist die Auftragsklasse, die die Vorteile der NotificationManager Klasse nutzt, um Benachrichtigungen zu versenden, wie bereits erwähnt.

  • CustomJobFactory - Diese Klasse ist für die Schaffung von Arbeitsplätzen verantwortlich.

  • CustomJobScheduler - Diese Klasse ist für die Planung von Aufträgen zuständig.

  • QuartzExtensions - Diese Klasse enthält eine Erweiterungsmethode namens AddQuartz zur Vereinfachung und Organisation des Codes.

project architecture diagramproject architecture diagram

Verwenden von WMI zum Abrufen von CPU- und Speicherauslastung

Windows Management Instrumentation (WMI) ist ein objektorientiertes, erweiterbares und auf Standards basierendes Framework, das das Windows-Treibermodell erweitert und eine Betriebssystemschnittstelle bietet, über die instrumentierte Komponenten Informationen und Benachrichtigungen übermitteln. Das Hauptziel von WMI besteht darin, eine Reihe von umweltunabhängigen Spezifikationen zu schaffen, die den Austausch von Verwaltungsinformationen zwischen Anwendungen ermöglichen.

Um die CPU- und Speichernutzung in Prozent abzurufen, verwenden Sie die ManagementObjectSearcher Klasse aus dem Namensraum System Management. Diese Klasse kann alle Laufwerke, Netzwerkadapter, Prozesse und andere Verwaltungsobjekte auf einem System aufzählen und eine Sammlung von Verwaltungsobjekten auf der Grundlage der von Ihnen angegebenen Abfrage abrufen. Wenn Sie diese Klasse instanziieren, müssen Sie eine WMI-Abfrage übergeben, die in einer ObjectQuery dargestellt wird, und optional eine ManagementScope übergeben, die den WMI-Namensraum zur Ausführung der Abfrage darstellt.

Erstellen Sie eine Klasse mit dem Namen PerformanceHelper.cs in dem ASP.NET Core MVC 5-Projekt, das Sie zuvor geklont haben, und schreiben Sie den folgenden Code:

public static class PerformanceHelper
{
  public static double GetCPUUsageInPercentage()
  {
    ObjectQuery objQuery = new ObjectQuery("SELECT * FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name=\"_Total\"");
    ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher(objQuery);
    ManagementObjectCollection managementObjectCollection = managementObjectSearcher.Get();
    double cpu_usage = 0;

    if (managementObjectCollection.Count > 0)
    {
      foreach (ManagementObject managementObject in managementObjectCollection)
      {
        try
        {
          cpu_usage = 100 - Convert.ToUInt32(managementObject["PercentIdleTime"]);
          break;
        }
        catch (Exception ex)
        {
          break;
        }
      }
    }

    return cpu_usage;
  }

  public static Dictionary<string, double> GetMemoryUsageInfo()
  {
    Dictionary<string, double> keyValuePairs = new Dictionary<string, double>();

    ObjectQuery objQuery = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
    ManagementObjectSearcher searcher = new ManagementObjectSearcher(objQuery);
    ManagementObjectCollection results = searcher.Get();
    Dictionary<string, double> resourceConsumptionResults = new Dictionary<string, double>();

    foreach (ManagementObject result in results)
    {
      var totalVisibleMemory = Math.Round(double.Parse(result["TotalVisibleMemorySize"].ToString()), 2);
      var totalFreeMemory = Math.Round(double.Parse(result["FreePhysicalMemory"].ToString()), 2);

      resourceConsumptionResults.Add("Total_Visible_Memory", totalVisibleMemory);
      resourceConsumptionResults.Add("Free_Physical_Memory", totalFreeMemory);
    }

    double totalVisibleMemorySize = Math.Round(resourceConsumptionResults["Total_Visible_Memory"] / (1024 * 1024), 2);
    double freePhysicalMemory = Math.Round(resourceConsumptionResults["Free_Physical_Memory"] / (1024 * 1024), 2);
    double totalUsedMemory = totalVisibleMemorySize - freePhysicalMemory;
    double memory_usage = 0;

    try
    {
      memory_usage = ((totalVisibleMemorySize - freePhysicalMemory) / totalVisibleMemorySize) * 100;
      keyValuePairs.Add("Total_Visible_Memory", totalVisibleMemorySize);
      keyValuePairs.Add("Total_Free_Memory", freePhysicalMemory);
      keyValuePairs.Add("Total_Used_Memory", totalUsedMemory);
      keyValuePairs.Add("Total_Used_Memory_Percentage", memory_usage);
    }
    catch
    {
      throw;
    }

    return keyValuePairs;
  }
}

Erstellen einer Jobklasse

Als nächstes erstellen Sie die Jobklasse. Eine Jobklasse in Quartz erweitert die IJob-Schnittstelle und implementiert ihre Mitglieder. Erstellen Sie eine Klasse namens NotificationJob in einer Datei namens NotificationJob.cs mit dem folgenden Inhalt:

public class NotificationJob: IJob 
{
  private readonly NotificationManager _notificationManager;
  public NotificationJob(NotificationManager notificationManager)
  {
    _notificationManager = notificationManager;
  }
  public async Task Execute(IJobExecutionContext context)
  {
    await _notificationManager.SendNotification();
  }
}

Die Execute-Methode der Klasse NotificationJob Klasse ruft die SendNotification Methode der Klasse NotificationManager Klasse auf. Erstellen Sie eine neue Klasse namens NotificationManager in einer Datei namens NotificationManager.cs mit dem folgenden Code an:

public class NotificationManager
{
  private ILoggerFactory LoggerFactory { get; }
  private readonly ILogger Logger;
  public IConfiguration Configuration { get; set; }

  public NotificationManager(IConfiguration config, ILoggerFactory loggerFactory)
  {
    Configuration = config;
    LoggerFactory = loggerFactory;

    if (loggerFactory != null) {
      Logger = loggerFactory.CreateLogger("NotificationManager");
    }
  }
    
  public async Task SendNotification()
  {
    await SendResourceUsageInfo();
    await Task.CompletedTask;
  }

  private async Task SendResourceUsageInfo()
  {
    bool isMemoryUsageHigh = false;
    bool isCPUUsageHigh = false;

    var keyValuePairs = PerformanceHelper.GetMemoryUsageInfo();

    var memoryUsage = Math.Round(keyValuePairs["Total_Used_Memory_Percentage"], 2);
    var cpuUsage = Math.Round(PerformanceHelper.GetCPUUsageInPercentage(), 2);

    var memoryThreshold = Configuration["Memory_Threshold"];
    var cpuThreshold = Configuration["CPU_Threshold"];

    if (memoryUsage > double.Parse(memoryThreshold))
    {
      isMemoryUsageHigh = true;
    }
    if (cpuUsage > double.Parse(cpuThreshold))
    {
      isCPUUsageHigh = true;
    }

    if (isMemoryUsageHigh)
    {
      string memoryConsumptionAlertMessage = string.Format($"Alert!!! Memory Usage: " +
      $ "{keyValuePairs["
      Total_Used_Memory "]} GB " +
      $ "/ {keyValuePairs["
      Total_Visible_Memory "]} GB");
      SendTextMessage(memoryConsumptionAlertMessage);
    }

    if (isCPUUsageHigh)
    {
      string cpuConsumptionAlertMessage = string.Format("Alert!!! CPU Usage: {0}", cpuUsage);
      SendTextMessage(cpuConsumptionAlertMessage);
    }

    await Task.CompletedTask;
  }

    private void SendTextMessage(string message)
    {
      try
      {
        var apiKey = Configuration["API_KEY"];
        var apiSecret = Configuration["API_SECRET"];
        var credentials = Credentials.FromApiKeyAndSecret(apiKey, apiSecret);
        var client = new SmsClient(credentials);

        var request = new SendSmsRequest
        {
          To = Configuration["TO"],
          From = Configuration["FROM"],
          Text = message
        };

        var response = client.SendAnSms(request);
        Logger?.LogInformation(response.MessageCount);
      }
      catch (VonageSmsResponseException ex)
      {
        Logger?.LogError(ex.Message);
        throw;
      }
  }
}

Die Funktion SendTextMessage verwendet das Vonage-Paket, um eine SMS zu versenden. SendTextMessage wird aufgerufen von SendResourceUsageInfo aufgerufen, wenn die CPU- oder Speichernutzung einen Schwellenwert überschritten hat. Sie werden die Schwellenwerte und Konfigurationsvariablen später festlegen.

Erstellen einer benutzerdefinierten JobFactory-Klasse

Als nächstes müssen Sie unsere benutzerdefinierte JobFactory Klasse erstellen. Diese Klasse wird benötigt, um neue Job-Instanzen zu erstellen und sie zu entsorgen, wenn sie nicht mehr benötigt werden. Sie benötigen eine eigene Implementierung von Quartz IJobFactory da die Standardimplementierung die Verwendung von Dependency Injection (DI) bei der Erstellung von Aufträgen nicht zulässt. Erstellen Sie eine Klasse namens CustomJobFactory in einer gleichnamigen Datei, die den folgenden Code enthält:

public class CustomJobFactory : IJobFactory
{
    private readonly IServiceProvider _serviceProvider;
    public CustomJobFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        var jobDetail = bundle.JobDetail;
        return (IJob)_serviceProvider.GetService(jobDetail.JobType);
    }
    public void ReturnJob(IJob job)
    {
        var disposableJobInstance = job as IDisposable;

        if (disposableJobInstance != null)
        {
            disposableJobInstance.Dispose();
        }
    }
}

Erstellen Sie die Klasse CustomJobScheduler

Erstellen Sie nun eine neue Klasse namens CustomJobScheduler in einer Datei namens CustomJobScheduler.cs im Stammordner des Projekts und ersetzen Sie den Standard-Quellcode der Klasse CustomJobScheduler den Sie gerade erstellt haben, durch den folgenden Code:

public class CustomJobScheduler
{
    public static void ScheduleJob(IScheduler scheduler)
    {
      var jobName = "NotificationJob";

      var job = JobBuilder.Create<NotificationJob>()
      .WithIdentity(jobName)
      .Build();

      var trigger = TriggerBuilder.Create()
      .WithIdentity($"{jobName}.trigger")
      .StartNow()
      .WithSimpleSchedule(scheduleBuilder =>
          scheduleBuilder
              .WithInterval(TimeSpan.FromSeconds(5))
              .RepeatForever())
      .Build();

      scheduler.ScheduleJob(job, trigger);
    }
}

Die ScheduleJob Methode der CustomJobScheduler Klasse akzeptiert eine Instanz des Typs IScheduler und verwendet sie, um einen Auftrag auf der Grundlage eines gegebenen Auslösers zu planen, der in diesem Fall ein TimeSpan.

Einrichten der Abhängigkeiten

Erstellen Sie nun eine Klasse namens QuartzExtensions mit dem folgenden Code an:

public static class QuartzExtensions
{
  public static void AddQuartz(this IServiceCollection services, params Type[] jobs)
  {
    services.AddSingleton<IJobFactory, CustomJobFactory>();
    services.AddSingleton<NotificationJob>();

    services.AddSingleton(provider =>
    {
      var schedulerFactory = new StdSchedulerFactory();
      var scheduler = schedulerFactory.GetScheduler().Result;
      scheduler.JobFactory = provider.GetService<IJobFactory>();
      scheduler.Start();
      return scheduler;
    });
  }
}

Wie in dem zuvor gezeigten Komponentendiagramm dargestellt, enthält die QuartzExtensions Klasse eine Methode namens AddQuartz. Die Methode erzeugt Instanzen von JobFactory undNotificationJob, die beide eine Singleton-Lebensdauer haben. AddQuartz fügt sie beide dem Container hinzu. Sie ordnet auch unsere benutzerdefinierte JobFactory Klasse der Scheduler-Instanz zu.

Konfigurieren Sie die Anwendung

Fügen Sie die Memory_Threshold, CPU_Threshold, TO, FROM, API_KEY, und API_SECRET Schlüssel in der appsettings.json Datei. Ersetzen von FROM, API_KEY, und API_SECRET durch Ihre Vonage-Nummer, den API-Schlüssel und das API-Geheimnis. TO sollte die Nummer sein, an die Sie die SMS-Benachrichtigungen senden möchten:

{

"AllowedHosts": "*",

"Memory_Threshold": 80,

"CPU_Threshold": 80,

"TO": "TO",

"FROM": "FROM",

"API_KEY": "API_KEY",

"API_SECRET": "API_SECRET"

}

Sie sollten auch NLog.config konfigurieren und dort den Namen und den Pfad der Protokolldatei angeben.

<?xml version="1.0" encoding="utf-8" ?>
<nlog
  xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Trace"
      internalLogFile="C:\Logs\InternalLog.txt">
  <targets>
    <target name="logfile" xsi:type="File"
            fileName="C:/Logs/${shortdate}_AppLog.txt"
            layout="${longdate} ${level:uppercase=true} ${message}"/>
  </targets>
  <rules>
    <logger name="*" minlevel="Debug" writeTo="logfile">
      <filters defaultAction='Log'>
        <when condition="contains('${message}','Quartz scheduler')" action="Ignore" />
      </filters>
    </logger>
  </rules>
</nlog>

Fügen Sie die folgende Zeile in der ConfigureServices Methode der Startup Klasse hinzu, um eine Instanz der NotificationManager Klasse als transienter Dienst zum eingebauten Dienste-Container hinzuzufügen:

services.AddTransient<NotificationManager>();

Planen Sie den Auftrag

Schließlich sollten Sie eine Scheduler-Instanz aus dem Service-Container der Anwendung abrufen und sie zur Planung eines Auftrags verwenden. Hierfür müssen Sie die Configure Methode der Startup Klasse verwenden. Die ConfigureServices und Configure Methoden der Startup Klasse werden standardmäßig generiert, wenn ein Webprojekt in ASP.NET Core oder ASP.NET Core MVC erstellt wird. Die ConfigureServices Methode wird verwendet, um Dienste zum integrierten Container hinzuzufügen, und die Configure Methode wird verwendet, um die HTTP-Anfrage-Pipeline zu konfigurieren.

Schreiben Sie den folgenden Code in die Configure Methode der Klasse Startup:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())

    {
      app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseAuthorization();

    var scheduler = app.ApplicationServices.GetService<IScheduler>();
    CustomJobScheduler.ScheduleJob(scheduler);

    app.UseEndpoints(endpoints =>
    {
      endpoints.MapControllerRoute(
          name: "default",
          pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

Ausführen der Anwendung

So führen Sie diese Anwendung aus:

  1. Suchen Sie die .sln-Datei und doppelklicken Sie darauf, um sie in Visual Studio 2019 zu öffnen.

  2. Drücken Sie F5, um die Anwendung auszuführen

Sobald die Anwendung läuft, warten Sie, bis die Anwendung Protokolle erstellt. Sie können sehen, dass alle 5 Sekunden Protokollnachrichten im C:\\Logs Ordner. Sie können diese Dauer nach Bedarf ändern, indem Sie sie in der Datei appsettings.json Datei konfigurieren. Sie sehen auch, dass über die Vonage SMS API Benachrichtigungen an die vorkonfigurierte Mobilfunknummer gesendet werden, sobald der CPU- oder Speicherverbrauch die Schwellenwerte überschreitet.

a text message generated by the application when the memory usage exceeds 80%a text message generated by the application when the memory usage exceeds 80%

Wie geht es weiter?

Sie können sich eine fertige Version des Projekts auf GitHub unter dem Ordner completed.

Teilen Sie:

https://a.storyblok.com/f/270183/320x319/b39aa07782/joydip-kanjilal.png
Joydip Kanjilal

Joydip is a Microsoft Most Valuable Professional in ASP.NET, Speaker, and Author of several books and articles. He has more than 25 years of experience in IT with more than 18 years in Microsoft .NET and its related technologies. Several times, he was selected as MSDN Featured Developer of the Fortnight (MSDN) and was a Community Credit Winner at http://www.community-credit.com. He has authored more than 500 articles in some of the most reputed sites worldwide, including MSDN, Info World, CodeMag, Tech Beacon, Tech Target, etc.