突然长大的孩子 发表于 2023-12-30 20:40:01

Asp.net core Webapi 如何执行定时任务?


前言

在计算机系统中,定时执行一些后台任务是很常见的场景,比如定时发送邮件、备份数据等等。
那么,.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数据库中
[*]...... 等等

来源:https://www.cnblogs.com/JackyGz/Undeclared/17928776.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Asp.net core Webapi 如何执行定时任务?