.NET 帮助

C# HttpClient(面向开发人员的工作原理)

HttpClient 类是 .NET 框架的一部分,提供了发送 HTTP 请求和从由 URI 标识的资源接收 HTTP 响应的方法。 它简化了 HTTP 请求调用,无论您是执行 GET、POST 请求、PUT 还是 DELETE 请求。 本指南将涵盖在实际场景中使用HttpClient的基本用法,并介绍IronPDF库

创建新的 HttpClient 实例

HttpClient 类用于发送 HTTP 请求。 您可以按如下方式创建一个新实例:

using System;
using System.Net.Http;
class Program
{
    static async Task Main(string[] args)
    {
        using var client = new HttpClient(); //HttpClient client
        var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London"); // HTTP response for GET request
        var responseBody = await response.Content.ReadAsStringAsync(); // response content
        Console.WriteLine(responseBody);
    }
}
using System;
using System.Net.Http;
class Program
{
    static async Task Main(string[] args)
    {
        using var client = new HttpClient(); //HttpClient client
        var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London"); // HTTP response for GET request
        var responseBody = await response.Content.ReadAsStringAsync(); // response content
        Console.WriteLine(responseBody);
    }
}
Imports System
Imports System.Net.Http
Friend Class Program
	Shared Async Function Main(ByVal args() As String) As Task
		Dim client = New HttpClient() 'HttpClient client
		Dim response = Await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London") ' HTTP response for GET request
		Dim responseBody = Await response.Content.ReadAsStringAsync() ' response content
		Console.WriteLine(responseBody)
	End Function
End Class
$vbLabelText   $csharpLabel

在此示例中:

  • 使用 var client = new HttpClient() 创建一个新的 HttpClient 实例。
  • 使用 GetAsync 方法发送 HTTP GET 请求。
  • HttpResponseMessage 保存在 var response 中。
  • 响应的内容是使用response.Content.ReadAsStringAsync()获取的。

发送 HTTP 请求

HTTP GET 请求

发出 HTTP GET 请求并处理响应:

var client = new HttpClient();
var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Paris");
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
var client = new HttpClient();
var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Paris");
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
Dim client = New HttpClient()
Dim response = Await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Paris")
If response.IsSuccessStatusCode Then
	Dim responseBody = Await response.Content.ReadAsStringAsync()
	Console.WriteLine(responseBody)
End If
$vbLabelText   $csharpLabel
  • IsSuccessStatusCode 属性可确保请求成功。
  • 使用 ReadAsStringAsync 异步读取响应体。

HTTP POST 请求

发送 POST 请求涉及添加请求正文:

var client = new HttpClient();
var requestBody = new StringContent("{ \"location\": \"New York\" }", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody);
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
var client = new HttpClient();
var requestBody = new StringContent("{ \"location\": \"New York\" }", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody);
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
Dim client = New HttpClient()
Dim requestBody = New StringContent("{ ""location"": ""New York"" }", Encoding.UTF8, "application/json")
Dim response = Await client.PostAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody)
If response.IsSuccessStatusCode Then
	Dim responseBody = Await response.Content.ReadAsStringAsync()
	Console.WriteLine(responseBody)
End If
$vbLabelText   $csharpLabel
  • PostAsync 发送包含指定主体(requestBody)的请求。
  • 您必须指定内容类型(application/JSON)。

HTTP PUT 请求

HTTP PUT 请求更新资源:

var client = new HttpClient();
var requestBody = new StringContent("{ \"location\": \"Tokyo\", \"days\": 3 }", Encoding.UTF8, "application/json");
var response = await client.PutAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody);
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
var client = new HttpClient();
var requestBody = new StringContent("{ \"location\": \"Tokyo\", \"days\": 3 }", Encoding.UTF8, "application/json");
var response = await client.PutAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody);
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
Dim client = New HttpClient()
Dim requestBody = New StringContent("{ ""location"": ""Tokyo"", ""days"": 3 }", Encoding.UTF8, "application/json")
Dim response = Await client.PutAsync("https://api.weatherapi.com/v1/forecast.json?key=YOUR_API_KEY", requestBody)
If response.IsSuccessStatusCode Then
	Dim responseBody = Await response.Content.ReadAsStringAsync()
	Console.WriteLine(responseBody)
End If
$vbLabelText   $csharpLabel
  • PutAsync 发送 PUT 请求以更新指定 URI 上的资源。
  • 请求正文通常包含需要更新的数据。

HTTP DELETE 请求

发送 HTTP DELETE 请求:

var client = new HttpClient();
var response = await client.DeleteAsync("https://api.weatherapi.com/v1/locations/1?key=YOUR_API_KEY");
if (response.IsSuccessStatusCode)
{
    Console.WriteLine("Resource deleted successfully");
}
var client = new HttpClient();
var response = await client.DeleteAsync("https://api.weatherapi.com/v1/locations/1?key=YOUR_API_KEY");
if (response.IsSuccessStatusCode)
{
    Console.WriteLine("Resource deleted successfully");
}
Dim client = New HttpClient()
Dim response = Await client.DeleteAsync("https://api.weatherapi.com/v1/locations/1?key=YOUR_API_KEY")
If response.IsSuccessStatusCode Then
	Console.WriteLine("Resource deleted successfully")
End If
$vbLabelText   $csharpLabel
  • DeleteAsync 发送 DELETE 请求以删除资源。

处理 HTTP 响应

每个 HTTP 请求都会返回一个 HttpResponseMessage 对象,其中包括响应正文、标头和状态代码。 例如

var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Sydney");
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}
var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Sydney");
if (response.IsSuccessStatusCode)
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseBody);
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}
Dim response = Await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Sydney")
If response.IsSuccessStatusCode Then
	Dim responseBody = Await response.Content.ReadAsStringAsync()
	Console.WriteLine(responseBody)
Else
	Console.WriteLine($"Error: {response.StatusCode}")
End If
$vbLabelText   $csharpLabel
  • Response.StatusCode 提供状态码(例如,200,404)。
  • response.Content 包含响应正文,可使用 ReadAsStringAsync 异步读取。

高效使用 HttpClient

HttpClient 实例应重复使用,以利用连接池,避免耗尽系统资源。 一种典型的模式是为应用程序或服务的整个生命周期创建一个 HttpClient 实例。这可以通过静态变量或网络应用程序的依赖注入来实现。

静态 HttpClient 示例

public static class HttpClientProvider
{
    private static readonly HttpClient client = new HttpClient();
    public static HttpClient Client => client;
}
public static class HttpClientProvider
{
    private static readonly HttpClient client = new HttpClient();
    public static HttpClient Client => client;
}
Public Module HttpClientProvider
'INSTANT VB NOTE: The field client was renamed since Visual Basic does not allow fields to have the same name as other class members:
	Private ReadOnly client_Conflict As New HttpClient()
	Public ReadOnly Property Client() As HttpClient
		Get
			Return client_Conflict
		End Get
	End Property
End Module
$vbLabelText   $csharpLabel

HttpClient 实例在整个应用程序中重复使用,减少了创建新 HTTP 连接的开销。

通过依赖注入使用 HttpClient

在网络应用程序中,建议将 HttpClient 注册为单例服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
}
Public Sub ConfigureServices(ByVal services As IServiceCollection)
	services.AddHttpClient()
End Sub
$vbLabelText   $csharpLabel

您还可以创建命名客户端和键入客户端,以进行更具体的配置。

连接池和代理设置

通过重复使用 HttpClient 实例,您可以从连接池中获益,从而提高向同一服务器发出多个请求的性能。 您还可以使用 HttpClientHandler 类配置代理设置:

var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://proxyserver:port"),
    UseProxy = true
};
var client = new HttpClient(handler);
var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://proxyserver:port"),
    UseProxy = true
};
var client = new HttpClient(handler);
Dim handler = New HttpClientHandler With {
	.Proxy = New WebProxy("http://proxyserver:port"),
	.UseProxy = True
}
Dim client = New HttpClient(handler)
$vbLabelText   $csharpLabel

错误处理和状态代码

要处理不同的 HTTP 状态代码,请检查 HttpResponseMessage.StatusCode 属性:

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
async Task MakeRequestAsync()
{
    try
    {
        using var client = new HttpClient();
        var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Berlin");
        // Response Status Code Cases
        switch (response.StatusCode)
        {
            case HttpStatusCode.OK:
                Console.WriteLine("Success");
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"Response content: {content}");
                break;
            case HttpStatusCode.NotFound:
                Console.WriteLine("Resource not found");
                break;
            case HttpStatusCode.Unauthorized:
                Console.WriteLine("Unauthorized access");
                break;
            case HttpStatusCode.InternalServerError:
                Console.WriteLine("Server error occurred");
                break;
            default:
                Console.WriteLine($"Unexpected status code: {response.StatusCode}");
                break;
        }
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine($"Request error: {e.Message}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"An error occurred: {e.Message}");
    }
}
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
async Task MakeRequestAsync()
{
    try
    {
        using var client = new HttpClient();
        var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Berlin");
        // Response Status Code Cases
        switch (response.StatusCode)
        {
            case HttpStatusCode.OK:
                Console.WriteLine("Success");
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"Response content: {content}");
                break;
            case HttpStatusCode.NotFound:
                Console.WriteLine("Resource not found");
                break;
            case HttpStatusCode.Unauthorized:
                Console.WriteLine("Unauthorized access");
                break;
            case HttpStatusCode.InternalServerError:
                Console.WriteLine("Server error occurred");
                break;
            default:
                Console.WriteLine($"Unexpected status code: {response.StatusCode}");
                break;
        }
    }
    catch (HttpRequestException e)
    {
        Console.WriteLine($"Request error: {e.Message}");
    }
    catch (Exception e)
    {
        Console.WriteLine($"An error occurred: {e.Message}");
    }
}
Imports System
Imports System.Net
Imports System.Net.Http
Imports System.Threading.Tasks
Async Function MakeRequestAsync() As Task
	Try
		Dim client = New HttpClient()
		Dim response = Await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=Berlin")
		' Response Status Code Cases
		Select Case response.StatusCode
			Case HttpStatusCode.OK
				Console.WriteLine("Success")
				Dim content = Await response.Content.ReadAsStringAsync()
				Console.WriteLine($"Response content: {content}")
			Case HttpStatusCode.NotFound
				Console.WriteLine("Resource not found")
			Case HttpStatusCode.Unauthorized
				Console.WriteLine("Unauthorized access")
			Case HttpStatusCode.InternalServerError
				Console.WriteLine("Server error occurred")
			Case Else
				Console.WriteLine($"Unexpected status code: {response.StatusCode}")
		End Select
	Catch e As HttpRequestException
		Console.WriteLine($"Request error: {e.Message}")
	Catch e As Exception
		Console.WriteLine($"An error occurred: {e.Message}")
	End Try
End Function
$vbLabelText   $csharpLabel

JSON 响应体处理

您经常要处理 JSON 响应。 您可以将响应内容反序列化为强类型对象:

using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
var client = new HttpClient();
var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London");
var jsonString = await response.Content.ReadAsStringAsync();
var weatherResponse = JsonSerializer.Deserialize<WeatherResponse>(jsonString);
public class WeatherResponse
{
    public string Location { get; set; }
    public double Temperature { get; set; }
}
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
var client = new HttpClient();
var response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London");
var jsonString = await response.Content.ReadAsStringAsync();
var weatherResponse = JsonSerializer.Deserialize<WeatherResponse>(jsonString);
public class WeatherResponse
{
    public string Location { get; set; }
    public double Temperature { get; set; }
}
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks
Private client = New HttpClient()
Private response = await client.GetAsync("https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London")
Private jsonString = await response.Content.ReadAsStringAsync()
Private weatherResponse = JsonSerializer.Deserialize(Of WeatherResponse)(jsonString)
Public Class WeatherResponse
	Public Property Location() As String
	Public Property Temperature() As Double
End Class
$vbLabelText   $csharpLabel

ReadAsStringAsync 方法简化了将 JSON 内容直接读入 C# 对象的过程。

介绍IronPDF

C# HttpClient(它如何为开发人员工作):图1 - IronPDF

IronPDF for .NET 是一个 .NET PDF 库,旨在用 C# 创建、操作和转换 PDF 文件。 它被广泛用于从HTML、CSS、JavaScript和其他格式生成高质量PDF。 IronPDF 提供 HTML 到 PDF 的转换、PDF 合并、水印等功能,甚至还有数字签名和 PDF 加密等高级操作。 它兼容各种平台,包括 Windows、Linux 和 macOS,是跨平台开发的通用解决方案。

使用 IronPDF 和 HttpClient

将 IronPDF 与 C# 中的 HttpClient 类相结合,是动态生成和处理来自网络资源的 PDF 文档的有效方法。 例如,您可以通过 HttpClient 从 URL 获取 HTML 内容,然后使用 IronPdf 将此 HTML 转换为 PDF 文档。 这在根据实时网络内容动态生成报告、发票或任何文档时非常有用。

using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
using IronPdf;
class Program
{
    static async Task Main(string[] args)
    {
        License.LicenseKey = "IRONSUITE.WINSWRITERSIRONSOFTWARECOM.31102-3E7C9B3308-CIHWVP2-L2A7FVEBUSFN-DP6CI32YVXIN-45HQSAZOBOCW-YCYETGS4ENQX-NZXXG4YCPAYI-EBCIJ43PNLJW-DKWC7U-TFXPQ4LKLXGPEA-DEPLOYMENT.TRIAL-G43TTA.TRIAL.EXPIRES.20.MAY.2025";
        using var client = new HttpClient();
        var response = await client.GetAsync("https://api.weatherapi.com/v1/forecast.json?key=55c9040a6ad34c6c90470702240609&q=London&days=3");
        if (response.IsSuccessStatusCode)
        {
            var jsonContent = await response.Content.ReadAsStringAsync();
            // Pretty-print the JSON
            var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonContent);
            var formattedJson = JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true });
            // Escape the JSON for HTML
            formattedJson = System.Web.HttpUtility.HtmlEncode(formattedJson);
            var htmlContent = $@"
            <html>
            <head>
                <style>
                    body {{ font-family: Arial, sans-serif; }}
                    pre {{ background-color: #f4f4f4; padding: 20px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; }}
                </style>
            </head>
            <body>
                <h1>Weather Forecast (JSON Data)</h1>
                <pre>{formattedJson}</pre>
            </body>
            </html>";
            var renderer = new ChromePdfRenderer();
            var pdf = renderer.RenderHtmlAsPdf(htmlContent);
            pdf.SaveAs("F://weather_report.pdf");
            Console.WriteLine("PDF generated successfully!");
        }
        else
        {
            Console.WriteLine($"Failed to retrieve content. Status code: {response.StatusCode}");
        }
    }
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
using IronPdf;
class Program
{
    static async Task Main(string[] args)
    {
        License.LicenseKey = "IRONSUITE.WINSWRITERSIRONSOFTWARECOM.31102-3E7C9B3308-CIHWVP2-L2A7FVEBUSFN-DP6CI32YVXIN-45HQSAZOBOCW-YCYETGS4ENQX-NZXXG4YCPAYI-EBCIJ43PNLJW-DKWC7U-TFXPQ4LKLXGPEA-DEPLOYMENT.TRIAL-G43TTA.TRIAL.EXPIRES.20.MAY.2025";
        using var client = new HttpClient();
        var response = await client.GetAsync("https://api.weatherapi.com/v1/forecast.json?key=55c9040a6ad34c6c90470702240609&q=London&days=3");
        if (response.IsSuccessStatusCode)
        {
            var jsonContent = await response.Content.ReadAsStringAsync();
            // Pretty-print the JSON
            var jsonElement = JsonSerializer.Deserialize<JsonElement>(jsonContent);
            var formattedJson = JsonSerializer.Serialize(jsonElement, new JsonSerializerOptions { WriteIndented = true });
            // Escape the JSON for HTML
            formattedJson = System.Web.HttpUtility.HtmlEncode(formattedJson);
            var htmlContent = $@"
            <html>
            <head>
                <style>
                    body {{ font-family: Arial, sans-serif; }}
                    pre {{ background-color: #f4f4f4; padding: 20px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; }}
                </style>
            </head>
            <body>
                <h1>Weather Forecast (JSON Data)</h1>
                <pre>{formattedJson}</pre>
            </body>
            </html>";
            var renderer = new ChromePdfRenderer();
            var pdf = renderer.RenderHtmlAsPdf(htmlContent);
            pdf.SaveAs("F://weather_report.pdf");
            Console.WriteLine("PDF generated successfully!");
        }
        else
        {
            Console.WriteLine($"Failed to retrieve content. Status code: {response.StatusCode}");
        }
    }
}
Imports System
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Text.Json
Imports IronPdf
Friend Class Program
	Shared Async Function Main(ByVal args() As String) As Task
		License.LicenseKey = "IRONSUITE.WINSWRITERSIRONSOFTWARECOM.31102-3E7C9B3308-CIHWVP2-L2A7FVEBUSFN-DP6CI32YVXIN-45HQSAZOBOCW-YCYETGS4ENQX-NZXXG4YCPAYI-EBCIJ43PNLJW-DKWC7U-TFXPQ4LKLXGPEA-DEPLOYMENT.TRIAL-G43TTA.TRIAL.EXPIRES.20.MAY.2025"
		Dim client = New HttpClient()
		Dim response = Await client.GetAsync("https://api.weatherapi.com/v1/forecast.json?key=55c9040a6ad34c6c90470702240609&q=London&days=3")
		If response.IsSuccessStatusCode Then
			Dim jsonContent = Await response.Content.ReadAsStringAsync()
			' Pretty-print the JSON
			Dim jsonElement = JsonSerializer.Deserialize(Of JsonElement)(jsonContent)
			Dim formattedJson = JsonSerializer.Serialize(jsonElement, New JsonSerializerOptions With {.WriteIndented = True})
			' Escape the JSON for HTML
			formattedJson = System.Web.HttpUtility.HtmlEncode(formattedJson)
			Dim htmlContent = $"
            <html>
            <head>
                <style>
                    body {{ font-family: Arial, sans-serif; }}
                    pre {{ background-color: #f4f4f4; padding: 20px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; }}
                </style>
            </head>
            <body>
                <h1>Weather Forecast (JSON Data)</h1>
                <pre>{formattedJson}</pre>
            </body>
            </html>"
			Dim renderer = New ChromePdfRenderer()
			Dim pdf = renderer.RenderHtmlAsPdf(htmlContent)
			pdf.SaveAs("F://weather_report.pdf")
			Console.WriteLine("PDF generated successfully!")
		Else
			Console.WriteLine($"Failed to retrieve content. Status code: {response.StatusCode}")
		End If
	End Function
End Class
$vbLabelText   $csharpLabel

C# HttpClient(它对开发人员的作用):图 2 - PDF 输出

在使用真实天气 API 时,请记住用 API 密钥替换 "YOUR_API_KEY"。

结论

C# HttpClient(它如何为开发者工作):图3 - 许可

本教程探讨了如何使用 C# 中的 HttpClient 类发送 HTTP 请求并处理响应。 我们还介绍了 IronPDF,这是一个功能强大的库,用于在 .NET 应用程序中生成 PDF。 我们演示了如何结合这些技术,使用 HttpClient 从网络服务中检索 HTML 内容,并使用 IronPDF 将其转换为 PDF。

IronPDF 提供免费试用,其许可证起价为 $749,这使其成为寻求全面 PDF 生成功能的开发人员的有价值工具。

Chipego
软件工程师
Chipego 拥有出色的倾听技巧,这帮助他理解客户问题并提供智能解决方案。他在 2023 年加入 Iron Software 团队,此前他获得了信息技术学士学位。IronPDF 和 IronOCR 是 Chipego 主要专注的两个产品,但他对所有产品的了解每天都在增长,因为他不断找到支持客户的新方法。他喜欢 Iron Software 的合作氛围,公司各地的团队成员贡献他们丰富的经验,以提供有效的创新解决方案。当 Chipego 离开办公桌时,你经常可以发现他在看书或踢足球。
< 前一页
C# AES 加密(如何为开发人员工作)
下一步 >
C# 歧义联盟(如何为开发人员工作)