【半小時大話.net依賴註入】(一)理論基礎+實戰控制台程式實現AutoFac註入

来源:https://www.cnblogs.com/RayWang/archive/2019/07/08/11128554.html
-Advertisement-
Play Games

系列目錄 1. "第一章|理論基礎+實戰控制台程式實現AutoFac註入" 1. 第二章|AutoFac的常見使用套路 1. 第三章|實戰Asp.Net Framework Web程式實現AutoFac註入 1. 第四章|實戰Asp.Net Core自帶DI實現依賴註入 1. 第五章|實戰Asp.N ...


系列目錄

  1. 第一章|理論基礎+實戰控制台程式實現AutoFac註入

  2. 第二章|AutoFac的常見使用套路

  3. 第三章|實戰Asp.Net Framework Web程式實現AutoFac註入

  4. 第四章|實戰Asp.Net Core自帶DI實現依賴註入

  5. 第五章|實戰Asp.Net Core引入AutoFac的兩種方式

說明

簡介

該系列共5篇文章,旨在以實戰模式,在.net下的

  • 控制台程式

  • Framework Mvc程式

  • Framework WebApi程式

  • Core Api程式

分別實現依賴註入。

其中.Net Framework框架主要以如何引入AutoFac作為容器以及如何運用AuotoFac為主,.Net Core框架除了研究引入AutoFac的兩種方式,同時也運用反射技巧對其自帶的DI框架進行了初步封裝,實現了相同的依賴註入效果。
項目架構如下圖:

項目 名稱 類型 框架
Ray.EssayNotes.AutoFac.Infrastructure.CoreIoc Core容器 類庫 .NET Core 2.2
Ray.EssayNotes.AutoFac.Infrastructure.Ioc Framework容器 類庫 .NET Framework 4.5
Ray.EssayNotes.AutoFac.Model 實體層 類庫 .NET Framework 4.5
Ray.EssayNotes.AutoFac.Repository 倉儲層 類庫 .NET Framework 4.5
Ray.EssayNotes.AutoFac.Service 業務邏輯層 類庫 .NET Framework 4.5
Ray.EssayNotes.AutoFac.ConsoleApp 控制台主程式 控制台項目 .NET Framework 4.5
Ray.EssayNotes.AutoFac.CoreApi Core WebApi主程式 Core Api項目 .NET Core 2.2
Ray.EssayNotes.AutoFac.NetFrameworkApi Framework WebApi主程式 Framework WebApi項目 .NET Framework 4.5
Ray.EssayNotes.AutoFac.NetFrameworkMvc Framework MVC主程式 Framework MVC項目 .NET Framework 4.5

GitHub源碼地址:https://github.com/WangRui321/Ray.EssayNotes.AutoFac

Welcome to fork me~(歡迎來叉我~)

適用對象

該項目主要實戰為主,理論部分我會結合例子和代碼,深入淺出地闡述,如果你是:

  • 從來沒聽過IoC、DI這些勞什子
  • 瞭解一些依賴註入的理論知識但是缺乏實戰
  • 在.Net Framework下已熟練運用依賴註入,但在.Net Core還比較陌生

只要你花上半個小時認真讀完每一句話,我有信心這篇文章一定會對你有所幫助。

如果你是:

  • 發量比我還少的秒天秒地的大牛

那麼也歡迎閱讀,雖然可能對你幫助並不大,但是歡迎提供寶貴的意見,有寫的不好的地方可以互相交流~

下麵開始第一章《理論知識+實戰控制台程式實現AutoFac註入》


理論基礎

依賴

依賴,簡單說就是,當一個類需要另一個類協作來完成工作的時候就產生了依賴。這也是耦合的一種形式。

舉個例子,比如標準的三層架構模式

名稱 職責 舉例
界面層(UI) 負責展示數據 StudentController
業務邏輯層(BLL) 負責業務邏輯運算 StudentService
數據訪問層(DAL) 負責提供數據 StudentRepository

數據訪問層(DAL)代碼:

    /// <summary>
    /// 學生倉儲
    /// </summary>
    public class StudentRepository
    {
        public string GetName(long id)
        {
            return "學生張三";//造個假數據返回
        }
    }

業務層(BLL)代碼:

    /// <summary>
    /// 學生邏輯處理
    /// </summary>
    public class StudentService
    {
        private readonly StudentRepository _studentRepository;

        public StudentService()
        {
            _studentRepository = new StudentRepository();
        }

        public string GetStuName(long id)
        {
            var stu = _studentRepository.Get(id);
            return stu.Name;
        }
    }

其中,StudentService的實現,就必須要依賴於StudentRepository。而且這是一種緊耦合,一旦StudentRepository有任何更改,必然導致StudentService的代碼同樣也需要更改,這種情況是程式員們不願意看到的。

介面驅動

介面驅動是為了實現一個設計原則:要依賴於抽象,而不是具體的實現
還拿上面的例子說明,現在我添加一個DAL的介面層,IStudentRepository,抽象出所需方法:

    /// <summary>
    /// 學生倉儲interface
    /// </summary>
    public interface IStudentRepository
    {
        string GetName(long id);
    }

然後讓StudentRepository去實現這個介面:

    /// <summary>
    /// 學生倉儲
    /// </summary>
    public class StudentRepository : IStudentRepository
    {
        public string GetName(long id)
        {
            return "學生張三";//造個假數據返回
        }
    }

然後在StudentService里只依賴於IStudentRepository,以後的增刪改查都通過IStudentRepository這個抽象來做:

    /// <summary>
    /// 學生邏輯處理
    /// </summary>
    public class StudentService
    {
        private readonly IStudentRepository _studentRepository;

        public StudentService()
        {
            _studentRepository = new StudentRepository();
        }

        public string GetStuName(long id)
        {
            var stu = _studentRepository.Get(id);
            return stu.Name;
        }
    }

這樣做的好處有兩個,一個是低耦合,一個是職責清晰。如果對此還有懷疑的話,我們可以想象一個情景,就是負責寫StudentService的是程式員A,負責寫StudentRepository的是另一個程式員B,那麼:

  • 針對程式員A

我(程式員A)只需要關註業務邏輯層面,如果我需要從倉儲層拿資料庫的數據,比如我需要根據Id獲取學生實體,那麼我只需要去IStudentRepository找Get(long id)函數就可以了,至於實現它的倉儲怎麼實現這個方法我完全不用管,你怎麼從資料庫拿數據不是我該關心的事情。

  • 針對程式員B

我(程式員B)的工作就是實現IStudentRepository介面的所有方法就行了,簡單而明確,至於誰來調用我,我不用管。IStudentRepository里有根據Id獲取學生姓名的方法,我實現了就行,至於業務邏輯層拿這個名字幹啥,那不是我要關心的事情。

這樣看的話是不是彼此的職責就清晰多了,更進一步再舉個極端的例子:
比如程式員B是個實習生,整天划水摸魚,技術停留在上個世紀,結果他寫的倉儲層讀取資料庫全部用的手寫sql語句的方式,極難維護,後來被領導發現領了盒飯,公司安排了另一個程式員C來重寫倉儲層,C這時不需要動其他代碼,只需要新建一個倉儲StudentNewRepository,然後實現之前的IStudentRepository,C使用Dapper或者EF,寫完新的倉儲層之後,剩下的只需要在StudentService里改一個地方就行了:

        public StudentService()
        {
            _studentRepository = new StudentNewRepository();
        }

是不是很清晰,耦合不會像以前那麼重。
其實對於這個小例子來說,介面驅動的優勢還不太明顯,但是在系統層面優勢就會被放大。比如上面換倉儲的例子,雖然職責是清晰了,但是項目里有幾個Service就需要改幾個地方,還是很麻煩。原因就是上面講的,這是一種依賴關係,Service要依賴Repository,有沒有一種方法可以讓這種控制關係反轉過來呢?當Service需要使用Repository,有沒有辦法讓我需要的Repository自己註入到我這裡來?
當然有,這就是我們將要實現的依賴註入。使用依賴註入後你會發現,當C寫完新的倉儲後,業務邏輯層(StudentService)是不需要改任何代碼的,所有的Service都不需要一個一個去改,直接在註入的時候修改規則,不要註入以前老的直接註入新的倉儲就可以了。

面向介面後的架構:

名稱 職責 舉例
界面層(UI) 負責展示數據 StudentController
業務邏輯抽象層(InterfaceBLL) 業務邏輯運算抽象介面 IStudentService
業務邏輯層(BLL) 負責業務邏輯運算 StudentService
數據訪問抽象層(InterfaceDAL) 數據訪問抽象介面 IStudentRepository
數據訪問層(DAL) 負責提供數據 StudentRepository

什麼是IoC

IoC,全稱Inversion of Control,即“控制反轉”,是一種設計原則,最早由Martin Fowler提出,因為其理論提出時間和成熟時間相對較晚,所以並沒有被包含在GoF的《設計模式》中。

什麼是DI

DI,全稱Dependency Injection,即依賴註入,是實現IoC的其中一種設計方法。
其特征是通過一些技巧,將依賴的對象註入到調用者當中。(比如把Repository註入到Service當中)
這裡說的技巧目前主要指的就是引入容器,先把所有會產生依賴的對象統一添加到容器當中,比如StudentRepository和StudentService,把分配許可權交給容器,當StudentService內部需要使用StudentRepository時,這時不應該讓它自己new出來一個,而是通過容器,把StudentRepository註入到StudentService當中。
這就是名稱“依賴註入”的由來。

DI和IoC有什麼區別

這是個老生常談的問題了,而且這兩個名字經常在各種大牛和偽大牛的吹逼現場頻繁出現 ,聽的新手雲里霧裡,莫名感到神聖不可侵犯。那麼DI和IoC是同一個東西嗎?如果不是,它們又有什麼區別呢?
回答很簡單:不是一個東西
區別也很簡單,一句話概括就是:IoC是一種很寬泛的理念,DI是實現了IoC的其中一種方法
說到這裡我已經感覺到屏幕後的你性感地添了一下嘴唇,囤積好口水,準備開始噴我了。
先別慌,我有證據,我們先來看下微軟怎麼說:

ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies.

地址:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2

翻譯過來就是“ASP.NET Core支持依賴註入(DI)的軟體設計模式,該模式是一種在類和它依賴的對象之間實現了控制反轉(IoC)的技術”。

如果有人覺得辣雞微軟不夠權威,那我們去看下IoC以及DI這兩個概念的發明人——Martin Fowler怎麼說:

幾位輕量級容器的作者曾驕傲地對我說:這些容器非常有用,因為它們實現了控制反轉。這樣的說辭讓我深感迷惑:控制反轉是框架所共有的特征,如果僅僅因為使用了控制反轉就認為這些輕量級容器與眾不同,就好象在說我的轎車是與眾不同的,因為它有四個輪子。
因此,我想我們需要給這個模式起一個更能說明其特點的名字——”控制反轉”這個名字太泛了,常常讓人有些迷惑。經與多位IoC 愛好者討論之後,我們決定將這個模式叫做”依賴註入”(Dependency Injection)。

地址:http://insights.thoughtworkers.org/injection/

Martin Fowler說的比較委婉,其實說白了就是建議我們,不要亂用IoC裝逼,IoC是一種設計理念,很寬泛,你把程式里的一個寫死的變數改成從配置文件里讀取也是一種控制反轉(由程式控制反轉為由框架控制),你把這個配置改成用戶UI界面的一個輸入文本框由用戶輸入也是一種控制反轉(由框架控制反轉為由用戶自己控制
所以,如果確定討論的模式是DI,那麼就表述為DI,還是儘量少用IoC這種寬泛的表達。

AutoFac

AutoFac是一個開源的輕量級的DI容器,也是.net下最受大家歡迎的實現依賴註入的工具之一,通過AutoFac我們可以很方便的實現一些DI的騷操作。

實戰控制台程式依賴註入

目標很簡單,就是控制台程式啟動後,將學生姓名列印出來。
程式啟動流程是,控制台主程式調用Service層,Service層調用Repository層獲取數據(示例項目的倉儲層沒有連接資料庫,只是直接造個假數據返回)。
沒有依賴註入的情況下,肯定是主程式會new一個StudentService,StudentService里會new一個StudentRepository,現在引入依賴註入後,就不應該這麼new出來了,而是通過容器註入,也就是容器會把StudentRepository自動註入到StudentService當中。

架構

實體層

學生實體類StudentEntity:

namespace Ray.EssayNotes.AutoFac.Model
{
    /// <summary>學生實體</summary>
    public class StudentEntity
    {
        /// <summary>唯一標識</summary>
        public long Id { get; set; }
        /// <summary>姓名</summary>
        public string Name { get; set; }
        /// <summary>成績</summary>
        public int Grade { get; set; }
    }
}

倉儲層

IStudentRepository介面:

using Ray.EssayNotes.AutoFac.Model;

namespace Ray.EssayNotes.AutoFac.Repository.IRepository
{
    /// <summary>學生倉儲interface</summary>
    public interface IStudentRepository
    {
        string GetName(long id);
    }
}

StudentRepository倉儲類:

using Ray.EssayNotes.AutoFac.Model;
using Ray.EssayNotes.AutoFac.Repository.IRepository;

namespace Ray.EssayNotes.AutoFac.Repository.Repository
{
    /// <summary>
    /// 學生倉儲
    /// </summary>
    public class StudentRepository : IStudentRepository
    {
        public string GetName(long id)
        {
            return "學生張三";//造個假數據返回
        }
    }
}

Service層

IStudentService介面

namespace Ray.EssayNotes.AutoFac.Service.IService
{
    /// <summary>
    /// 學生邏輯處理interface
    /// </summary>
    public interface IStudentService
    {
        string GetStuName(long id);
    }
}

StudentService類:

using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Service.IService;

namespace Ray.EssayNotes.AutoFac.Service.Service
{
    /// <summary>
    /// 學生邏輯處理
    /// </summary>
    public class StudentService : IStudentService
    {
        private readonly IStudentRepository _studentRepository;
        /// <summary>
        /// 構造註入
        /// </summary>
        /// <param name="studentRepository"></param>
        public StudentService(IStudentRepository studentRepository)
        {
            _studentRepository = studentRepository;
        }

        public string GetStuName(long id)
        {
            var stu = _studentRepository.Get(id);
            return stu.Name;
        }
    }
}

其中構造函數是一個有參的函數,參數是學生倉儲,這個後面依賴註入時會用。

AutoFac容器

需要先通過Nuget導入Autofac包:

using System;
using System.Reflection;
//
using Autofac;
using Autofac.Core;
//
using Ray.EssayNotes.AutoFac.Repository.IRepository;
using Ray.EssayNotes.AutoFac.Repository.Repository;
using Ray.EssayNotes.AutoFac.Service.IService;
using Ray.EssayNotes.AutoFac.Service.Service;

namespace Ray.EssayNotes.AutoFac.Infrastructure.Ioc
{
    /// <summary>
    /// 控制台程式容器
    /// </summary>
    public static class Container
    {
        /// <summary>
        /// 容器
        /// </summary>
        public static IContainer Instance;

        /// <summary>
        /// 初始化容器
        /// </summary>
        /// <returns></returns>
        public static void Init()
        {
            //新建容器構建器,用於註冊組件和服務
            var builder = new ContainerBuilder();
            //自定義註冊
            MyBuild(builder);
            //利用構建器創建容器
            Instance = builder.Build();
        }

        /// <summary>
        /// 自定義註冊
        /// </summary>
        /// <param name="builder"></param>
        public static void MyBuild(ContainerBuilder builder)
        {
            builder.RegisterType<StudentRepository>().As<IStudentRepository>();
            builder.RegisterType<StudentService>().As<IStudentService>();
        }
    }
}

其中:

  • public static IContainer Instance
    為單例容器
  • Init()方法
    用於初始化容器,即往容器中添加對象,我們把這個添加的過程稱為註冊(Register)。
    ContainerBuilder為AutoFac定義的容器構造器,我們通過使用它往容器內註冊對象。
  • MyBuild(ContainerBuilder builder)方法
    我們具體註冊的實現函數。RegisterType是AutoFac封裝的一種最基本的註冊方法,傳入的泛型(StudentService)就是我們欲添加到容器的對象;As函數負責綁定註冊對象的暴露類型,一般是以其實現的介面類型暴露,這個暴露類型是我們後面去容器內查找對象時使用的搜索標識,我們從容器外部只有通過暴露類型才能找到容器內的對象。

    主程式

需要先Nuget導入AutoFac程式包:

using System;
//
using Autofac;
//
using Ray.EssayNotes.AutoFac.Infrastructure.Ioc;
using Ray.EssayNotes.AutoFac.Service.IService;


namespace Ray.EssayNotes.AutoFac.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Container.Init();//初始化容器,將需要用到的組件添加到容器中

            PrintStudentName(10001);

            Console.ReadKey();
        }

        /// <summary>
        /// 輸出學生姓名
        /// </summary>
        /// <param name="id"></param>
        public static void PrintStudentName(long id)
        {
            //從容器中解析出對象
            IStudentService stuService = Container.Instance.Resolve<IStudentService>();
            string name = stuService.GetStuName(id);
            Console.WriteLine(name);
        }
     }
 }

進入Main函數,先調用容器的初始化函數,該函數執行成功後,StudentRepository和StudentService就被註冊到容器中了。
然後調用列印學生姓名的函數,其中Resolve()方法是AutoFac封裝的容器的解析方法,傳入的泛型就是之前註冊時的暴露類型,下麵可以詳細看下這一步到底發生了哪些事情:

  • 容器根據暴露類型解析對象

也就是容器會根據暴露類型IStudentService去容器內部找到其對應類(即StudentService),找到後會試圖實例化一個對象出來。

  • 實例化StudentService

AutoFac容器在解析StudentService的時候,會調用StudentService的構造函數進行實例化。

  • 構造註入

AutoFac容器發現StudentService的構造函數需要一個IStudnetRepository類型的參數,於是會自動去容器內尋找,根據這個暴露類型找到對應的StudnetRepository後,自動將其註入到了StudentService當中

經過這幾步,一個簡單的基於依賴註入的程式就完成了。

結果

我們將控制台程式設置為啟動項目,點擊運行,如圖調用成功:

如果把調試斷點加在容器初始化函數里,可以很清晰的看到哪些對象被註冊到了容器里:

補充

使用控制台程式本來是為了突出容器的概念,但是容易造成一些誤解,DI的最終形態可以參考源碼里的Api項目和MVC項目,本來想循序漸進,先第一章控制台引入容器的概念,然後第二章講批量註冊、註入泛型、生命周期域管理,第三章講Api和MVC項目,最後兩章講下.net core的DI,但是這裡還是先說下吧:

  • 誤解1:每次添加Service和Repository都要去註冊,不是更麻煩?

其實是不需要一個一個註冊的,運用批量註冊後容器內部的代碼是這樣的,可以直接批量註冊所有的:

    /// <summary>
    /// .net framework MVC程式容器
    /// </summary>
    public static class MvcContainer
    {
        public static IContainer Instance;

        /// <summary>
        /// 初始化容器
        /// </summary>
        /// <param name="func"></param>
        /// <returns></returns>
        public static void Init(Func<ContainerBuilder, ContainerBuilder> func = null)
        {
            //新建容器構建器,用於註冊組件和服務
            var builder = new ContainerBuilder();
            //註冊組件
            MyBuild(builder); 
            func?.Invoke(builder);
            //利用構建器創建容器
            Instance = builder.Build();

            //將AutoFac設置為系統DI解析器
            System.Web.Mvc.DependencyResolver.SetResolver(new AutofacDependencyResolver(Instance));
        }

        public static void MyBuild(ContainerBuilder builder)
        {
            Assembly[] assemblies = Helpers.ReflectionHelper.GetAllAssembliesWeb();

            //批量註冊所有倉儲 && Service
            builder.RegisterAssemblyTypes(assemblies)//程式集內所有具象類(concrete classes)
                .Where(cc => cc.Name.EndsWith("Repository") |//篩選
                             cc.Name.EndsWith("Service"))
                .PublicOnly()//只要public訪問許可權的
                .Where(cc => cc.IsClass)//只要class型(主要為了排除值和interface類型)
                .AsImplementedInterfaces();//自動以其實現的所有介面類型暴露(包括IDisposable介面)

            //註冊泛型倉儲
            builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>));

            //註冊Controller
            Assembly mvcAssembly = assemblies.FirstOrDefault(x => x.FullName.Contains(".NetFrameworkMvc"));
            builder.RegisterControllers(mvcAssembly);
        }
    }

誤解2:每次使用都要解析下,還不如直接new
好吧,其實也是不需要自己去解析的,最終形態的Controller入口是這樣的,直接在構造函數里寫就行了:

    public class StudentController : Controller
    {
        private readonly IStudentService _studentService;
        public StudentController(IStudentService studentService)
        {
            _studentService = studentService;
        }

        /// <summary>
        /// 獲取學生姓名
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public string GetStuNameById(long id)
        {
            return _studentService.GetStuName(id);
        }
    }

就是直接在構造函數里註入就可以了。

  • 誤解3:依賴註入是不是過度設計?

首先DI是一個設計模式(design pattern),其本身完全不存在過不過度的問題,這完全取決於用的人和怎麼用。
另外,在.NET Core中,DI被提到了一個很重要的地位,如果想要瞭解.NET Core,理解DI是必不可少的。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 引子 上一節中我們知道GIL鎖將導致CPython中多線程無法並行執行,只能併發的執行。 而併發實現的原理是切換+保存,那就意味著使用多線程實現併發,就需要為每一個任務創建一個線程,必然增加了線程創建銷毀與切換的帶來的開銷 明顯的問題就是,高併發情況下,由於任務數量太多導致無法開啟新的線程,使得即沒 ...
  • 本文是一篇《Java 8實戰》的閱讀筆記,閱讀大約需要5分鐘。 有點標題黨,但是這確實是我最近使用Lambda表達式的感受。設計模式是過去的一些好的經驗和套路的總結,但是好的語言特性可以讓開發者不去考慮這些設計模式。面向對象常見的設計模式有策略模式、模板方法、觀察者模式、責任鏈模式以及工廠模式,使用 ...
  • 最近遇到一個問題,就是要對一個vector的變數設置空的參數預設值,剛開始寫NULL,發現不行,後來再網上查了一下,可以通過在外部設置一個變數,來為它賦值為空 運行結果: ...
  • 一、Scala環境基礎 Scala對Java相關的類,介面進行了包裝,所以依賴Jvm環境。 二、配置Scala解壓版 1)註意路徑無空格和中文 2)配置環境變數 添加到path目錄 3)檢測是否安裝 配置成功,沒錯就是這麼簡單。 3、配置Idea開發 1)插件安裝,就是點點點 2)新建兩個maven ...
  • 問題描述: 假設需要生成前N個自然數的一個隨機置換。例如,{4,3,1,5,2}和{3,1,4,2,5}就是合法的置換,但{5,4,1,2,1}卻不是,因為數1出現兩次而數3卻沒有。這個程式常常用於模擬一些演算法。我們假設存在一個隨機數生成器RandInt(i,j),它以相同的概率生成i和j之間的一個 ...
  • 捕捉信號 Go // 運行此程式,控制台將列印"Waiting for signal" // 按Ctrl + C 發送信號以關閉程式,將發生中斷 // 隨後控制台依次列印"Signal .."、"Exiting..." package main import ( "os" "os/signal" " ...
  • 客戶端與服務端多功能傳輸小程式 server.py ...
  • 擁抱開源的腳步,我們從來都是一直在路上;.NETCore作為後起之秀,帶給我們太多的驚喜和感動;但是也正是由於年輕,.NETCore 的生態還是不夠完善,這就非常需要我們社區的力量,需要大家一起參與,把開源社區好的工具、組件、應用接入到 .NETCore 應用中。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...