在前面隨筆,我們介紹過這個基於SqlSugar的開發框架,我們區分Interface、Modal、Service三個目錄來放置不同的內容,其中Modal是SqlSugar的對映實體,Interface是定義存取介面,Service是提供具體的資料操作實現。在Service層中,往往除了本身的一些增刪改查等處理操作外,也需要涉及到相關業務的服務介面,這些服務介面我們通過利用.net 的介面注入方式,實現IOC控制反轉的處理的。
如下面的VS中的專案服務層,包含很多業務表的服務介面實現,如下所示。
我們以其中簡單的Customer業務表為例,它的服務類程式碼如下所示(主要關注服務類的定義即可)。
/// <summary> /// 客戶資訊應用層服務介面實現 /// </summary> public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService { ............... }
它除了在泛型約束中增加SqlSugar實體類,主鍵型別,分頁條件物件外,還繼承介面 ICustomerService ,這個介面就是我們實現IOC的第一步,服務層繼承指定的介面實現,對我們實現IOC控制反轉提供便利。
/// <summary> /// 客戶資訊服務介面 /// </summary> public interface ICustomerService : IMyCrudService<CustomerInfo, string, CustomerPagedDto>, ITransientDependency { }
這個客戶資訊業務處理,是比較典型的單表處理案例,它沒有涉及到相關服務介面的整合,如果我們在其中服務介面中需要呼叫其他服務介面,那麼我們就需要通過建構函式注入介面物件的方式獲得物件的範例,如下我們說介紹的就是服務呼叫其他相關介面的實現。
如對於角色服務介面來說,它往往和使用者、機構有關係,因此我們在角色的服務介面層,可以整合使用者、機構的對應服務介面,如下程式碼所示。
/// <summary> /// 角色資訊 應用層服務介面實現 /// </summary> public class RoleService : MyCrudService<RoleInfo,int, RolePagedDto>, IRoleService { private IOuService _ouService; private IUserService _userService; /// <summary> /// 預設建構函式 /// </summary> /// <param name="ouService">機構服務介面</param> /// <param name="userService">使用者服務介面</param> public RoleService(IOuService ouService, IUserService userService) { this._ouService = ouService; this._userService = userService; } }
通過建構函式的注入,我們就可以獲得對應介面實現的範例,進行呼叫它的服務層方法使用了。
這樣我們在角色的服務介面實現中,就可以呼叫其他如使用者、機構相關的服務介面了。
其他模組的處理方式也是類似,如字典專案中,使用字典型別的服務介面。
/// <summary> /// 應用層服務介面實現 /// </summary> public class DictDataService : MyCrudService<DictDataInfo, string, DictDataPagedDto> , IDictDataService { /// <summary> /// 測試字典型別介面 /// </summary> protected IDictTypeService _dictTypeService; /// <summary> /// 注入方式獲取介面 /// </summary> /// <param name="dictTypeService">字典型別處理</param> public DictDataService(IDictTypeService dictTypeService) { this._dictTypeService = dictTypeService; } }
這裡值得注意的是,由於介面層是同級物件,因此要避免介面的相互參照而導致出錯,依賴關係要清晰,才不會發生這個情況。
在服務層中,我們是通過引數化建構函式的方式,引入對應的介面的,這個操作方式是建構函式的注入處理。
不過在此之前,我們需要在.net 的內建IOC容器中註冊對應的介面範例,否則引數化建構函式會因為找不到介面範例而出錯。
.net 的內建Ioc容器及註冊處理,我們需要在nuget引入下面兩個參照。
1、Microsoft.Extensions.DependencyInjection 2、Microsoft.Extensions.DependencyInjection.Abstractions
.net 中 負責依賴注入和控制反轉的核心元件有兩個:IServiceCollection和IServiceProvider。其中,IServiceCollection負責註冊,IServiceProvider負責提供範例。
在註冊介面和類時,IServiceCollection
提供了三種註冊方法,如下所示:
1、services.AddTransient<IDictDataService, DictDataService>(); // 瞬時生命週期 2、services.AddScoped<IDictDataService, DictDataService>(); // 域生命週期 3、services.AddSingleton<IDictDataService, DictDataService>(); // 全域性單例生命週期
如果使用AddTransient
方法註冊,IServiceProvider
每次都會通過GetService
方法建立一個新的範例;
如果使用AddScoped
方法註冊, 在同一個域(Scope
)內,IServiceProvider
每次都會通過GetService
方法呼叫同一個範例,可以理解為在區域性實現了單例模式;
如果使用AddSingleton
方法註冊, 在整個應用程式生命週期內,IServiceProvider
只會建立一個範例。
我們為了在註冊的時候方便通過遍歷方式處理介面範例的註冊,因此我們根據這幾種關係定義了幾個基礎類別介面,便於根據特定的介面方式來構建介面範例。
namespace WHC.Framework.ControlUtil { //用於定義這三種生命週期的標識介面 /// <summary> /// 三種標識介面的基礎類別介面 /// </summary> public interface IDependency { } /// <summary> /// 瞬時(每次都重新範例) /// </summary> public interface ITransientDependency : IDependency { } /// <summary> /// 單例(全域性唯一) /// </summary> public interface ISingletonDependency : IDependency { } /// <summary> /// 一個請求內唯一(執行緒內唯一) /// </summary> public interface IScopedDependency : IDependency { } }
這樣我們在定義註冊型別的時候,通過它的介面指定屬於上面那種型別。如對於字典專案的服務層,我們約定採用瞬時的註冊方式,那麼它的介面定義如下所示。
/// <summary> /// 字典專案服務介面 /// </summary> public interface IDictDataService : IMyCrudService<DictDataInfo, string, DictDataPagedDto>, ITransientDependency { }
設定自動註冊介面的時候,我們新增如下函數處理即可。
/// <summary> /// 設定依賴注入物件 /// </summary> /// <param name="services"></param> public static void ConfigureRepository(IServiceCollection services) { #region 自動注入對應的服務介面 //services.AddSingleton<IDictDataService, DictDataService>();//services.AddScoped<IUserService, UserService>(); var baseType = typeof(IDependency); var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; var getFiles = Directory.GetFiles(path, "*.dll").Where(Match); //.Where(o=>o.Match()) var referencedAssemblies = getFiles.Select(Assembly.LoadFrom).ToList(); //.Select(o=> Assembly.LoadFrom(o)) var ss = referencedAssemblies.SelectMany(o => o.GetTypes()); var types = referencedAssemblies .SelectMany(a => a.DefinedTypes) .Select(type => type.AsType()) .Where(x => x != baseType && baseType.IsAssignableFrom(x)).ToList(); var implementTypes = types.Where(x => x.IsClass).ToList(); var interfaceTypes = types.Where(x => x.IsInterface).ToList(); foreach (var implementType in implementTypes) { if (typeof(IScopedDependency).IsAssignableFrom(implementType)) { var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType)); if (interfaceType != null) services.AddScoped(interfaceType, implementType); } else if (typeof(ISingletonDependency).IsAssignableFrom(implementType)) { var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType)); if (interfaceType != null) services.AddSingleton(interfaceType, implementType); } else { var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType)); if (interfaceType != null) services.AddTransient(interfaceType, implementType); } } #endregion }
上面根據我們自定義介面的不同,適當的採用不同的註冊方式來加入Ioc容器中,從而實現了介面的註冊,在服務層中就可以通過建構函式注入的方式獲得對應的介面範例了。
這樣,不管是在WInform的啟動模組中,還是在Web API的啟動模組中,我們在IOC容器中加入對應的介面即可,如下所示。
/// <summary> /// 應用程式的主入口點。 /// </summary> [STAThread] static void Main() { // IServiceCollection負責註冊 IServiceCollection services = new ServiceCollection(); //services.AddSingleton<IDictDataService, DictDataService>(); //services.AddSingleton<IDictTypeService, DictTypeService>(); //新增IApiUserSession實現類 services.AddSingleton<IApiUserSession, ApiUserPrincipal>(); //呼叫自定義的服務註冊 ServiceInjection.ConfigureRepository(services); // IServiceProvider負責提供範例 IServiceProvider provider = services.BuildServiceProvider(); services.AddSingleton(provider);//註冊到服務集合中,需要可以在Service中建構函式中注入使用
Web API中的程式碼如下所示
//新增HTTP上下文存取 builder.Services.AddHttpContextAccessor(); //設定依賴注入存取資料庫 ServiceInjection.ConfigureRepository(builder.Services); //新增IApiUserSession實現類 builder.Services.AddSingleton<IApiUserSession, ApiUserPrincipal>(); var app = builder.Build();
都是類似的處理方式。
同樣在Web API專案中的控制器處理中,也是一樣通過建構函式注入的方式使用介面的,如下所示。
namespace WebApi.Controllers { /// <summary> /// 客戶資訊的控制器物件 /// </summary> public class CustomerController : BusinessController<CustomerInfo, string, CustomerPagedDto> { private ICustomerService _customerService; /// <summary> /// 建構函式,並注入基礎介面物件 /// </summary> /// <param name="customerService"></param> public CustomerController(ICustomerService customerService) :base(customerService) { this._customerService = customerService; } } }
或者登入處理的控制器定義如下。
/// <summary> /// 登入獲取令牌授權的處理 /// </summary> [Route("api/[controller]")] [ApiController] public class LoginController : ControllerBase { private readonly IHttpContextAccessor _contextAccessor; private readonly IConfiguration _configuration; private readonly IUserService _userService; /// <summary> /// 令牌失效天數,預設令牌7天有效期 /// </summary> protected const int expiredDays = 7; /// <summary> /// 建構函式,注入所需介面 /// </summary> /// <param name="configuration">設定物件</param> /// <param name="httpContext">HTTP上下文物件</param> /// <param name="userService">使用者資訊</param> public LoginController(IConfiguration configuration, IHttpContextAccessor httpContext, IUserService userService) { this._configuration = configuration; this._contextAccessor = httpContext; this._userService = userService; }
系列文章:
《基於SqlSugar的開發框架的循序漸進介紹(1)--框架基礎類的設計和使用》
《基於SqlSugar的開發框架循序漸進介紹(2)-- 基於中間表的查詢處理》
《基於SqlSugar的開發框架循序漸進介紹(3)-- 實現程式碼生成工具Database2Sharp的整合開發》
《基於SqlSugar的開發框架循序漸進介紹(4)-- 在資料存取基礎類別中對GUID主鍵進行自動賦值處理 》
《基於SqlSugar的開發框架循序漸進介紹(5)-- 在服務層使用介面注入方式實現IOC控制反轉》