Caller 服務調用 - HttpClient
前言
絕大多數項目都離不開服務調用,服務的調用方式通常是基於 Http、RPC 協議的調用,需要獲取到對應服務的域名或者 ip 地址以及詳細的控制器方法後才能進行調用,如果項目需要支持分佈式部署,則需要藉助服務發現或者 Nginx 才能實現。
但隨着 Dapr 的崛起,服務的調用方式也發生了變化,它不僅僅提供了處理重試和瞬態錯誤等功能,還內置服務發現,啓用 dapr 的服務僅需知道任意一個啓用 dapr 服務的 HttpPort 端口、gRpc 端口、以及對應服務的 appid 以及對應的方法名稱就可以完成調用,dapr 的出現使得服務間調用變得更爲的簡單、方便
目前我們有一個項目是 Dapr 的,但它所依賴的另外一個項目是基於 Http 協議的調用,目前只能使用 HttpClient 或 RestSharp 實現服務間的調用,但未來有一天它會使用 Dapr,因爲我們計劃會把所有的項目都逐步升級到 Dapr 上
什麼是 Masa.Utils.Caller?
Masa 的 Caller 是一個用於服務調用的類庫,它提供了以下能力:
-
基礎 Http 請求的能力,包括 Get、Post、Put、Delete 等
-
GetStringAsync: 得到響應信息爲字符串類型的 Get 請求
-
GetByteArrayAsync: 得到響應信息爲字節數組的 Get 請求
-
GetStreamAsync: 得到響應信息爲流的 Get 請求
-
GetAsync: 得到響應信息支持泛型類型的 Get 請求
-
GetAsync
-
PostAsync
-
PatchAsync
-
PutAsync
-
DeleteAsync
-
SendGrpcAsync (Caller.HttpClient 暫不支持)
-
SendAsync
-
降低了不同的部署方式對業務代碼的影響,對於前期不使用 Dapr,後期更改爲通過 Dapr 調用,只需修改少量代碼即可
-
當請求方法發生異常後,會繼續拋出異常,服務的調用變得像方法調用一樣簡單,對開發者友好,當然你也可以選擇返回 HttpResponseMessage 自行解析
檢查請求響應的
StatusCode
的值來判斷當前請求是否成功,具體代碼可查看
目前 Caller 支持了兩種實現方式:
-
基於 HttpClient 的實現: Masa.Utils.Caller.HttpClient
-
基於 DaprClient 的實現: Masa.Utils.Caller.DaprClient
下面就讓我們先看一下 HttpClient 版的 Caller
Caller.HttpClient 入門
下面我們會寫一個簡單的 Demo,作爲入門教程,爲大家講解一下 Get 請求與 Post 請求的使用辦法
在開始之前,我們先明確我們的目的以及打算如何做?
-
目標:通過 Caller.HttpClient 讓大家理解 Masa 提供的 Caller 如何實現服務調用(請求)
-
如何做:分別創建兩個 Asp.Net Core 空的 Web 服務,一個作爲服務端(被調用方),一個作爲客戶端(調用方),在服務端寫兩個方法,分別是 Get 請求(獲取用戶信息)、Post 請求(創建用戶)的方法,在客戶端同樣創建兩個對應的方法用來測試獲取用戶請求、創建用戶請求能否正常運行
準備工作
- 安裝. Net 6.0
-
創建 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; } }
-
創建 ASP.NET Core 空項目
Assignment.Client.HttpClientWeb
作爲客戶端 -
選中
Assignment.Client.HttpClientWeb
並安裝Masa.Utils.Caller.HttpClient
dotnet add package Masa.Utils.Caller.HttpClient --version 0.4.0-preview.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.Server
、Assignment.Client.HttpClientWeb
服務,瀏覽器訪問http://localhost:5107/Test/User/Get
、http://localhost:5107/Test/User/Add
,分別輸出對應的獲取用戶信息成功以及創建用戶成功的提示,則證明調用成功了。
搞笑對話:
HttpClient 進階版
隨着與工程師經理的一番切磋後發現了上述代碼僅是基礎版的,更貼合傳統的 HttpClient 的寫法,與默認的 HttpClient 有異曲同工之妙,但並不是只能這樣寫,下面就來看看新的寫法:
-
創建 ASP.NET Core 空項目
Assignment.Client.HttpClientWeb.V2
作爲調用方 V2 版本 -
選中
Assignment.Client.HttpClientWeb.V2
並安裝Masa.Utils.Caller.HttpClient
dotnet add package Masa.Utils.Caller.HttpClient --version 0.4.0-preview.4
-
添加類
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 >用戶id</param> /// <returns></returns> public Task<UserDto?> GetUserAsync(int id) => CallerProvider.GetAsync<object, UserDto>("User", new { id = id }); /// <summary> /// 調用服務添加用戶(重點) /// </summary> /// <param ></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!; }
-
修改
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.Server
、Assignment.Client.HttpClientWeb.V2
服務,瀏覽器訪問http://localhost:5188/Test/User/Get
、http://localhost:5188/Test/User/Add
,分別輸出對應的獲取用戶信息成功以及創建用戶成功的提示,則證明調用成功了。
這個版本的 Caller 很不錯,調用請求變成了跟調用方法一樣,簡單明瞭,很不錯
不過
ServerCaller
下面好像是同一個服務的方法,如果我這個服務方法特別多的話,那這個類豈不是特別龐大,但如果我要拆分成好幾個的話,那BaseAddress
我豈不是需要複製很多份 ʅ(T﹏T)ʃ
HttpClient 推薦
讓請求調用更簡單,讓你的代碼更簡潔
-
在 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'
-
調整
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 >用戶id</param> /// <returns></returns> public Task<UserDto?> GetUserAsync(int id) => CallerProvider.GetAsync<object, UserDto>("User", new { id = id }); /// <summary> /// 調用服務添加用戶 /// </summary> /// <param ></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!; }
-
修改
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.Server
、Assignment.Client.HttpClientWeb.V3
服務,瀏覽器訪問http://localhost:5201/Test/User/Get
、http://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,歡迎聯繫我們
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/d7T4EqcV7G-6gZZIP5vW6Q