在前面随笔介绍的基于SqlSugar的WInform端管理系统中,数据提供者是直接访问数据库的方式,不过窗体界面调用数据接口获取数据的时候,我们传递的是标准的接口,因此可扩展性比较好。我曾经在随笔《基于SqlSugar的开发框架循序渐进介绍(5)– 在服务层使用接口注入方式实现IOC控制反转》中介绍过,该SqlSugar开发框架本身是基于IOC控制反转的,因此对于接入不同的数据提供者,只需要切换到对应的实现层上即可。本篇随笔介绍基于SqlSugar开发框架的Winform端,实现包括对直接访问数据库,远程调用Web API接口的两种不同的处理方式的整合。
1、Winform模块中对具体接口的调用及接口注册
Winform中的界面展示,以及数据处理,都需要具体实现的支撑,由于本身IOC控制反转的接口设计,我们对具体数据的访问,也是基于特定的接口层进行调用的,具体的实现,则是在程序启动的时候,注入对应的接口实现即可。
例如对于客户信息的展示业务操作,代码如下所示
/// <summary> /// 数据显示的函数 /// </summary> public async override void DisplayData() { if (!string.IsNullOrEmpty(ID)) { #region 显示信息 var info = await BLLFactory<ICustomerService>.Instance.GetAsync(ID); if (info != null) { tempInfo = info;//重新给临时对象赋值,使之指向存在的记录对象 txtName.Text = info.Name; txtAge.Value = info.Age; } #endregion //this.btnOK.Enabled = HasFunction("Customer/Edit"); } else { //this.btnOK.Enabled = HasFunction("Customer/Add"); } }
上面代码可以看到,我们是调用接口进行数据的处理的,而这个接口就是在程序启动之处,通过自动的方式获得对应的接口和实现类,然后进行注入的。
.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
只会创建一个实例。
前面说到,接口我们是自动遍历响应的程序集进行注册的,注册接口的逻辑,我们可以统一抽取唯一个公用的函数处理,如下代码所示。
/// <summary> /// 配置依赖注入对象 /// </summary> /// <param name="services"></param> public static void ConfigureRepository(IServiceCollection services) { #region 自动注入对应的服务接口 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 baseType = typeof(IDependency); 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(); RegisterService(services, implementTypes, interfaceTypes); #endregion }
如果我们这里增加一个对Web API的调用,那么在这里注册的时候,切换向Web API代理的注册接口就可以,如下图所示。
因此原来的Winform界面上的调用代码,不需要任何变化,只需要注入不同的接口实现,就能获得不同的方式:普通访问数据库方式,还是分布式获取服务WebAPI的处理方式。
通过切换开关变量的方式,客户可以非常方便的自由切换不同的数据访问方式。数据提供服务,可以是直接访问数据库的方式,也可以是远端的Web API服务方式,从而实现更加广泛的业务需求。
根据不同开关变量,处理不同的接口注册的代码如下所示。
/// <summary> /// 根据配置文件,决定采用直连的DLL,还是代理API的DLL,构建接口进行注入 /// </summary> /// <param name="services"></param> public static void ConfigureRepositoryAuto(IServiceCollection services) { var config = new AppConfig(); string callerType = config.AppConfigGet("CallerType"); if (!string.IsNullOrWhiteSpace(callerType) && callerType.Equals("api", StringComparison.OrdinalIgnoreCase)) { //如果配置为API模式 ConfigureRepositoryApi(services); } else { //如果配置为普通模式 ConfigureRepository(services); } }
API方式的注册,和普通的注册方式类似,就是定位具体的实现,获得接口和具体的实现对象,进行服务注册即可,在此不再赘述。
2、具体的Web API代理实现
在随笔《基于SqlSugar的开发框架循序渐进介绍(5)– 在服务层使用接口注入方式实现IOC控制反转 》我们介绍过具体实现类的继承关系,一般都是构建相应的基类和接口,然后才是具体的业务实现和接口,这样处理可以重用基类的很多接口,提高代码的重用效率。
我们以其中简单的Customer业务表为例,它的服务类代码如下所示(主要关注服务类的定义即可)。
/// <summary> /// 客户信息应用层服务接口实现 /// </summary> public class CustomerService : MyCrudService<CustomerInfo, string, CustomerPagedDto>, ICustomerService { ............... }
而对应Web API的代理调用类,那么为了极大的重用常规的接口处理,我们需要类似的继承关系。
具体的代码实现关系如下所示。
/// <summary> /// 客户信息的Web API调用处理 /// </summary> public class CustomerApiCaller : AsyncCrudApiCaller<CustomerInfo, string, CustomerPagedDto>, ICustomerService { }
我们可以利用代码生成工具生成主要的继承关系,然后实现具体的函数封装即可。我们独立一个项目用来承载API的代理类处理。
在AsyncCrudApiCaller 类中做了很多Web API的调用封装,对于接口的访问,是需要令牌的,因此在用户访问其他接口前,需要获取用户身份令牌信息,并缓存起来供后续使用。
/// <summary> /// 对用户身份进行认证 /// </summary> /// <param name="username">用户名</param> /// <param name="password">用户密码</param> /// <returns></returns> public async virtual Task<AuthenticateResultDto> Authenticate(string username, string password) { var url = string.Format("{0}/api/Login/Authenticate", ServerRootAddress); var input = new { UsernameOrEmailAddress = username, Password = password }; var result = await apiClient.PostAsync<AuthenticateResultDto>(url, input); return result; }
后续每次接口访问的时候,填入相应的令牌信息。
/// <summary> /// 重新增加相应的请求头,如认证的信息 /// </summary> protected virtual void AddRequestHeaders() { //读取需要设置的请求头 apiClient.RequestHeaders.Clear(); foreach (var item in RequestHeaders) { apiClient.RequestHeaders.Add(item); } //从缓存里面读取令牌信息,并在请求的时候自动加入(如果没有加的话) var accessToken = Cache.Instance["AccessToken"] as string; if (!string.IsNullOrWhiteSpace(accessToken)) { var bearer = new NameValue("Authorization", "Bearer " + accessToken); if (apiClient.RequestHeaders != null && !apiClient.RequestHeaders.Contains(bearer)) { apiClient.RequestHeaders.Add(bearer); } } }
而ApiCaller的实现类此对于具体的调用,由于封装了相应的处理类,因此操作代码是比较简单的。
/// <summary> /// 获取所有对象列表 /// </summary> /// <returns></returns> public async virtual Task<ListResultDto<TEntity>> GetAllAsync() { return await DoActionAsync<PagedResultDto<TEntity>>("all"); } /// <summary> /// 获取所有对象列表 /// </summary> /// <param name="input">获取所有条件</param> /// <returns></returns> public async virtual Task<ListResultDto<TEntity>> GetAllByIdsAsync(IEnumerable<TPrimaryKey> input) { return await DoActionAsync<PagedResultDto<TEntity>>("all-byids", input); }
GET参数可以选用Dict方式传递,或者直接传入匿名类也可以,后台代码自动生成相关的URL参数传递的。
public async Task<bool> SetDeletedFlag(int id, bool deleted = true) { var action = $"set-deleted"; var input = new { id, deleted }; return await DoActionAsync<bool>(action, input, HttpVerb.Post); } public async Task<OuInfo> FindByName(string name) { var action = $"byname/{name}"; var dict = new Dictionary<string, string> { { "name", name } }; return await DoActionAsync<OuInfo>(action, dict, HttpVerb.Get); }
剩下的任务就是完善ApiCaller项目的类,与Web API控制器提供的接口的对应关系了,处理完成后,就可以进行测试了。
只要做好模块接口的对接关系,界面的处理代码不用变化就可以切换到其他方式上去了(如Web API的数据提供方式)。
系列文章:
《基于SqlSugar的开发框架的循序渐进介绍(1)–框架基础类的设计和使用》
《基于SqlSugar的开发框架循序渐进介绍(2)– 基于中间表的查询处理》
《基于SqlSugar的开发框架循序渐进介绍(3)– 实现代码生成工具Database2Sharp的整合开发》
《基于SqlSugar的开发框架循序渐进介绍(4)– 在数据访问基类中对GUID主键进行自动赋值处理 》
《基于SqlSugar的开发框架循序渐进介绍(5)– 在服务层使用接口注入方式实现IOC控制反转》
《基于SqlSugar的开发框架循序渐进介绍(6)– 在基类接口中注入用户身份信息接口 》
《基于SqlSugar的开发框架循序渐进介绍(7)– 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传》
《基于SqlSugar的开发框架循序渐进介绍(8)– 在基类函数封装实现用户操作日志记录》
《基于SqlSugar的开发框架循序渐进介绍(9)– 结合Winform控件实现字段的权限控制》
《基于SqlSugar的开发框架循序渐进介绍(10)– 利用axios组件的封装,实现对后端API数据的访问和基类的统一封装处理》
《基于SqlSugar的开发框架循序渐进介绍(11)– 使用TypeScript和Vue3的Setup语法糖编写页面和组件的总结》
《基于SqlSugar的开发框架循序渐进介绍(12)– 拆分页面模块内容为组件,实现分而治之的处理》
《基于SqlSugar的开发框架循序渐进介绍(13)– 基于ElementPlus的上传组件进行封装,便于项目使用》
《基于SqlSugar的开发框架循序渐进介绍(14)– 基于Vue3+TypeScript的全局对象的注入和使用》
《基于SqlSugar的开发框架循序渐进介绍(15)– 整合代码生成工具进行前端界面的生成》
《基于SqlSugar的开发框架循序渐进介绍(16)– 工作流模块的功能介绍》
《基于SqlSugar的开发框架循序渐进介绍(17)– 基于CSRedis实现缓存的处理》
《基于SqlSugar的开发框架循序渐进介绍(18)– 基于代码生成工具Database2Sharp,快速生成Vue3+TypeScript的前端界面和Winform端界面》
《基于SqlSugar的开发框架循序渐进介绍(19)– 基于UniApp+Vue的移动前端的功能介绍》
《基于SqlSugar的开发框架循序渐进介绍(20)– 在基于UniApp+Vue的移动端实现多条件查询的处理》
《基于SqlSugar的开发框架循序渐进介绍(21)– 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换》
《基于SqlSugar的开发框架循序渐进介绍(22)– Vue3+TypeScript的前端工作流模块中实现统一的表单编辑和表单详情查看处理 》
《基于SqlSugar的开发框架循序渐进介绍(23)– Winform端管理系统中平滑增加对Web API对接的需求》
《基于SqlSugar的开发框架循序渐进介绍(24)– 使用Serialize.Linq对Lambda表达式进行序列化和反序列化 》