همراه با انتشار Net Core 3. تیم Asp.Net یک الگوی جدید برای ساخت برنامه های پس زمینه معرفی کرد به نام Worker Service که به عنوان بخشی از SDK در دسترس میباشد. Worker Service برای انجام کارهای پس زمینه ایجاده شده اند و ویژگی اصلی Worker Service این است که بر خلاف برنامه های IIS که بعد از مدتی اگر درخواستی به برنامه ارسال نشود برنامه به حالت تعلیق میرود, Worker Service ها مانند سرویس های ویندوز همیشه فعال خواهند ماند و طی زمان بندی مشخصی کارها را انجام میدهند. در این مطلب به پیاده سازی یک Worker Service و پابلیش گرفتن از آن و معرفی آن به عنوان یک سرویس ویندوز میپردازیم. برای ایجاد پروژه در ویژوال استودیو از بین پروژه ها Worker Service را انتخاب کنید و یک پروژه به نام WorkerServiceExample ایجاد کنید. سپس باید یک کلاس به نام Worker مانند زیر ایجاد شده باشد :
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
متد ExecuteAsync یکبار اجرا میشود و تا زمانی که مقدار CancellationToken برابر true نباشد درون حلقه while میماند و کارهای مورد نیاز را انجام میدهد. متد ExecuteAsync بعد از متد StartAsync اجرا میشود. و بعد از اتمام کار بعد از متد ExecuteAsync متد StopAsync اجرا میشود. متد های StopAsync و StartAsync در کلاس BackgroundService ایجاد شده اند و میتوانید این دو متد را override کنید و بسته به نیاز خود یک سری کار را انجام دهید هرچند می تواند این متدها را override نکنید اما باید متد ExecuteAsync را حتما پیاده سازی کنید به دلیل abstract بودن متد. اگر برنامه را اجرا کنید هر یک ثانیه یک بار پیغامی در کنسول نشان داده میشود مانند زیر :
info: WorkerServiceExample.Worker[0]
Worker running at: 10/09/2020 20:35:00 +03:30
info: WorkerServiceExample.Worker[0]
Worker running at: 10/09/2020 20:35:01 +03:30
info: WorkerServiceExample.Worker[0]
Worker running at: 10/09/2020 20:35:02 +03:30
info: WorkerServiceExample.Worker[0]
Worker running at: 10/09/2020 20:35:03 +03:30
در Worker Service ها امکان دریافت سرویس های رجیسترشده از نوع Scoped وجود ندارد به طور معمولی اما میتوانید سرویس های خود را از نوع singleton و transient رجیستر کنید و آنها را از DI دریافت کنید و اگر میخواهید یک سرویس رجیستر شده از نوع Scoped را از DI دریافت کنید باید از IServiceProvider استفاده کنید. در ادامه یک سرویس ایجاد میکنیم و سرویس را به صورت Scoped به DI معرفی میکنیم و سپس Worker Service را به عنوان یک سرویس به سرویس های ویندوز معرفی میکنیم. ابتدا یک اینترفیس به نام ISomeService ایجاد میکنیم و یک کلاس که از اینترفیس ارث بری کرده باشد و متد های آنرا implement کند.
public interface ISomeService
{
Task DoSomething(CancellationToken cancellationToken);
}
public class SomeService : ISomeService
{
private readonly ILogger<SomeService> _logger;
public SomeService(ILogger<SomeService> logger)
{
_logger = logger;
}
public async Task DoSomething(CancellationToken cancellationToken)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.CompletedTask;
}
}
سپس این سرویس را به صورت Scoped به DI رجیستر میکنیم در کلاس Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddScoped<ISomeService, SomeService>();//<--NOTE THIS
});
}
در ادامه برای استفاده از سرویس ISomeService در کلاس Worker باید ابتدا IServiceProvider را از DI دریافت کنیم و در متد ExecuteAsync یک scope ایجاد کنیم و سپس سرویس مورد نیاز خود را که از نوع Scoped رجیستر شده است را دریافت کنیم و متد DoSomething را فراخوانی کنیم.
public class Worker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using(var scope = _serviceProvider.CreateScope())
{
var someService = scope.ServiceProvider.GetRequiredService<ISomeService>();
await someService.DoSomething(stoppingToken);
}
await Task.Delay(1000, stoppingToken);
}
}
}
اکنون اگر برنامه را اجرا کنید تفاوتی با قبل برای اجرای کارها مشاهده نمیکند فقط یک سرویس از نوع Scoped را هربار از DI دریافت میکنیم و متد DoSomething آنرا صدا میزنیم. هرچند نیازی به رجیستر کردن سرویس از نوع Scoped در این پروژه نبود فقط هدف از این کار نحوه دریافت سرویس های ثبت شده از نوع Scoped در Worker Service بود. اگر همین سرویس ISomeService را از نوع Transient رجیستر کنید میتوانید مستقیما درون constructor کلاس Worker سرویس مورد نیاز خود را از DI دریافت کنید و متد DoSomething را فراخوانی کنید و نیازی به دریافت IServiceProvider و ایجاد scope نیست. در ادامه برای پابلیش پروژه و معرفی کردن آن به سرویس های ویندوز باید پکیج Microsoft.Extensions.Hosting.WindowsServices را نصب کنید و در کلاس Program.cs دستور UseWindowsService را اضافه کنید.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.AddScoped<ISomeService, SomeService>();
})
.UseWindowsService();
سپس یک پابلیش از پروژه بگیرید و در مسیر فایل های ایجاد شده پابلیش powershell را از طریق ادمین اجرا کنید و کافیست دستور زیر را اجرا کنید :
sc.exe create WorkerServiceExample binPath="C:\Users\Farhad\source\repos\WorkerServiceExample\WorkerServiceExample\bin\Release\netcoreapp3.1\publish\WorkerServiceExample.exe"
توجه داشته باشید که باید مسیر کامل فایل exe را به powershell معرفی کنید. این دستور یک سرویس به نام WorkerServiceExample در سرویس های ویندوز اجرا میکند.اکنون سرویس ایجاد شده است اما Stop است, برای اجرای سرویس کافیست دستور زیر را اجرا کنید:
sc.exe start WorkerServiceExample
هر زمان خواستید سرویس مورد نظر را stop یا delete کنید میتوانید دستورات زیر را اجرا کنید:
sc.exe delete WorkerServiceExample
و
sc.exe stop WorkerServiceExample
;)
Powered by Froala Editor