Приложение Service Monitor с .NET Core
Это руководство о том, как создать приложение Service Monitor, но что это такое? Проще говоря, это приложение, которое позволяет отслеживать сервисы в сети и сохранять результаты мониторинга в базе данных, в данном случае SQL Server.
Существует много инструментов, которые могут предоставить эту функцию, а также есть лучшие инструменты, которые можно купить за деньги, но цель этого руководства - показать, как использовать возможности .NET Core для создания приложения, которое разработчики могут расширить для пользовательских требований. ,
Основная идея заключается в следующем: иметь процесс для выполнения бесконечными способами для мониторинга хостов, баз данных и API; сохранить результаты мониторинга в базе данных SQL Server, затем мы можем создать удобный пользовательский интерфейс для конечного пользователя и показать статус для каждой службы, у нас может быть множество целей для мониторинга, но лучше разрешить пользователям подписываться на определенные службы, а не на все; например, администраторы баз данных должны следить за серверами баз данных, а не за API, разработчики должны наблюдать за базами данных и API-интерфейсами для разработки и т. д.
Также подумайте о том, чтобы иметь большие мониторы в вашей комнате разработки и наблюдать за статусом ваших услуг, а лучше всего - иметь диаграммы. :)
Одной из специальных функций может быть наличие службы уведомлений для отправки сообщений всем администраторам в случае сбоя одной или нескольких служб, в этом контексте под службой понимается цель, такая как хост, база данных, API
В этом руководстве мы будем работать с мониторингом следующих сервисов:
название |
Описание |
Хост |
Пинг существующего хоста |
База данных |
Откройте и закройте соединение для существующей базы данных |
RESTful API |
Использовать одно действие из существующего API |
Предыстория
Как мы уже говорили ранее, мы создадим приложение для мониторинга существующих целей (хостов, баз данных, API), поэтому нам необходимо иметь базовые знания об этих концепциях.
Хосты будут осуществлять мониторинг с помощью действия ping, поэтому мы добавим пакеты, связанные с сетью, для выполнения этого действия.
Базы данных будут отслеживаться с открытыми и закрытыми соединениями, не используйте интегрированную защиту, потому что вам нужно имитироватьь процесс мониторинга службы с вашими учетными данными, поэтому в этом случае лучше иметь конкретного пользователя для соединения с базой данных, и только это действие, чтобы избежать взлома.
API RESTful будут отслеживать с клиентом REST целевое действие, которое возвращает простой JSON.
База данных
Внутри репозитория есть каталог с именем \Resources\Database, и этот каталог содержит связанные файлы базы данных, поэтому обязательно запустите следующие файлы в следующем порядке:
Имя файла |
Описание |
00 - Database.sql |
Определение базы данных |
01 - Tables.sql |
Определение таблиц |
02 - Constraints.sql |
Ограничения (первичные ключи, внешние ключи и уникальные) |
03 - Rows.sql |
Исходные данные |
Мы можем найти скрипты для базы данных здесь
Таблица |
Описание |
EnvironmentCategory |
Содержит все категории для сред: разработка, контроль качества и производство |
ServiceCategory |
Содержит все категории для сервисов: база данных, API остальных, сервер, URL и веб-сервис |
Service |
Содержит все определения услуг |
ServiceWatcher |
Содержит все компоненты со стороны C # для выполнения операций наблюдения |
ServiceEnvironment |
Содержит отношение к сервису и среде, например, мы можем определить сервис с именем FinanceService с различными средами: разработка, контроль качества и производство |
ServiceEnvironmentStatus |
Содержит статус для каждого сервиса в среде |
ServiceEnvironmentStatusLog |
Содержит детали для каждого статуса среды обслуживания |
Owner |
Содержит список пользователей для приложения, которое представляет всех владельцев |
ServiceOwner |
Содержит связь между сервисом и владельцем |
User |
Содержит всех пользователей для просмотра услуг |
ServiceUser |
Содержит отношение между сервисом и пользователем |
Пожалуйста, не забывайте, что мы работаем с решением, которое работает на локальном компьютере, в каталоге ресурсов есть пример API для выполнения тестов, но вам нужно изменить строку подключения и добавить свои службы в соответствии с вашим контекстом.
Кроме того, не рекомендуется выставлять реальные строки подключения в таблице ServiceEnvironment. Пожалуйста, запросите у своего администратора базы данных, чтобы один пользователь мог только выполнить открытое подключение для целевой базы данных, в случае, если безопасность баз данных будет задачей с вашей стороны, создайте конкретных пользователей для выполнения. Открывайте ТОЛЬКО соединение с базами данных и не допускайте раскрытия конфиденциальной информации.
.NET Core Solution
Теперь нам нужно определить проекты для этого решения, чтобы получить четкое представление о масштабах проекта:
название проекта |
Тип |
Описание |
ServiceMonitor.Core |
Библиотека классов |
Содержит все определения, связанные с хранением базы данных |
ServiceMonitor.Common |
Библиотека классов |
Содержит общие определения для проекта ServiceMonitor, такие как наблюдатели, сериализатор и клиенты (REST) |
ServiceMonitor.WebAPI |
Веб-API |
Содержит контроллеры Web API для чтения и записи информации о мониторинге |
ServiceMonitor |
Консольное приложение |
Содержит процесс для мониторинга всех услуг |
ServiceMonitor.Core
Этот проект содержит все определения объектов и доступа к базе данных, поэтому нам необходимо добавить следующие пакеты для проекта:
название |
Версия |
Описание |
Microsoft.EntityFrameworkCore.SqlServer |
Последняя версия |
Предоставляет доступ к SQL Server через EF Core |
Этот проект состоит из трех уровней: бизнес-логика, доступ к базе данных и сущности; пожалуйста, ознакомьтесь со статьей EF Core для Entreprise, чтобы лучше понять этот проект и его слои.
Код класса DashboardService :
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.Core.BusinessLayer.Responses;
using ServiceMonitor.Core.DataLayer;
using ServiceMonitor.Core.DataLayer.DataContracts;
using ServiceMonitor.Core.EntityLayer;
namespace ServiceMonitor.Core.BusinessLayer
{
public class DashboardService : Service, IDashboardService
{
public DashboardService(ILogger<DashboardService> logger, ServiceMonitorDbContext dbContext)
: base(logger, dbContext)
{
}
public async Task<IListResponse<ServiceWatcherItemDto>> GetActiveServiceWatcherItemsAsync()
{
Logger?.LogDebug("'{0}' has been invoked", nameof(GetActiveServiceWatcherItemsAsync));
var response = new ListResponse<ServiceWatcherItemDto>();
try
{
response.Model = await DbContext.GetActiveServiceWatcherItems().ToListAsync();
Logger?.LogInformation("The service watch items were loaded successfully");
}
catch (Exception ex)
{
response.SetError(Logger, nameof(GetActiveServiceWatcherItemsAsync), ex);
}
return response;
}
public async Task<IListResponse<ServiceStatusDetailDto>> GetServiceStatusesAsync(string userName)
{
Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusesAsync));
var response = new ListResponse<ServiceStatusDetailDto>();
try
{
var user = await DbContext.GetUserAsync(userName);
if (user == null)
{
Logger?.LogInformation("There isn't data for user '{0}'", userName);
return new ListResponse<ServiceStatusDetailDto>();
}
else
{
response.Model = await DbContext.GetServiceStatuses(user).ToListAsync();
Logger?.LogInformation("The service status details for '{0}' user were loaded successfully", userName);
}
}
catch (Exception ex)
{
response.SetError(Logger, nameof(GetServiceStatusesAsync), ex);
}
return response;
}
public async Task<ISingleResponse<ServiceEnvironmentStatus>> GetServiceStatusAsync(ServiceEnvironmentStatus entity)
{
Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceStatusAsync));
var response = new SingleResponse<ServiceEnvironmentStatus>();
try
{
response.Model = await DbContext.GetServiceEnvironmentStatusAsync(entity);
}
catch (Exception ex)
{
response.SetError(Logger, nameof(GetServiceStatusAsync), ex);
}
return response;
}
}
}
ServiceMonitor.Common
контракты
- IWatcher
- IWatchResponse
- ISerializer
Код интерфейса IWatcher:
Скрыть копию кода
using System.Threading.Tasks;
namespace ServiceMonitor.Common.Contracts
{
public interface IWatcher
{
string ActionName { get; }
Task<WatchResponse> WatchAsync(WatcherParameter parameter);
}
}
Код интерфейса IWatchResponse:
namespace ServiceMonitor.Common.Contracts
{
public interface IWatchResponse
{
bool Success { get; set; }
string Message { get; set; }
string StackTrace { get; set; }
}
}
Код интерфейса ISerializer:
Скрыть копию кода
namespace ServiceMonitor.Common.Contracts
{
public interface ISerializer
{
string Serialize<T>(T obj);
T Deserialze<T>(string source);
}
}
Наблюдатели
Это реализации:
- DatabaseWatcher
- HttpRequestWatcher
- PingWatcher
Код класса DatabaseWatcher:
Скрыть код копии
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor.Common
{
public class DatabaseWatcher : IWatcher
{
public string ActionName
=> "OpenDatabaseConnection";
public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
{
var response = new WatchResponse();
using (var connection = new SqlConnection(parameter.Values["ConnectionString"]))
{
try
{
await connection.OpenAsync();
response.Success = true;
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
response.StackTrace = ex.ToString();
}
}
return response;
}
}
}
Код класса HttpWebRequestWatcher:
Скрыть код копии
using System;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor.Common
{
public class HttpRequestWatcher : IWatcher
{
public string ActionName
=> "HttpRequest";
public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
{
var response = new WatchResponse();
try
{
var restClient = new RestClient();
await restClient.GetAsync(parameter.Values["Url"]);
response.Success = true;
}
catch (Exception ex)
{
response.Success = false;
response.Message = ex.Message;
response.StackTrace = ex.ToString();
}
return response;
}
}
}
Код класса PingWatcher:
Скрыть копию кода
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor.Common
{
public class PingWatcher : IWatcher
{
public string ActionName
=> "Ping";
public async Task<WatchResponse> WatchAsync(WatcherParameter parameter)
{
var ping = new Ping();
var reply = await ping.SendPingAsync(parameter.Values["Address"]);
return new WatchResponse
{
Success = reply.Status == IPStatus.Success ? true : false
};
}
}
}
ServiceMonitor.WebAPI
Этот проект представляет RESTful API для службы мониторинга, поэтому у нас будет два контроллера: DashboardController и AdministrationController. Панель инструментов содержит все операции, связанные с результатами конечного пользователя, а Администрирование содержит все операции, связанные с сохранением информации (создание, редактирование и удаление).
Панель инструментов
DashboardController код класса:
Скрыть код копии
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebAPI.Responses;
namespace ServiceMonitor.WebAPI.Controllers
{
#pragma warning disable CS1591
[Route("api/v1/[controller]")]
[ApiController]
public class DashboardController : ControllerBase
{
protected readonly ILogger Logger;
protected readonly IDashboardService Service;
public DashboardController(ILogger<DashboardController> logger, IDashboardService service)
{
Logger = logger;
Service = service;
}
#pragma warning restore CS1591
/// <summary>
/// Gets service watcher items (registered services to watch with service monitor)
/// </summary>
/// <returns>A sequence of services to watch</returns>
/// <response code="200"></response>
/// <response code="500"></response>
[HttpGet("ServiceWatcherItem")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetServiceWatcherItemsAsync()
{
Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceWatcherItemsAsync));
var response = await Service.GetActiveServiceWatcherItemsAsync();
return response.ToHttpResponse();
}
}
}
Администрирование
Код класса AdministrationController:
Скрыть код копии
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Core.BusinessLayer.Contracts;
using ServiceMonitor.WebAPI.Responses;
namespace ServiceMonitor.WebAPI.Controllers
{
#pragma warning disable CS1591
[Route("api/v1/[controller]")]
[ApiController]
public class DashboardController : ControllerBase
{
protected readonly ILogger Logger;
protected readonly IDashboardService Service;
public DashboardController(ILogger<DashboardController> logger, IDashboardService service)
{
Logger = logger;
Service = service;
}
#pragma warning restore CS1591
/// <summary>
/// Gets service watcher items (registered services to watch with service monitor)
/// </summary>
/// <returns>A sequence of services to watch</returns>
/// <response code="200"></response>
/// <response code="500"></response>
[HttpGet("ServiceWatcherItem")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetServiceWatcherItemsAsync()
{
Logger?.LogDebug("'{0}' has been invoked", nameof(GetServiceWatcherItemsAsync));
var response = await Service.GetActiveServiceWatcherItemsAsync();
return response.ToHttpResponse();
}
}
}
ServiceMonitor
Этот проект содержит все объекты для Service Monitor Client, в этом проекте мы добавили пакет Newtonsoft.Json для сериализации JSON, в ServiceMonitor.Common есть интерфейс с именем, ISerializer и так как мы не хотим заставлять использовать определенный сериализатор, вы можете изменить это на этом уровне. :)
Код класса ServiceMonitorSerializer:
using Newtonsoft.Json;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor
{
public class ServiceMonitorSerializer : ISerializer
{
public string Serialize<T>(T obj)
=> JsonConvert.SerializeObject(obj);
public T Deserialze<T>(string source)
=> JsonConvert.DeserializeObject<T>(source);
}
}
Далее мы будем работать над лассом MonitorControllerк, в этом классе мы будем выполнять все операции наблюдения и сохранять все результаты в базе данных через AdministrationController в API-интерфейсе Service Monitor.
Код класса MonitorController:
Скрыть код копии
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Clients;
using ServiceMonitor.Clients.Models;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor
{
public class MonitorController
{
public MonitorController(ILogger logger, IWatcher watcher, IServiceMonitorWebAPIClient client, AppSettings appSettings)
{
Logger = logger;
Watcher = watcher;
Client = client;
AppSettings = appSettings;
}
public ILogger Logger { get; }
public IWatcher Watcher { get; }
public IServiceMonitorWebAPIClient Client { get; }
public AppSettings AppSettings { get; }
public async Task ProcessAsync(ServiceWatchItem item)
{
while (true)
{
try
{
Logger?.LogTrace("{0} - Watching '{1}' for '{2}' environment", DateTime.Now, item.ServiceName, item.Environment);
var watchResponse = await Watcher.WatchAsync(new WatcherParameter(item.ToDictionary()));
if (watchResponse.Success)
Logger?.LogInformation(" Success watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);
else
Logger?.LogError(" Failed watch for '{0}' in '{1}' environment", item.ServiceName, item.Environment);
var serviceStatusLog = new ServiceStatusLogRequest
{
ServiceID = item.ServiceID,
ServiceEnvironmentID = item.ServiceEnvironmentID,
Target = item.ServiceName,
ActionName = Watcher.ActionName,
Success = watchResponse.Success,
Message = watchResponse.Message,
StackTrace = watchResponse.StackTrace
};
try
{
await Client.PostServiceEnvironmentStatusLog(serviceStatusLog);
}
catch (Exception ex)
{
Logger?.LogCritical(" Error on saving watch response ({0}): '{1}'", item.ServiceName, ex.Message);
}
}
catch (Exception ex)
{
Logger?.LogCritical(" Error watching service: '{0}': '{1}'", item.ServiceName, ex.Message);
}
Thread.Sleep(item.Interval ?? AppSettings.DelayTime);
}
}
}
}
Перед запуском консольного приложения убедитесь в следующих аспектах:
- База данных ServiceMonitor доступна
- База данных ServiceMonitor содержит информацию для категорий услуг, сервисов, сервис-наблюдателей и пользователей.
- API ServiceMonitor доступен
Мы можем проверить возвращаемое значение для URL api/v1/Dashboard/ServiceWatcherItems :
{
"message":null,
"didError":false,
"errorMessage":null,
"model":[
{
"serviceID":1,
"serviceEnvironmentID":1,
"environment":"Development",
"serviceName":"Northwind Database",
"interval":15000,
"url":null,
"address":null,
"connectionString":"server=(local);database=Northwind;user id=johnd;password=SqlServer2017$",
"typeName":"ServiceMonitor.Common.DatabaseWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
},
{
"serviceID":2,
"serviceEnvironmentID":3,
"environment":"Development",
"serviceName":"DNS",
"interval":3000,
"url":null,
"address":"192.168.1.1",
"connectionString":null,
"typeName":"ServiceMonitor.Common.PingWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
},
{
"serviceID":3,
"serviceEnvironmentID":4,
"environment":"Development",
"serviceName":"Sample API",
"interval":5000,
"url":"http://localhost:5612/api/values",
"address":null,
"connectionString":null,
"typeName":"ServiceMonitor.Common.HttpWebRequestWatcher, ServiceMonitor.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
}
]
}
Как мы видим, API возвращает все службы DefaultUser, пожалуйста помните о концепции, согласно которой один пользователь может подписать несколько служб для просмотра, очевидно, в этом примере наш пользователь по умолчанию привязан ко всем службам, но мы можем изменить эту ссылку в таблице ServiceUser.
Код класса Program:
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using ServiceMonitor.Clients;
using ServiceMonitor.Clients.Models;
using ServiceMonitor.Common;
using ServiceMonitor.Common.Contracts;
namespace ServiceMonitor
{
class Program
{
private static ILogger Logger;
private static readonly AppSettings AppSettings;
static Program()
{
Logger = LoggingHelper.GetLogger<Program>();
var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
var configuration = builder.Build();
AppSettings = new AppSettings();
configuration.GetSection("appSettings").Bind(AppSettings);
}
static void Main(string[] args)
{
StartAsync(args).GetAwaiter().GetResult();
Console.ReadLine();
}
static async Task StartAsync(string[] args)
{
Logger.LogDebug("Starting service monitor...");
var client = new ServiceMonitorWebAPIClient();
var serviceWatcherItemsResponse = default(ServiceWatchResponse);
try
{
serviceWatcherItemsResponse = await client.GetServiceWatcherItemsAsync();
}
catch (Exception ex)
{
Logger.LogError("Error on retrieve watch items: {0}", ex);
return;
}
foreach (var item in serviceWatcherItemsResponse.Model)
{
var watcherType = Type.GetType(item.TypeName, true);
var watcherInstance = Activator.CreateInstance(watcherType) as IWatcher;
await Task.Factory.StartNew(async () =>
{
var controller = new MonitorController(Logger, watcherInstance, client, AppSettings);
await controller.ProcessAsync(item);
});
}
}
}
}
Как только мы проверили предыдущие аспекты, теперь мы переходим к включению консольного приложения, и получили следующий вывод консоли:
dbug: ServiceMonitor.Program[0]
Starting application
sr trce: ServiceMonitor.Program[0]
06/20/2017 23:09:30 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:30 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:30 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:35 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:37 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:39 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:42 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:43 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:45 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:47 - Watching 'Northwind Database' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:48 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:48 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:51 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:53 - Watching 'Sample API' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:54 - Watching 'DNS' for 'Development' environment
trce: ServiceMonitor.Program[0]
06/20/2017 23:09:57 - Watching 'DNS' for 'Development' environment
Теперь мы приступаем к проверке сохраненных данных в базе данных, пожалуйста, проверьте таблицу ServiceEnvironmentStatus, вы должны получить следующий результат:
Скрыть копию кода
ServiceEnvironmentStatusID ServiceEnvironmentID Success WatchCount LastWatch
-------------------------- -------------------- ------- ----------- -----------------------
1 4 1 212 2018-11-22 23:11:34.113
2 1 1 78 2018-11-22 23:11:33.370
3 3 1 366 2018-11-22 23:11:34.620
(3 row(s) affected)
Как это все вместе работает? Консольное приложение берет все сервисы для наблюдения из API, а затем бесконечно запускает одну задачу на элемент наблюдения, MonitorController. Для каждой задачи есть время задержки, этот интервал задается в определении сервиса, но если нет определенного значения для интервала интервал берется из AppSettings; поэтому после выполнения действия Watch результат сохраняется в базе данных через API, и процесс повторяется. Если вы хотите выполнить операцию watch для других типов, вы можете создать свой собственный класс Watcher.
Точки интереса
- DatabaseWatcher работает с SQL Server, так как вы подключаетесь к MySQL, PostgreSQL, Oracle и другим СУБД? Создайте свой класс Watcher для конкретной СУБД, реализации интерфейса IWatcher и напишите код для подключения к целевой базе данных.
- Можем ли мы разместить сервисный монитор на платформах не Windows? Да, поскольку .NET Core является кроссплатформенным, мы можем разместить этот проект на Windows, Mac OS и Linux.
- Насколько нам известно, в .NET Core нет встроенной поддержки ASMX, но мы можем отслеживать оба вида сервисов, просто добавляя строки в таблицу Service, ASMX заканчивается на .asmx .
- Почему консольный клиент и API не являются одним проектом? Чтобы избежать распространенных проблем при публикации, лучше иметь два разных проекта, потому что в этом случае мы можем запустить сервисный монитор на одном сервере и разместить API на другом сервере.
- В этой первоначальной версии нет никаких настроек безопасности, потому что лучше добавить эту реализацию в соответствии с вашим сценарием: вы можете заставить этот проект работать с Аутентификацией Windows, пользовательской Аутентификацией или добавить внешний сервис для аутентификации.
Улучшения кода
- Добавьте Identity Server
- Добавьте уведомления администраторам о критических ошибках при просмотре сервисов (электронная почта, смс и т. д.)
- Лучше иметь TypeName в ServiceCategory вместо ServiceWatcher
- Добавьте проект пользовательского интерфейса, чтобы наглядно показать состояние сервисов для конечных пользователей, используя некоторые интерфейсные среды, такие как Angular