目标是让网站在后台每月进行一次用户统计,考虑过直接在数据库操作,但是最后还是选择直接在程序内实现。

虽然功能是实现了,但是看过文档和其他解释后还没能理解,所以这里只写How不写Why。

环境

  • dotnet core 2.1

参考

步骤

官方一共写了三种后台任务用例,分别为Timed background tasks(定时任务)、scoped service background tasks(有作用域服务) 和 Queued background tasks(队列任务)。

对于定时向数据库写入内容,我的实现方式是 timed bg task + scoped service 结合,即在定时任务内执行有作用域的服务(数据库操作)。一开始尝试只用定时方式,但是数据库的上下文依赖注入一直失败,之后在StackOverflow上找到了需要实现一样功能的人,发现他的写法结合了两种方式,最后添加了scoped service方式后成功写入数据库。

  1. 首先实现scoped service,在类中添加接口,以及实现接口的类,在类中写入数据库相关代码
    public class DbOperation {
        internal interface IScopedProcessingService {
            void DoWork();
        }
    
        internal class ScopedProcessingService : IScopedProcessingService {
            private readonly TestDbContext db;
    
            public ScopedProcessingService(TestDbContext _db) {
                db = _db;
            }
    
            ... ...
    
            public void DoWork() {
                var user = new User() {
                    name = GetRandomName(),
                    age = GetRandomAge()
                };
    
                db.Users.Add(user);
                db.SaveChanges();
            }
        }
    }

    DbOperation中定义了接口IScopedProcessingService和接口实现ScopedProcessingServiceScopedProcessingService中实现了接口中声明的,向数据库写入新条目的方法DoWork()

  2. 新建定时任务类,并在定时执行的方法中调用数据库操作服务
    internal class WriteToDb : IHostedService, IDisposable {
        private readonly ILogger logger;
        private Timer timer;
    
        public WriteToDb(ILogger<WriteToDb> _logger, IServiceProvider services) {
            Services = services;
            logger = _logger;
        }
    
        public IServiceProvider Services { get; }
    
        public Task StartAsync(CancellationToken cancellationToken) {
            logger.LogInformation("Starting");
    
            timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromHours(1)); //新建定时任务
    
            return Task.CompletedTask;
        }
    
        public void DoWork(object state) { //数据库服务
            using (var scope = Services.CreateScope()) {
                var scopedProcessingService =
                    scope.ServiceProvider.GetRequiredService<DbOperation.IScopedProcessingService>();
    
                scopedProcessingService.DoWork(); //调用写入新条目的方法
            }
            logger.LogInformation("Insert New User");
        }
    
        public Task StopAsync(CancellationToken cancellationToken) {
            logger.LogInformation("Stopping");
    
            timer?.Change(Timeout.Infinite, 0);
    
            return Task.CompletedTask;
        }
    
        public void Dispose() {
            timer?.Dispose();
        }
    }

    IHostedService中,StartAsync()StopAsync() 是必须实现的方法。应用程序准备好时会触发 StartAsync(),在应用程序“优雅地”关闭(原文:graceful shutdown)时,会触发 StopAsync()

    通过修改新建定时器的最后一个参数(代码中为TimeSpan.FromHours(1)) 来改变任务执行的周期,修改第三个参数(代码中为TimeSpan.Zero)来更改程序启动后执行任务的延时——指定为TimeSpan.Zero则在程序启动后立刻执行一次任务。

  3. 在Startup.cs中依赖注入
    public void ConfigureServices(IServiceCollection services) {
        ... ...
    
        services.AddHostedService<WriteToDb>();
        services.AddScoped<DbOperation.IScopedProcessingService, DbOperation.ScopedProcessingService>();
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

Continue reading “ASP.NET Core 通过 IHostedService 实现后台任务运行”

有一视图模型——控制器先获取数据库中的信息,再将信息通过构造函数参数传递给这个视图模型,视图模型拿到数据后在类内生成对应的List<SelectItem>列表实例,这一顿操作的目的是在视图中生成对应本模型某属性的DropDownList

模型的构造函数一开始是这么写的:

但是在页面将信息post回控制器时,会报“无法将模型实例化”的错误:

InvalidOperationException: Could not create an instance of type ‘[我的模型]’. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the ‘[我的模型实例]’ parameter a non-null default value.

Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext bindingContext)

之后尝试过将列表直接给ViewBag,但是不能实现模型的绑定(假如数据库中用户的性别为男,在页面生成后DropDownList无法直接显示“男”),或许有其他方法(比如在Controller先对List进行IsSelected处理,但是比较繁琐),没有进行更多尝试(其实就是想一个模型就完事)。

之后在StackOverflow找到解决方法:Passing data into an Asp.Net Core bound model

解决方法很简单,将带参数的构造函数写为重载函数:

回答者的解释:

在模型绑定过程中,引擎不能识别带参数的构造函数

原文:during model binding engine can’t identify with parameter constructor.

在 MD5 Encryption in .NET/.NET Core 中写了一个MD5加密模块,在使用中访问加载了此模块的页面会立即报错“InvalidOperationException: Unable to resolve service for type”。

原因是我在Controller代码中使用了依赖注入的写法,但是Startup.cs中没有注入该模块。

参考

步骤

  1. 我的Controller代码:
    public class LoginController : Controller
    {
            //加密模块
            private readonly Encryption encryption;
            private readonly FamilySysDbContext db;
    
            public LoginController(FamilySysDbContext _db, Encryption _encryption)
            {
                db = _db;
                encryption = _encryption;
            }
                ... ...
    }
  2. 需要在Startup.cs中依赖注入该模块:
    public void ConfigureServices(IServiceCollection services) {
                    ... ...
        //加密模块
        services.AddScoped<Encryption>();
    
        ... ...
    
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }

有三种方法实现依赖注入,分别为AddTransient()AddSingleton()AddScoped()。关于它们的区别可见 asp.net core2.0 依赖注入 AddTransient与AddScoped的区别