最近一直在思考一个问题,就是写教程的时候,要如何用最少的文字和代码尽可能的把教程涉及到的方方面面演示出来!
如果只是语言层面,感觉还是可以的,但是如果用到了框架,用到了系统给予的解决方案,也的确有难度。
这篇,我们尝试 从零开始用小化的代码演示 HttpClient 和 IHttpClientFactory 的使用 ,看看篇幅怎么样!
1. 创建项目
我们使用 Visual Studio 或 Visual Studio Code 创建一个名为 myhttpclient1
的模板为 控制台应用程序 的项目
如果你习惯使用命令行,比如我,那么可以用下面的命令创建
dotnet new console --name myhttpclient1
创建完成后目录结果如下
├── Program.cs ├── myhttpclient1.csproj └── obj ├── myhttpclient1.csproj.nuget.dgspec.json ├── myhttpclient1.csproj.nuget.g.props ├── myhttpclient1.csproj.nuget.g.targets ├── project.assets.json └── project.nuget.cache
2. 改变项目的程序集
使用 Visual Studio Code 打开 myhttpclient1.csproj
文件,将 <Project Sdk="Microsoft.NET.Sdk">
改成 <Project Sdk="Microsoft.NET.Sdk.Web">
- <Project Sdk="Microsoft.NET.Sdk"> + <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> </Project>
如果你使用 Visual Studio,则是在 myhttpclient1
项目上 点右键,然后点击 编辑项目文件
为什么要改变程序集
因为原本的 Microsoft.Net.SDK
没有包含 Web 应用程序所需的命名空间,只有 Microsoft.Net.SDK.Web
包含
3. 编辑 Program.cs
文件添加一些命名空间
打开 Program.cs
文件后,我们可以看到原文件内容大概是这样的
using System; namespace myhttpclient1 { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } } }
我们需要加入以下命名空间
using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging;
这些命名空间我们不用刻意加入,因为编辑器的自动完成功能可以很好的帮我们完成这些。
4. 在 Program.cs
中添加一个服务,名字为 MyService
接下来我们需要在 myhttpclient1
空间下添加一个服务 MyService
。
按照 C# 和 .NET Core 程序惯例,我们将添加一个名为 IMyService
的接口和 MyService
的实现类
using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace myhttpclient1 { class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } } interface IMyService { } class MyService:IMyService { } }
IMyService
和 MyService
没有添加任何方法和属性,这不重要,我们首先需要的就是一个架子
5. 改造 Main()
方法创建一个 IHost
对象并注入 MyService
服务
using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace myhttpclient1 { class Program { static void Main(string[] args) { // 1. 创建一个 IHostBuilder 对象 // 2. 通过 IHostBuilder 对象的 ConfigureServices 添加 HttpClient 服务和 `MyService` 服务 var builder = new HostBuilder() .ConfigureServices((HostBuilderContext, services) => { services.AddHttpClient(); services.AddTransient<IMyService, MyService>(); }).UseConsoleLifetime(); // 4. 创建一个 IHost 对象 var host = builder.Build(); Console.WriteLine("Hello World!"); } } interface IMyService { } class MyService:IMyService { } }
这里的代码很熟悉是不是?
其实这里的代码就是 ASP.NET Core 一般项目的 Program.cs
和 StartUp.cs
的合并。
-
首先我们通过
new HostBuilder()
创建一个IHostBuilder
的实例,我们将用这个实例来创建Host
主机对象。 -
IHostBuilder
的ConfigureServices()
方法其实就是StartUp.cs
里的ConfigureServices()
方法,我们在这个方法里通过services.AddHttpClient()
加入了 HttpClient 服务,通过services.AddTransient<IMyService, MyService>()
把我们自定义的MyService
添加到服务列表。 -
IHostBuilder
对象的UseConsoleLifetime()
表示接受Ctrl+C
来结束生命周期,也就是停止服务。 -
最后通过
IHostBuilder
对象的Build()
方法创建一个Host
对象。
其实,这段文字详细的给我们描述了一般的 ASP.NET Core 项目中的 Program
类和 StartUp
类之间的关系。如果你有空,可以尝试删掉 StartUp
类,并把里面的内容搬到 Program
中试一试
6. 完善 MyService,添加一个 GetPage()
方法获取百度首页的内容
using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace myhttpclient1 { class Program { static void Main(string[] args) { // 1. 创建一个 IHostBuilder 对象 // 2. 通过 IHostBuilder 对象的 ConfigureServices 添加 HttpClient 服务和 `MyService` 服务 var builder = new HostBuilder() .ConfigureServices((HostBuilderContext, services) => { services.AddHttpClient(); services.AddTransient<IMyService, MyService>(); }).UseConsoleLifetime(); // 4. 创建一个 IHost 对象 var host = builder.Build(); Console.WriteLine("Hello World!"); } } public interface IMyService { Task<string> GetPage(); } public class MyService : IMyService { private readonly IHttpClientFactory _clientFactory; public MyService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<string> GetPage() { var request = new HttpRequestMessage(HttpMethod.Get, "http://www.baidu.com/"); var client = _clientFactory.CreateClient(); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } return $"StatusCode:{response.StatusCode}"; } } }
-
首先改造
IMyService
接口添加GetPage
异步方法Task<string> GetPage()
-
改造
MyService
类,添加类型为IHttpClientFactory
的 只读属性_clientFactory
,我们将用这个IHttpClientFactory
对象创建HttpClient
实例。private readonly IHttpClientFactory _clientFactory;
-
-
改造
MyService
类,将IHttpClientFactory
的实例作为构造函数的一部分。这样,系统在创建MyService
类时,将会自动注入HttpClientFactory
对象。而这个HttpClientFactory
对象是通过services.AddHttpClient()
注入的。public MyService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; }
-
好了,最后我们在
GetPage
方法中通过_clientFactory
的CreateClient()
创建HttpClient
对象,并且调用该对象的SendAsync()
来发起一个请求,并把请求结果的前 500 个字符座位结果返回public async Task<string> GetPage() { // 1. 使用 HttpRequestMessage 构造一个 Http 请求,需要指定请求方法和请求 Url var request = new HttpRequestMessage(HttpMethod.Get, "http://www.baidu.com/"); // 2. 调用 CreateClient() 方法创建 HttpClient 对象 var client = _clientFactory.CreateClient(); // 3. 调用 HttpClient.SendAsync() 发起请求 var response = await client.SendAsync(request); // 4. 通过 Http 响应的 IsSuccessStatusCode 来判断是否返回 200 服务 if (response.IsSuccessStatusCode) { // 5. 获取响应的内容 return await response.Content.ReadAsStringAsync(); } return $"StatusCode:{response.StatusCode}"; }
7. 调用 MyService.GetPage()
方法
然后我们就可以在 Main()
方法中使用我们的 MyService
服务,并且调用 GetPage
方法
static async Task<int> Main(string[] args) { var builder = new HostBuilder() .ConfigureServices((HostBuilderContext,services) => { services.AddHttpClient(); services.AddTransient<IMyService,MyService>(); }).UseConsoleLifetime(); var host = builder.Build(); try { var myService = host.Services.GetRequiredService<IMyService>(); var pageContent = await myService.GetPage(); Console.WriteLine(pageContent.Substring(0,500)); } catch(Exception ex) { var logger = host.Services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex,"An error occurred!"); } return 0; }
- 我们通过
Host
实例的Services
属性上的GetRequiredService<IMyService>()
方法获取我们加入的MyService
的实例。 - 然后我们就可以通过
MyService.GetPage()
获取百度的内容
运行
代码阶段我们已经完全结束了,全部代码如下
using System; using System.Net.Http; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace myhttpclient1 { class Program { static async Task<int> Main(string[] args) { var builder = new HostBuilder() .ConfigureServices((HostBuilderContext,services) => { services.AddHttpClient(); services.AddTransient<IMyService,MyService>(); }).UseConsoleLifetime(); var host = builder.Build(); try { var myService = host.Services.GetRequiredService<IMyService>(); var pageContent = await myService.GetPage(); Console.WriteLine(pageContent.Substring(0,500)); } catch(Exception ex) { var logger = host.Services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex,"An error occurred!"); } return 0; } } public interface IMyService { Task<string> GetPage(); } public class MyService: IMyService { private readonly IHttpClientFactory _clientFactory; public MyService(IHttpClientFactory clientFactory) { _clientFactory = clientFactory; } public async Task<string> GetPage() { // 1. 使用 var request = new HttpRequestMessage(HttpMethod.Get, "http://www.baidu.com/"); var client = _clientFactory.CreateClient(); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { return await response.Content.ReadAsStringAsync(); } return $"StatusCode:{response.StatusCode}"; } } }
60+ 行代码就演示了如何使用 HttpClient 和 IHttpClientFactory ,还附带让大家了解了 StartUp
类的由来
上面的代码,运行结果如下
<!DOCTYPE html><!--STATUS OK--> <html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="referrer"><meta name="theme-color" content="#2932e1"><meta name="description" content="全球领先的中文搜索引擎、致力于让网民更便捷地获取信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。"><link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /><link rel="search" type="application/opensearchdescription+xml" href="/cont