前言

绝大多数项目都离不开服务调用,服务的调用方式通常是基于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服务,浏览器访问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服务,浏览器访问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,欢迎联系我们

让服务调用更简单 - Caller.HttpClient的更多相关文章

  1. Caller 服务调用 - Dapr

    前言 上一篇我们讲了使用HttpClient的方式调用,那么如果我们现在需要更换为通过dapr实现服务调用,我们需要做哪些事情呢? Caller.Dapr 入门 如果我们的项目原本使用的是Caller ...

  2. 3. Caller 服务调用 - dapr

    前言 上一篇我们讲了使用HttpClient的方式调用,那么如果我们现在需要更换为通过dapr实现服务调用,我们需要做哪些事情呢? Caller.Dapr 入门 如果我们的项目原本使用的是Caller ...

  3. Anno 让微服务、混合编程更简单(Net love Java)

    在社区或者QQ群我们经常看到有人争辩编程语言的好坏,只要一提起这个话题常常就能引来很多人参与,往往最后就变成了一群人几个小时的骂战.今天我们要说的是如何让Java和.Net(甚至更多语言)相结合.充分 ...

  4. Rsession让Java调用R更简单

    Rsession让Java调用R更简单 R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大. R语言作为统计学一门语言,一直在小众领域闪耀着光芒. ...

  5. 微服务平台(Micro Service Platform : MSP)旨在提供一个集开发、测试、运维于一体的开发者专属平台,让开发者能快速构建或使用微服务,让开发更简单,让运维更高效。

    微服务平台(Micro Service Platform : MSP)旨在提供一个集开发.测试.运维于一体的开发者专属平台,让开发者能快速构建或使用微服务,让开发更简单,让运维更高效. MSP采用业界 ...

  6. Http请求封装(对HttpClient类的进一步封装,使之调用更方便。另外,此类管理唯一的HttpClient对象,支持线程池调用,效率更高)

    package com.ad.ssp.engine.common; import java.io.IOException; import java.util.ArrayList; import jav ...

  7. 三、springcloud之服务调用Feign

    一.背景 项目中接口调用: Httpclient Okhttp Httpurlconnection RestTemplate 微服务提供了更简单,方便的Feign 二.Feign简介 Feign是一个 ...

  8. Spring Cloud Feign 声明式服务调用

    目录 一.Feign是什么? 二.Feign的快速搭建 三.Feign的几种姿态 参数绑定 继承特性 四.其他配置 Ribbon 配置 Hystrix 配置 一.Feign是什么? ​ 通过对前面Sp ...

  9. net core天马行空系列:移植Feign,结合Polly,实现回退,熔断,重试,超时,做最好用的声明式http服务调用端

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...

随机推荐

  1. Memcached 服务分布式集群如何实现?

    特殊说明:Memcached 集群和 web 服务集群是不一样的,所有 Memcached 的数据总和才是数据库的数据.每台 Memcached 都是部分数据.(一台 memcached 的数据,就是 ...

  2. Redis 回收进程如何工作的?

    一个客户端运行了新的命令,添加了新的数据.Redi 检查内存使用情况,如 果大于 maxmemory 的限制, 则根据设定好的策略进行回收.一个新的命令被执 行,等等.所以我们不断地穿越内存限制的边界 ...

  3. 什么是 bean 的自动装配?

    Spring 容器能够自动装配相互合作的 bean,这意味着容器不需要和配置,能通 过 Bean 工厂自动处理 bean 之间的协作.

  4. Tomcat警告之“资源添加到Web应用程序[]的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间”

    原因 缓存不够,可以将其缓存调大 解决办法: 在 /conf/context.xml 的 前添加以下内容: <Resources cachingAllowed="true" ...

  5. HTML5相关文章和资源

    Polyfills HTML5 Cross Browser Polyfills canvas HTML5 JS实现毛玻璃效果(高斯模糊) 高斯模糊的算法Canvas 内部元素添加事件处理 应用场景 P ...

  6. 如何利用WebSocket实现网页版聊天室

    花了将近一周的时间终于完成了利用WebSocket完成网页版聊天室这个小demo,期间还走过了一段"看似弯曲"的道路,但是我想其实也不算是弯路吧,因为你走过的路必将留下你的足迹.这 ...

  7. 腾讯云服务nginx部署静态项目

    一直想要搭建自己的blog,买了基础云服务器练手 文章内容是根据腾讯文档(https://cloud.tencent.com/document/product/213/2131)总结 部署静态页面归纳 ...

  8. java——封装

    java--封装 java--封装1 封装的理解和好处2 封装的事项实现步骤3 将构造器和setXx结合4 this和super区分 1 封装的理解和好处 隐藏实现细节:[方法(连接数据库)<- ...

  9. redis的基础命令操作

    文章目录 前言 一.字符串类型 二.哈希类型 三.列表类型 四.集合类型 五.有序集合类型 六.通过命令 前言 redis的数据结构 redis存储的是key,value格式的数据,其中的key是字符 ...

  10. spring-注入list集合对象(值是对象)

    1.创建stu类 public class Stu { // //1.数组类型 // private String[] courses; // // //2.list集合属性 // private L ...