Topshelf – tworzenie usługi systemowej

Wprowadzenie

Rozwijanie usługi systemowej stworzonej z domyślnego szablonu w Visual Studio nie jest prostym zajęciem. Szczególnie debugowanie takiej aplikacji sprawia sporo problemów. Nie można z poziomu IDE uruchomić usługi i jej debugować. Trzeba podpinać się pod działający proces. W przypadku gdy chcemy zdebugować start usługi, musimy posiłkować się różnymi dziwnymi konstrukcjami, które zatrzymają start usługi do momentu podpięcia się debugera.

W dzisiejszym wpisie będę chciał Ci pokazać, jak dzięki bibliotece Topshelf możemy uprościć sobie tworzenie usługi. Zamienimy aplikację konsolową w usługę systemową. Dzięki temu proces tworzenia oraz debugowania takiej usługi jest dużo łatwiejszy. W przykładzie stworzymy nową usługę, która uruchomi serwer z biblioteki Hangfire, którą opisywałem kilka tygodni temu.

Przykład do testów

W przykładzie przygotowałem prostą klasę, której zadaniem będzie pobieranie strony z przekazanego adresu do metody. Z racji tego, że jest to przykład, klasa nie będzie nic z tym robiła. Klasa wygląda tak:

public class Downloader : IDownloader
{
public void Download(string url)
{
var client = new WebClient();
var content = client.DownloadString(url);
}
}
public interface IDownloader
{
void Download(string url);
}
view raw Downloader.cs hosted with ❤ by GitHub

Kod powyższej klasy będzie wykonywany przez serwer z Hangfire, który będzie działał w ramach tworzonej usługi z wykorzystaniem Topshelf. Zadanie do wykonania dodamy z poziomu aplikacji ASP.NET MVC, która będzie tylko je dodawała, bez jego wykonywania (w ramach niej nie będzie działał serwer Hangfire). Kod dodający zadanie do wykonania wygląda tak:

public class DownloadViewModel
{
[Required]
public string Url { get; set; }
}
public class HomeController : Controller
{
private Downloader _downloader = new Downloader();
public ActionResult Index()
{
return View();
}
public ActionResult Download()
{
return View(new DownloadViewModel());
}
[HttpPost]
public ActionResult Download(DownloadViewModel viewModel)
{
if(ModelState.IsValid == false)
{
return View(viewModel);
}
BackgroundJob.Enqueue(() => _downloader.Download(viewModel.Url));
return RedirectToAction("Download");
}
}

Viewmodel przekazywany do akcji zawiera jedną właściwość (Url), za pomocą której przekazujemy adres do pobrania. W kontrolerze w drugiej akcji Download powiązanej z żądaniem typu POST dodajemy zadanie pobrania przekazanego adresu z wykorzystaniem klasy BackgroundJob i metody Enqueue.

Usługa w Topshelf

Topshelf (http://topshelf-project.com/) bardzo ułatwia tworzenie usługi systemowej w stosunku do standardowego szablonu z Visual Studio. Przede wszystkim usługa tworzona z wykorzystaniem biblioteki jest aplikacją konsolową. Dzięki temu możemy ją uruchamiać z poziomu Visual Studio, w szczególności w trybie debugowania, czego nie zrobimy ze standardowym projektem.

Korzystanie z biblioteki Topshelf jest bardzo proste. Po pierwsze instalujemy bibliotekę z Nugeta, po drugie tworzymy klasę, która będzie zawierała kod wykonywany w ramach usługi. Klasa nie musi być w żaden sposób powiązana z biblioteką Topshelf. Wystarczy, że będzie zawierała metody dla startu oraz zatrzymania usługi. Może również zawierać metody dla pozostałych akcji (np. pauzowanie czy wznawianie).

W przykładzie utworzyłem prostą klasę, która tworzy serwer Hangfire, startuje go i zatrzymuje podczas zatrzymywania usługi:

public class HangfireService
{
private BackgroundJobServer _server;
static HangfireService()
{
GlobalConfiguration.Configuration
.UseSqlServerStorage(@"Server=localhost\sqlexpress;Database=TopshelfExample;Trusted_Connection=True;");
}
public void Start()
{
var options = new BackgroundJobServerOptions();
_server = new BackgroundJobServer(options);
}
public void Stop()
{
if(_server != null)
{
_server.Dispose();
}
}
}

W klasie zdefiniowany jest statyczny konstruktor, który ustawia connection string do bazy w Hangfire, w której znajdować się będą zadania dodane przez aplikację ASP.NET MVC. Metody Start oraz Stop uruchamiają i zatrzymują serwer Hangfire odpowiednio przy starcie oraz zatrzymaniu usługi.

W przykładzie kluczowy kod znajduje się w klasie Program. Tam wykorzystuje bibliotekę Topshelf do zdefiniowania usługi. W podstawowym użyciu kod wygląda tak:

class Program
{
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.UseNLog();
x.Service<HangfireService>(h =>
{
h.ConstructUsing(n => new HangfireService());
h.WhenStarted(s => s.Start());
h.WhenStopped(s => s.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("Hangfire Service");
x.SetDisplayName("Hangfire Service");
x.SetServiceName("Hangfire Service");
});
}
}
view raw Program.cs hosted with ❤ by GitHub

Konfigurację usługi definiujemy w delegacie przekazanym do statycznej metody Run klasy HostFactory. W delegacie określamy przede wszystkim, jaka klasa (w przykładzie HangfireService) jest używana jako klasa usługi, oraz które metody mają zostać uruchomione w poszczególnych etapach życia usługi (Start oraz Stop). Wszystko to określamy w kolejnym delegacie przekazywanym do metody Service.

Poza samą definicją usługi w klasie Run możemy określić też inne rzeczy. W powyższym kodzie określam jeszcze:

  • logowanie działania usługi z wykorzystaniem Nloga – wywołanie metody UseNlog,
  • działanie usługi z wykorzystaniem konta lokalnego po zainstalowaniu – wywołanie metody RunAsLocalSystem,
  • informacje wyświetlane między innymi w oknie z usługami systemowymi – wywołanie metod SetServiceName, SetDisplayName oraz SetDescritpion.

Biblioteka umożliwia określenie jeszcze wielu innych rzeczy związanych z działaniem usługi. Warto przejrzeć stronę z dokumentacją – https://topshelf.readthedocs.io/en/latest/index.html.

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Uruchamianie oraz debugowanie

Jednym z plusów z korzystania z biblioteki Topshelf jest możliwość uruchomienia usługi bezpośrednio z poziomu Visual Studio. Wystarczy, że ustawimy projekt z usługą jako startowy i go uruchomimy. Poniżej znajduje się zrzut okna konsoli testowego przykładu:

topshelf console app

Widać, że usługa wystartowała oraz w ramach niej wystartował serwer Hangfire (w przykładzie skonfigurowałem, żeby Nlog wyświetlał logi na konsoli).

Działa również debugowanie usługi uruchomionej z poziomu Visual Studio:

topshelf debugging

Instalacja usługi

Topshelf, obok ułatwienia tworzenia usługi systemowej, dodaje również do aplikacji kilka komend dostępnych z wiersza poleceń. Możemy na przykład za pomocą samej usługi zainstalować ją w systemie. Wystarczy uruchomić aplikację z komendą install. Można również przekazać różne parametry (np. nazwę usługi), które zostaną użyte podczas instalacji usługi. Gdy ich nie przekażemy, użyte zostaną domyślne wartości ustawione w kodzie.

Przykład instalacji widać poniżej na zrzucie ekranu:

topshelf install

Więcej informacji o dostępnych komendach i ich opcjach znajduje się w dokumentacji – https://topshelf.readthedocs.io/en/latest/overview/commandline.html.

Przykład

Na githubie znajduje się kod przykładu – https://github.com/danielplawgo/TopshelfExample. Przed uruchomieniem go należy ustawić poprawne connection stringi do bazy, w której Hangfire zapisuje zadania do wykonania. Connection string znajduje się w dwóch miejscach: w klasie Startup w projekcie TopshelfExample.Web oraz HangfireService w projekcie TopshelfExample.Service.

Podsumowanie

Topshelf bardzo ułatwia proces tworzenia usługi systemowej. Możliwość utworzenia jej z aplikacji konsolowej powoduje, że usługę możemy uruchamiać bezpośrednio z poziomu Visual Studio. To ułatwia jej debugowanie i analizowanie działania usługi.

A Ty lubisz tworzyć usługi systemowe?

Szkolenie C# i .NET 5

Szkolenie C# i .NET 5

Zainteresował Ciebie ten temat? A może chcesz więcej? Jak tak to zapraszam na moje autorskie szkolenie o C# oraz .NET.

2 thoughts on “Topshelf – tworzenie usługi systemowej

  • Pingback: dotnetomaniak.pl
  • Jeśli chodzi o debugowanie usług Windows w VS to pamiętam że dawało się to prosto zrobić. Parę lat tego już nie używałem więc podaje pierwszy link jaki znalazłem.

    https://docs.microsoft.com/pl-pl/dotnet/framework/windows-services/how-to-debug-windows-service-applications

    Add a method to your service that runs the OnStart and OnStop methods:
    internal void TestStartupAndStop(string[] args)
    {
    this.OnStart(args);
    Console.ReadLine();
    this.OnStop();
    }
    Rewrite the Main method as follows:
    static void Main(string[] args)
    {
    if (Environment.UserInteractive)
    {
    MyNewService service1 = new MyNewService(args);
    service1.TestStartupAndStop(args);
    }
    else
    {
    // Put the body of your old Main method here.
    }
    }
    In the Application tab of the project’s properties, set the Output type to Console Application.
    Choose Start Debugging (F5).
    To run the program as a Windows Service again, install it and start it as usual for a Windows Service. It’s not necessary to reverse these changes.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.