Net6 托管服务、FluentValidation

托管服务

1、场景,代码运行在后台。比如服务器启动的时候在后台预先加载数据到缓存,每天凌晨3点把数据导出到备份数据库,每隔5秒钟在两张表之间同步一次数据。
2、托管服务实现IHostedService接口,一般编写从BackgroundService继承的类。
测试:延迟若干秒再读取文件,再延迟,再输出。
3、services.AddHostedService< DemoBgService >();

4、从.NET 6开始,当托管服务中发生未处理异常的时候,程序就会自动停止并退出。可以把HostOptions.BackgroundServiceExceptionBehavior设置为Ignore,程序会忽略异常,而不是停止程序。不过推荐采用默认的设置,因为“异常应该被妥善的处理,而不是被忽略”。
5、要在ExecuteAsync方法中把代码用try……catch包裹起来,当发生异常的时候,记录日志中或发警报等。

在Program注入服务
builder.Services.AddHostedService<DemoBgService>();
DemoBgService
public class DemoBgService : BackgroundService
{
	private ILogger<DemoBgService> logger;
	public DemoBgService(ILogger<DemoBgService> logger)
	{
		this.logger = logger;
	}
	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		await Task.Delay(5000);
		string s = await File.ReadAllTextAsync("d:/1.txt");
		await Task.Delay(20000);
		logger.LogInformation(s);
	}
}

托管服务中使用DI

1、托管服务是以单例的生命周期注册到依赖注入容器中的。因此不能注入生命周期为范围或者瞬态的服务。
比如注入EF Core的上下文的话,程序就会抛出异常。
2、可以通过构造方法注入一个IServiceScopeFactory服务,它可以用来创建一个IServiceScope对象,这样我们就可以通过IServiceScope来创建短生命周期的服务了。记得在Dispose中释放IServiceScope。

核心代码如下
	private readonly TestDbContext ctx;
	private readonly ILogger<ExplortStatisticBgService> logger;
	private readonly IServiceScope serviceScope;
	public ExplortStatisticBgService(IServiceScopeFactory scopeFactory)
	{
        //用IServiceScopeFactory创建一个IServiceScope对象
		this.serviceScope = scopeFactory.CreateScope();
		var sp = serviceScope.ServiceProvider;
		this.ctx = sp.GetRequiredService<TestDbContext>();
		this.logger = sp.GetRequiredService<ILogger<ExplortStatisticBgService>>();
	}
//ExecuteAsync 方法里面按照上面的正常写逻辑即可

//override Dispose方法
public override void Dispose()
	{
		base.Dispose();
		serviceScope.Dispose();
	}

1、常驻后台的托管服务并不需要特殊的技术,我们只要让ExecuteAsync中的代码一直执行不结束就行了。
2、实现的功能就是每隔五秒钟对数据库中的数据做一下汇总,然后把汇总结果写入一个文本文件。

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		while (!stoppingToken.IsCancellationRequested)
		{
			try
			{
				await DoExecuteAsync();
				await Task.Delay(5000);
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "获取用户统计数据失败");
				await Task.Delay(1000);
			}
		}
	}
	private async Task DoExecuteAsync()
	{
		var items = ctx.Users.GroupBy(u => u.CreationTime.Date)
						.Select(e => new { Date = e.Key, Count = e.Count() });
		StringBuilder sb = new StringBuilder();
		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($"导出完成");
	}

如果是写定时服务每隔一段时间要执行什么,

Hangfire 定时服务框架 自己看资料(学完整个书籍后面再来)

参考博客:https://www.cnblogs.com/Can-daydayup/p/11610747.html

Net6内置的数据校验

内置数据校验机制

1、.NET Core中内置了对数据校验的支持,在System.ComponentModel.DataAnnotations这个命名空间下,比如[Required]、[EmailAddress] 、[RegularExpression]。CustomValidationAttribute、IValidatableObject。
2、演示其在ASP.NET Core中请求中的使用。
3、内置的校验机制的问题:校验规则都是和模型类耦合在一起,违反“单一职责原则”;很多常用的校验都需要编写自定义校验规则,而且写起来麻烦。

using System.ComponentModel.DataAnnotations;

public class Login1Request
{
	[Required]
	[EmailAddress]
	[RegularExpression("^.*@(qq|163)\\.com$", ErrorMessage = "只支持QQ和163邮箱")]
	public string Email { get; set; }
	[Required]
	[StringLength(10, MinimumLength = 3)]
	public string Password { get; set; }
	[Compare(nameof(Password2), ErrorMessage = "两次密码必须一致")]
	public string Password2 { get; set; }
}
FluentValidation的基本使用

1、FluentValidation:用类似于EF Core中Fluent API的方式进行校验规则的配置,也就是我们可以把对模型类的校验放到单独的校验类中。
2、 FluentValidation在ASP.NET Core项目中的用法 (NuGet:FluentValidation.AspNetCore)

Entity(为了方便写成record类型 打印测试也方便一些)
namespace FluentValidations;
{
    public record Login2Request(string Email, string Password, string Password2);
}
EntityValidation
using FluentValidation;

namespace FluentValidations;
public class Login2RequestValidator : AbstractValidator<Login2Request>
{
	public Login2RequestValidator()
	{
		RuleFor(x => x.Email).NotNull().EmailAddress()
			.Must(v => v.EndsWith("@qq.com") || v.EndsWith("@163.com"))
			.WithMessage("只支持QQ和163邮箱");
		RuleFor(x => x.Password).NotNull().Length(3, 10)
			.WithMessage("密码长度必须介于3到10之间")
 			.Equal(x => x.Password2).WithMessage("两次密码必须一致");
	}
}
Program 注入Validation
builder.Services.AddFluentValidation(fv => {
    Assembly assembly = Assembly.GetExecutingAssembly();
    fv.RegisterValidatorsFromAssembly(assembly);
});

FluentValidation + DI(可以在Validation通过DI使用对应的服务)

using FluentValidation;
using FluentValidations;

public class Login3RequestValidator : AbstractValidator<Login3Request>
{
	public Login3RequestValidator(TestDbContext dbCtx)
	{
		RuleFor(x => x.UserName).NotNull()
			.Must(name => dbCtx.Users.Any(u => u.UserName == name))
			.WithMessage(c => $"用户名{c.UserName}不存在");
	}
}

本文内容大部分都为杨中科老师《ASP.NET Core技术内幕与项目实战》一书中内容,此文只是做学习记录,如有侵权,联系立马删除。