前言
絕大多數專案都離不開服務呼叫,服務的呼叫方式通常是基於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等
- GetAsync
- GetStringAsync: 得到響應資訊為字串型別的Get請求
- GetByteArrayAsync: 得到響應資訊為位元組陣列的Get請求
- GetStreamAsync: 得到響應資訊為流的Get請求
- GetAsync: 得到響應資訊支援泛型型別的Get請求
- PostAsync
- PatchAsync
- PutAsync
- DeleteAsync
- SendGrpcAsync (Caller.HttpClient暫不支援)
- SendAsync
- GetAsync
- 降低了不同的部署方式對業務程式碼的影響,對於前期不使用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 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!; }
-
修改
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
服務,瀏覽器訪問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 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!; }
-
修改
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
服務,瀏覽器訪問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,歡迎聯絡我們