• Caller 服务调用 - HttpClient


    前言

    绝大多数项目都离不开服务调用,服务的调用方式通常是基于Http、RPC协议的调用,需要获取到对应服务的域名或者ip地址以及详细的控制器方法后才能进行调用,如果项目需要支持分布式部署,则需要借助服务发现或者Nginx才能实现。

    但随着Dapr的崛起,服务的调用方式也发生了变化,它不仅仅提供了处理重试和瞬态错误等功能,还内置服务发现,启用dapr的服务仅需知道任意一个启用dapr服务的HttpPort端口、gRpc端口、以及对应服务的appid以及对应的方法名称就可以完成调用,dapr的出现使得服务间调用变得更为的简单、方便

    目前我们有一个项目是Dapr的,但它所依赖的另外一个项目是基于Http协议的调用,目前只能使用HttpClientRestSharp实现服务间的调用,但未来有一天它会使用Dapr,因为我们计划会把所有的项目都逐步升级到Dapr上

    什么是Masa.Utils.Caller?

    Masa的Caller是一个用于服务调用的类库,它提供了以下能力:

    • 基础Http请求的能力,包括Get、Post、Put、Delete等
      • GetAsync
        • GetStringAsync: 得到响应信息为字符串类型的Get请求
        • GetByteArrayAsync: 得到响应信息为字节数组的Get请求
        • GetStreamAsync: 得到响应信息为流的Get请求
        • GetAsync: 得到响应信息支持泛型类型的Get请求
      • PostAsync
      • PatchAsync
      • PutAsync
      • DeleteAsync
      • SendGrpcAsync (Caller.HttpClient暂不支持)
      • SendAsync
    • 降低了不同的部署方式对业务代码的影响,对于前期不使用Dapr,后期更改为通过Dapr调用,只需修改少量代码即可
    • 当请求方法发生异常后,会继续抛出异常,服务的调用变得像方法调用一样简单,对开发者友好,当然你也可以选择返回HttpResponseMessage自行解析

    检查请求响应的StatusCode的值来判断当前请求是否成功,具体代码可查看

    目前Caller支持了两种实现方式:

    下面就让我们先看一下HttpClient版的Caller

    Caller.HttpClient 入门

    下面我们会写一个简单的Demo,作为入门教程,为大家讲解一下Get请求与Post请求的使用办法

    在开始之前,我们先明确我们的目的以及打算如何做?

    • 目标:通过Caller.HttpClient让大家理解Masa提供的Caller如何实现服务调用(请求)

    • 如何做:分别创建两个Asp.Net Core空的Web服务,一个作为服务端(被调用方),一个作为客户端(调用方),在服务端写两个方法,分别是Get请求(获取用户信息)、Post请求(创建用户)的方法,在客户端同样创建两个对应的方法用来测试获取用户请求、创建用户请求能否正常运行

    准备工作

    1. 创建ASP.NET Core 空项目Assignment.Server作为服务端,并修改Program.cs

      using Microsoft.AspNetCore.Mvc;
      
      var builder = WebApplication.CreateBuilder(args);
      var app = builder.Build();
      
      app.MapGet("/", () => "Hello Assignment.Server!");
      
      app.MapGet("/User", ([FromQuery] int id) =>
      {
          //todo: 模拟根据id查询用户信息
          return new
          {
              Id = id,
              Name = "John Doe"
          };
      });
      
      app.MapPost("/User", ([FromBody] AddUserRequest request) =>
      {
          //todo: 模拟添加用户,并返回用户名称
          return request.Name;
      });
      
      app.Run();
      
      public class AddUserRequest
      {
          public string Name { get; set; }
      }
      
    2. 创建ASP.NET Core 空项目Assignment.Client.HttpClientWeb作为客户端

    3. 选中Assignment.Client.HttpClientWeb并安装Masa.Utils.Caller.HttpClient

      dotnet add package Masa.Utils.Caller.HttpClient --version 0.4.0-preview.4
      
    4. 修改Program.cs

      using System.Globalization;
      using Masa.Utils.Caller.Core;
      using Masa.Utils.Caller.HttpClient;
      using Microsoft.AspNetCore.Mvc;
      
      var builder = WebApplication.CreateBuilder(args);
      builder.Services.AddCaller(option =>
      {
          option.UseHttpClient(opt =>
          {
              opt.BaseApi = "http://localhost:5000";
              opt.Name = "userCaller"; // 当前Caller的别名(仅有一个Caller时可以不填),Name不能重复
              opt.IsDefault = true; // 默认的Caller支持注入ICallerProvider获取
          });
      });
      var app = builder.Build();
      
      app.MapGet("/", () => "Hello HttpClientWeb.V1!");
      
      app.MapGet("/Test/User/Get", async ([FromServices] ICallerProvider callerProvider) =>
      {
          var user = await callerProvider.GetAsync<object, UserDto>("User", new { id = new Random().Next(1, 10) });
          return $"获取用户信息成功:用户名称为:{user!.Name}";
      });
      
      app.MapGet("/Test/User/Add", async ([FromServices] ICallerProvider callerProvider) =>
      {
          var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
          string timeSpan = dateTimeOffset.ToUnixTimeSeconds().ToString();
          var userName = "ss_" + timeSpan; //模拟一个用户名
          string? response = await callerProvider.PostAsync<object, string>("User", new { Name = userName });
          return $"创建用户成功了,用户名称为:{response}";
      });
      
      app.Run();
      
      public class UserDto
      {
          public int Id { get; set; }
      
          public string Name { get; set; } = default!;
      }
      
      

    -> > Q:为什么BaseApi的地址是http://localhost:5000

    A:因为服务端项目的地址是http://localhost:5000,根据实际情况替换成你自己的服务端项目地址

    现在Caller的HttpClient版本就可以使用了,分别启动Assignment.ServerAssignment.Client.HttpClientWeb服务,浏览器访问http://localhost:5107/Test/User/Gethttp://localhost:5107/Test/User/Add,分别输出对应的获取用户信息成功以及创建用户成功的提示,则证明调用成功了。

    搞笑对话:

    搞笑对话

    HttpClient 进阶版

    随着与工程师经理的一番切磋后发现了上述代码仅是基础版的,更贴合传统的HttpClient的写法,与默认的HttpClient有异曲同工之妙,但并不是只能这样写,下面就来看看新的写法:

    1. 创建ASP.NET Core 空项目Assignment.Client.HttpClientWeb.V2作为调用方V2版本

    2. 选中Assignment.Client.HttpClientWeb.V2并安装Masa.Utils.Caller.HttpClient

      dotnet add package Masa.Utils.Caller.HttpClient --version 0.4.0-preview.4
      
    3. 添加类ServerCaller (对应服务端服务)

      using Masa.Utils.Caller.HttpClient;
      namespace Assignment.Client.HttpClientWeb.V2;
      
      public class ServerCaller : HttpClientCallerBase
      {
          protected override string BaseAddress { get; set; } = "http://localhost:5000";
      
          public ServerCaller(IServiceProvider serviceProvider) : base(serviceProvider)
          {
          }
      
          /// <summary>
          /// 调用服务获取用户信息 (重点)
          /// </summary>
          /// <param name="id">用户id</param>
          /// <returns></returns>
          public Task<UserDto?> GetUserAsync(int id)
              => CallerProvider.GetAsync<object, UserDto>("User", new { id = id });
      
          /// <summary>
          /// 调用服务添加用户(重点)
          /// </summary>
          /// <param name="userName"></param>
          /// <returns></returns>
          public Task<string?> AddUserAsync(string userName)
              => CallerProvider.PostAsync<object, string>("User", new { Name = userName });
      }
      
      
      public class UserDto
      {
          public int Id { get; set; }
      
          public string Name { get; set; } = default!;
      }
      
    4. 修改Program.cs

      using System.Globalization;
      using Assignment.Client.HttpClientWeb.V2;
      using Masa.Utils.Caller.Core;
      using Microsoft.AspNetCore.Mvc;
      
      var builder = WebApplication.CreateBuilder(args);
      builder.Services.AddCaller();
      
      var app = builder.Build();
      
      app.MapGet("/", () => "Hello HttpClientWeb.V2!");
      
      // 重点:直接注入对应的ServiceCaller,调用对应的方法即可
      app.MapGet("/Test/User/Get", async ([FromServices] ServerCaller serverCaller) =>
      {
          var id = new Random().Next(1, 10);//默认用户id
          var user = await serverCaller.GetUserAsync(id);
          return $"获取用户信息成功:用户名称为:{user!.Name}";
      });
      
      app.MapGet("/Test/User/Add", async ([FromServices] ServerCaller serverCaller) =>
      {
          var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
          string timeSpan = dateTimeOffset.ToUnixTimeSeconds().ToString();
          var userName = "ss_" + timeSpan; //模拟一个用户名
          string? response= await serverCaller.AddUserAsync(userName);
          return $"创建用户成功了,用户名称为:{response}";
      });
      
      app.Run();
      

    最后,分别启动Assignment.ServerAssignment.Client.HttpClientWeb.V2服务,浏览器访问http://localhost:5188/Test/User/Gethttp://localhost:5188/Test/User/Add,分别输出对应的获取用户信息成功以及创建用户成功的提示,则证明调用成功了。

    这个版本的Caller很不错,调用请求变成了跟调用方法一样,简单明了,很不错

    不过ServerCaller下面好像是同一个服务的方法,如果我这个服务方法特别多的话,那这个类岂不是特别庞大,但如果我要拆分成好几个的话,那BaseAddress我岂不是需要复制很多份 ʅ( T﹏T )ʃ

    HttpClient 推荐

    让请求调用更简单,让你的代码更简洁

    1. 在V2版本的基础上添加类ServerCallerBase

      using Masa.Utils.Caller.HttpClient;
      namespace Assignment.Client.HttpClientWeb.V3;
      
      /// <summary>
      /// 注意:ServerCallerBase是抽象类哟
      /// </summary>
      public abstract class ServerCallerBase: HttpClientCallerBase
      {
          protected override string BaseAddress { get; set; } = "http://localhost:5000";
      
          protected ServerCallerBase(IServiceProvider serviceProvider) : base(serviceProvider)
          {
          }
      }
      

    ServerCallerBase可以以服务拆分,每个服务建一个'ServerCallerBase'

    1. 调整ServerCaller.cs重命名为UserCaller.cs,然后删除重写BaseAddress属性:

      namespace Assignment.Client.HttpClientWeb.V3;
      public class UserCaller : ServerCallerBase
      {
          // protected override string BaseAddress { get; set; } = "http://localhost:5000"; //注意:父类已经实现,无需重写,所以被删除了
          
          public UserCaller(IServiceProvider serviceProvider) : base(serviceProvider)
          {
          }
      
          /// <summary>
          /// 调用服务获取用户信息
          /// </summary>
          /// <param name="id">用户id</param>
          /// <returns></returns>
          public Task<UserDto?> GetUserAsync(int id)
              => CallerProvider.GetAsync<object, UserDto>("User", new { id = id });
      
          /// <summary>
          /// 调用服务添加用户
          /// </summary>
          /// <param name="userName"></param>
          /// <returns></returns>
          public Task<string?> AddUserAsync(string userName)
              => CallerProvider.PostAsync<object, string>("User", new { Name = userName });
      }
      
      public class UserDto
      {
          public int Id { get; set; }
      
          public string Name { get; set; } = default!;
      }
      
    2. 修改Program.cs,将ServerCaller修改为UserCaller即可

      app.MapGet("/Test/User/Get", async ([FromServices] UserCaller caller) =>
      {
          var id = new Random().Next(1, 10);//默认用户id
          var user = await caller.GetUserAsync(id);
          return $"获取用户信息成功:用户名称为:{user!.Name}";
      });
      
      app.MapGet("/Test/User/Add", async ([FromServices] UserCaller caller) =>
      {
          var dateTimeOffset = new DateTimeOffset(DateTime.UtcNow);
          string timeSpan = dateTimeOffset.ToUnixTimeSeconds().ToString();
          var userName = "ss_" + timeSpan; //模拟一个用户名
          string? response= await caller.AddUserAsync(userName);
          return $"创建用户成功了,用户名称为:{response}";
      });
      

    其余代码不变,就形成了V3版本,V3版本与V2版本相比,减少了多次对BaseAddress的赋值、使得代码更加简洁,按照控制器结构建立对应的Caller,让服务调用像方法调用一样简单明了

    最后,分别启动Assignment.ServerAssignment.Client.HttpClientWeb.V3服务,浏览器访问http://localhost:5201/Test/User/Gethttp://localhost:5201/Test/User/Add,分别输出对应的获取用户信息成功以及创建用户成功的提示,则证明调用成功了。

    本章源码

    Assignment02

    https://github.com/zhenlei520/MasaFramework.Practice

    开源地址

    MASA.BuildingBlocks:https://github.com/masastack/MASA.BuildingBlocks

    MASA.Contrib:https://github.com/masastack/MASA.Contrib

    MASA.Utils:https://github.com/masastack/MASA.Utils

    MASA.EShop:https://github.com/masalabs/MASA.EShop

    MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

    如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

    16373211753064.png

  • 相关阅读:
    洗地机哪个牌子好?2023热门洗地推荐
    视图、存储过程、触发器
    实战案例:如何批量查询物流信息并将结果导出到表格?
    Nginx优化
    C++信息学奥赛1168:大整数加法
    汽车电子——产品标准规范汇总和梳理(信息安全)
    AppBox快速开发框架(开源)开发流程介绍
    4.00001Postgresql的内存管理-从哪里开始了解内存管理之架构理解
    Dockershim即将被正式废弃,你准备好了吗?
    K8S知识点(三)
  • 原文地址:https://www.cnblogs.com/zhenlei520/p/16243015.html