第四十六節:後臺託管服務(DI的兩種寫法)、數據校驗規則(內置、FluentValidation)、程序發佈部署

一. 託管服務

1. 簡介

使用背景:代碼運行在後臺。比如服務器啓動的時候在後臺預先加載數據到緩存,再比如定時任務凌晨1點需要遍歷數據庫修改狀態等等。

注意:

   常駐後臺的託管服務並不需要特殊的技術,我們只要while (!stoppingToken.IsCancellationRequested) 讓ExecuteAsync中的代碼一直執行不結束就行了, 但是不能部署在IIS上。

   因爲如果掛在IIS上,閒置超時20分鐘,是指20分鐘內沒有任何請求進行訪問,如果有請求則這個閒置超時時間會重新計算。如果場景是定時任務,且期間沒有請求,該方案不適合,

   因爲IIS會回收它,這一點類似Quartz.Net 部署在IIS上是一個道理的(可以用控制檯方案解決 或 其它部署方案解決)。

2.核心說明

  (1). 託管服務實現IHostedService接口,但我們通常用BackgroundService這個基類來做

3. 託管服務的異常處理

 (1).從.NET 6開始,當託管服務中發生未處理異常的時候,程序就會自動停止並退出(之前程序不會停止)。可以把HostOptions.BackgroundServiceExceptionBehavior設置爲Ignore,程序會忽略異常,而不是停止程序。 不過推薦採用默認的設置,因爲“異常應該被妥善的處理,而不是被忽略”。

 (2).通常建議在ExecuteAsync方法中把代碼用try-catch包裹起來,當發生異常的時候,記錄日誌中或發警報等。

 (3). 代碼實操:

      A. 後臺常駐服務,通過 while (!stoppingToken.IsCancellationRequested) 來判斷

      B. 業務代碼要try-catch包裹

      C. 通過 await base.StopAsync(stoppingToken); 停止後臺服務

      D. 服務註冊 builder.Services.AddHostedService<TestBackService1>();  【這是單例模式的注入】

後臺服務代碼

public class TestBackService1 : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("----------後臺任務開啓-------------------");
            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    //模擬實際業務,輸出當前時間
                    Console.WriteLine($"當前時間爲:{DateTime.Now}");

                    //等待5s
                    await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);

                    //測試報錯
                    //int.Parse("fdgdfg");

                }
                catch (Exception ex)
                {
                    Console.WriteLine($"出錯了:{ex.Message}");

                    //根據實際情況決定是否停止後臺任務
                    await base.StopAsync(stoppingToken);
                }
            }
        }
    }

註冊服務 

//註冊後臺服務
builder.Services.AddHostedService<TestBackService1>();

運行結果  

 

4. 託管服務中的DI

(1). 託管服務是以單例的生命週期註冊到依賴注入容器中的。因此不能注入請求內單例或者瞬時的服務。比如注入EF Core的上下文的話(默認是請求內單例的),程序就會拋出異常。

(2). 解決方案

   創建一個IServiceScope對象,這樣我們就可以通過IServiceScope來創建所需聲明週期的服務即可

   這裏通常有兩種寫法,

         要麼直接在ExecuteAsync中的using中構建出來所需聲明週期的服務  【詳見代碼版本2】

         要麼在構造函數中創建出來所需聲明週期的服務(需要dispose一下) 【詳見代碼版本3】

(3). 代碼實操

   A.新建EF上下午MyDBContext, 然後 builder.Services.AddScoped<MyDbContext>();  請求內單例的

   B.在後臺服務中TestBackServiceDI中注入MyDBContext。 運行:直接報錯,錯誤內容如下圖,大體意思:不同生命週期的內容不能相互注入使用 【詳見版本1代碼】

代碼分享:

 public class TestBackServiceDI : BackgroundService
    {
        #region 版本1--構造函數注入MyDbContext【報錯】

        private readonly MyDbContext dbContext;
        public TestBackServiceDI(MyDbContext dbContext)
        {
            this.dbContext = dbContext;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("----------後臺任務開啓-------------------");

            while (!stoppingToken.IsCancellationRequested)
            {

                try
                {
                    //調用EF上下午
                    string result = dbContext.GetMsg();
                    Console.WriteLine(result);

                }
                catch (Exception ex)
                {
                    Console.WriteLine($"出錯了:{ex.Message}");

                    //根據實際情況決定是否停止後臺任務
                    await base.StopAsync(stoppingToken);
                }
            }
        }
        #endregion
    }

報錯:

 

   C.注入IServiceProvider service

   D.在ExecuteAsync中通過using+ serviceScope.ServiceProvider.GetRequiredService<MyDbContext>(); 創建dbContext即可,運行代碼,直接輸出Hello world 【詳見代碼版本2】

   E.詳見代碼版本3

解決方案1代碼 【推薦】

  public class TestBackServiceDI : BackgroundService
    {
        #region 版本2-通過service.CreateScope()創建

        private readonly IServiceProvider service;
        public TestBackServiceDI(IServiceProvider service)
        {
            this.service = service;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("----------後臺任務開啓-------------------");

            while (!stoppingToken.IsCancellationRequested)
            {

                try
                {
                    using (IServiceScope serviceScope = service.CreateScope())
                    {
                        var dbContext = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();
                        //調用EF上下午
                        string result = dbContext.GetMsg();
                        Console.WriteLine(result);
                        await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"出錯了:{ex.Message}");

                    //根據實際情況決定是否停止後臺任務
                    await base.StopAsync(stoppingToken);
                }
            }
        }
        #endregion

    }

解決方案2代碼

  public class TestBackServiceDI : BackgroundService
    {
        #region 版本3-通過構造函數中創建符合生命期的EF上下文

        private readonly IServiceScope serviceScope;
        private readonly MyDbContext dbContext;
        public TestBackServiceDI(IServiceProvider service)
        {
            this.serviceScope = service.CreateScope();
            this.dbContext = serviceScope.ServiceProvider.GetRequiredService<MyDbContext>();

        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            Console.WriteLine("----------後臺任務開啓-------------------");

            while (!stoppingToken.IsCancellationRequested)
            {

                try
                {
                    //調用EF上下午
                    string result = dbContext.GetMsg();
                    Console.WriteLine(result);
                    await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);

                }
                catch (Exception ex)
                {
                    Console.WriteLine($"出錯了:{ex.Message}");

                    //根據實際情況決定是否停止後臺任務
                    await base.StopAsync(stoppingToken);
                }
            }
        }

        public override void Dispose()
        {
            base.Dispose();
            serviceScope.Dispose();
        }
        #endregion

    }

 

 

 

二. 內置數據校驗

 

 

 

 

 

 

三. FluentValidition

 

 

 

 

 

 

四. 程序發佈部署

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章