【半小時大話.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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...