什麼是Json Schema ? Json schema是一種聲明式語言,它可以用來標識Json的結構,數據類型和數據的具體限制,它提供了描述期望Json結構的標準化方法。 利用Json Schema, 你可以定義Json結構的各種規則,以便確定Json數據在各個子系統中交互傳輸時保持相容和一致的格 ...
前言
在電腦系統中,定時執行一些後臺任務是很常見的場景,比如定時發送郵件、備份數據等等。
那麼,.NET 技術如何通過編程靈活地實現項目里複雜的自定義任務呢?
如果是 Windows 生態,通常來說,可以有這些方式:
- 編寫一個程式,通過 Windows 內置的任務計劃來定時執行。
- 編寫一個程式,通過 Windows 內置的 Services 來定時執行。
- 編寫一個定時迴圈執行任務的程式,在 Windows 系統啟動時配置為自動執行。
……
但是,如果是一個中小型的 Web 應用系統,這些方法方式就顯得不太合適。Asp.net core Webapi 有沒有辦法執行定時任務呢?答案是有的,Asp.net core Webapi 可以通過常駐後臺的托管服務來執行定時任務。
本文是 Asp.net core Webapi 運行一個常駐後臺並從資料庫中導出數據的托管服務的例子,寫出來供大家指點,在討論過程中共同提高水平。
Step By Step 實現步驟
- 創建一個 asp.net core webapi 項目
- 從 Nuget 安裝以下包
Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Relational
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools - 打開 appsettings.json 並添加資料庫連接字元串,如:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true" } }
- 添加一個繼承於 IdentityUser 的 User 類
using Microsoft.AspNetCore.Identity; public class User: IdentityUser<long> { public DateTime CreationTime { get; set; } public string? NickName { get; set; } }
- 添加一個繼承於 IdentityRole 的 Role 類
using Microsoft.AspNetCore.Identity; public class Role: IdentityRole<long> { }
- 創建資料庫上下文
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; public class TestDbContext: IdentityDbContext<User, Role, long> { public TestDbContext(DbContextOptions<TestDbContext> options):base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ApplyConfigurationsFromAssembly(this.GetType().Assembly); } }
- 創建一個 ExplortStatisticBgService 類並繼承 BackgroundService,這是托管服務類
using Microsoft.EntityFrameworkCore; using System.Text; public class ExplortStatisticBgService : BackgroundService { private readonly TestDbContext ctx; private readonly ILogger<ExplortStatisticBgService> logger; private readonly IServiceScope serviceScope; /// <summary> /// 在構造方法註入IServiceScopeFactory服務, /// 用來創建IServiceScope對象, /// 這樣就可以通過IServiceScope來創建短生命周期的服務了 /// </summary> /// <param name="scopeFactory"></param> public ExplortStatisticBgService(IServiceScopeFactory scopeFactory) { this.serviceScope = scopeFactory.CreateScope(); var sp = serviceScope.ServiceProvider; this.ctx = sp.GetRequiredService<TestDbContext>(); this.logger = sp.GetRequiredService<ILogger<ExplortStatisticBgService>>(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 用 while 迴圈實現服務常駐 while (!stoppingToken.IsCancellationRequested) { // 用 try...catch 捕捉異常記錄錯誤信息並避免方法退出 try { // 這裡實現每隔5秒從資料庫中導出數據 // 更複雜的配置可以用第三方開源的框架 await DoExecuteAsync(); await Task.Delay(5000); } catch (Exception ex) { logger.LogError(ex, "獲取用戶統計數據失敗"); await Task.Delay(1000); } } } private async Task DoExecuteAsync() { var items = ctx.Users.AsNoTracking().GroupBy(u => u.CreationTime.Date) .Select(e => new { Date = e.Key, Count = e.Count() }); StringBuilder sb = new StringBuilder(1024); sb.AppendLine($"Date: {DateTime.Now}"); foreach (var item in items) { sb.Append(item.Date).AppendLine($": {item.Count}"); } await File.WriteAllTextAsync("d:/1.txt", sb.ToString()); logger.LogInformation($"導出完成"); } /// <summary> /// IServiceScope 需要釋放 /// 所以重寫 Dispose 方法 /// </summary> public override void Dispose() { base.Dispose(); serviceScope.Dispose(); } }
- 打開 Program.cs,註入托管服務等,看代碼的註釋
using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); IServiceCollection services = builder.Services; // 註冊托管服務 services.AddHostedService<ExplortStatisticBgService>(); // 註入資料庫上下文 services.AddDbContext<TestDbContext>(options => { string connStr = builder.Configuration.GetConnectionString("Default")!; options.UseSqlServer(connStr); }); // 數據保護服務註入 // ----數據保護提供了一個簡單、基於非對稱加密改進的加密API用於確保Web應用敏感數據的安全存儲 // ----不需要開發人員自行生成密鑰,它會根據當前應用的運行環境,生成該應用獨有的一個私鑰 services.AddDataProtection(); // 註入 Identity 框架的一些重要的基礎配置 // 如果沒有這個,下麵的註入 UserManager 等服務會有問題,程式無法編譯 services.AddIdentityCore<User>(options => { options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequiredLength = 6; options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider; options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; }); // 註入 UserManager、RoleManager 等Identity 框架服務 var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services); idBuilder.AddEntityFrameworkStores<TestDbContext>() .AddDefaultTokenProviders() .AddRoleManager<RoleManager<Role>>() .AddUserManager<UserManager<User>>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); ``
- Ctrl+F5 運行項目,不做任何操作,托管程式會自動導出數據
擴展
托管服務在後臺運行,通過它可以實現在很多事情,比如:
- 監控消息隊列,當有數據進入消息隊列就處理。
- 再如每隔10s把A資料庫中的數據同步到B資料庫中
- ...... 等等