服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - ASP.NET教程 - Asp.net core Webapi 如何执行定时任务?

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

2024-01-06 00:03未知服务器之家 ASP.NET教程

Asp.net core Webapi 有没有办法执行定时任务呢?答案是有的 前言 在计算机系统中,定时执行一些后台任务是很常见的场景,比如定时发送邮件、备份数据等等。 那么,.NET 技术如何通过编程灵活地实现项目里复杂的自定义任务呢?

Asp.net core Webapi 有没有办法执行定时任务呢?答案是有的

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

前言

在计算机系统中,定时执行一些后台任务是很常见的场景,比如定时发送邮件、备份数据等等。

那么,.NET 技术如何通过编程灵活地实现项目里复杂的自定义任务呢?

如果是 Windows 生态,通常来说,可以有这些方式:

  1. 编写一个程序,通过 Windows 内置的任务计划来定时执行。
  2. 编写一个程序,通过 Windows 内置的 Services 来定时执行。
  3. 编写一个定时循环执行任务的程序,在 Windows 系统启动时配置为自动执行。
    ……

但是,如果是一个中小型的 Web 应用系统,这些方法方式就显得不太合适。Asp.net core Webapi 有没有办法执行定时任务呢?答案是有的,Asp.net core Webapi 可以通过常驻后台的托管服务来执行定时任务。

本文是 Asp.net core Webapi 运行一个常驻后台并从数据库中导出数据的托管服务的例子,写出来供大家指点,在讨论过程*同提高水平。

Step By Step 实现步骤

  1. 创建一个 asp.net core webapi 项目
  2. 从 Nuget 安装以下包

    Microsoft.AspNetCore.Identity.EntityFrameworkCore
    Microsoft.EntityFrameworkCore.Relational
    Microsoft.EntityFrameworkCore.SqlServer
    Microsoft.EntityFrameworkCore.Tools

  3. 打开 appsettings.json 并添加数据库连接字符串,如:
    {
      "Logging": {
    	"LogLevel": {
    	  "Default": "Information",
    	  "Microsoft.AspNetCore": "Warning"
    	}
      },
      "AllowedHosts": "*",
      "ConnectionStrings": {
    	"Default": "Server=(localdb)\\mssqllocaldb;Database=IdentityTestDB;Trusted_Connection=True;MultipleActiveResultSets=true"
      }
    }
    
  4. 添加一个继承于 IdentityUser 的 User 类
    using Microsoft.AspNetCore.Identity;
    
    public class User: IdentityUser<long>
    {
    	public DateTime CreationTime { get; set; }
    	public string? NickName { get; set; }
    }	
    
  5. 添加一个继承于 IdentityRole 的 Role 类
    using Microsoft.AspNetCore.Identity;
    
    public class Role: IdentityRole<long>
    {
    
    }
    
  6. 创建数据库上下文
    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);
    	}
    }	
    
  7. 创建一个 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();
    	}
    }	
    
  8. 打开 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();
    ``
    
  9. Ctrl+F5 运行项目,不做任何操作,托管程序会自动导出数据

扩展

托管服务在后台运行,通过它可以实现在很多事情,比如:

  1. 监控消息队列,当有数据进入消息队列就处理。
  2. 再如每隔10s把A数据库中的数据同步到B数据库中
  3. ...... 等等

延伸 · 阅读

精彩推荐