草庐IT

基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求

wuhuacong(伍华聪)的专栏 2023-03-28 原文

在前面随笔介绍的基于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表达式进行序列化和反序列化 

 

有关基于SqlSugar的开发框架循序渐进介绍(23)-- Winform端管理系统中平滑增加对Web API对接的需求的更多相关文章

  1. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  2. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  3. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  6. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  7. ruby - 以毫秒为单位获取当前系统时间 - 2

    在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:

  8. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  9. ruby-on-rails - 如何构建复杂的 Rails 系统 - 2

    关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和

  10. ruby-on-rails - Rails 3,在RAILS_ROOT上方显示来自本地文件系统的jpg图片 - 2

    我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。

随机推荐