在前面隨筆,我們介紹過這個基於SqlSugar的開發框架,我們區分Interface、Modal、Service三個目錄來放置不同的內容,其中Modal是SqlSugar的映射實體,Interface是定義訪問介面,Service是提供具體的數據操作實現。在Service層中,往往除了本身的一些增刪... ...
在前面隨筆,我們介紹過這個基於SqlSugar的開發框架,我們區分Interface、Modal、Service三個目錄來放置不同的內容,其中Modal是SqlSugar的映射實體,Interface是定義訪問介面,Service是提供具體的數據操作實現。在Service層中,往往除了本身的一些增刪改查等處理操作外,也需要涉及到相關業務的服務介面,這些服務介面我們通過利用.net 的介面註入方式,實現IOC控制反轉的處理的。
1、框架Service層的模塊
如下麵的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 { }
這個客戶信息業務處理,是比較典型的單表處理案例,它沒有涉及到相關服務介面的整合,如果我們在其中服務介面中需要調用其他服務介面,那麼我們就需要通過構造函數註入介面對象的方式獲得對象的實例,如下我們說介紹的就是服務調用其他相關介面的實現。
2、服務層的介面註入
如對於角色服務介面來說,它往往和用戶、機構有關係,因此我們在角色的服務介面層,可以整合用戶、機構的對應服務介面,如下代碼所示。
/// <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; } }
這裡值得註意的是,由於介面層是同級對象,因此要避免介面的相互引用而導致出錯,依賴關係要清晰,才不會發生這個情況。
3、服務介面的實例的容器註冊
在服務層中,我們是通過參數化構造函數的方式,引入對應的介面的,這個操作方式是構造函數的註入處理。
不過在此之前,我們需要在.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控制反轉》
《基於SqlSugar的開發框架循序漸進介紹(6)-- 在基類介面中註入用戶身份信息介面 》
主要研究技術:代碼生成工具、會員管理系統、客戶關係管理軟體、病人資料管理軟體、Visio二次開發、酒店管理系統、倉庫管理系統等共用軟體開發
專註於Winform開發框架/混合式開發框架、Web開發框架、Bootstrap開發框架、微信門戶開發框架的研究及應用。
轉載請註明出處:
撰寫人:伍華聰 http://www.iqidi.com