如何正确发送 HTTP Web GET 请求

176

我在C#上还是新手,正在尝试创建一个应用程序,用于通知我何时收到通知(回答、评论等)。但目前我只是尝试简单调用API来获取用户数据。

我正在使用Visual Studio Express 2012构建C#应用程序,在这个应用程序中,您输入您的用户ID,然后应用程序将使用该用户ID发出请求并显示该用户ID的统计信息。

以下是我尝试进行请求的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//Request library
using System.Net;
using System.IO;

namespace TestApplication
{
    class Connect
    {
        public string id;
        public string type;

        protected string api = "https://api.stackexchange.com/2.2/";
        protected string options = "?order=desc&sort=name&site=stackoverflow";
        
        public string request()
        {
            string totalUrl = this.join(id);

            return this.HttpGet(totalUrl);
        }

        protected string join(string s)
        {
            return api + type + "/" + s + options;
        }

        protected string get(string url)
        {
            try
            {
                string rt;

                WebRequest request = WebRequest.Create(url);
                
                WebResponse response = request.GetResponse();

                Stream dataStream = response.GetResponseStream();

                StreamReader reader = new StreamReader(dataStream);

                rt = reader.ReadToEnd();

                Console.WriteLine(rt);

                reader.Close();
                response.Close();

                return rt;
            }

            catch(Exception ex)
            {
                return "Error: " + ex.Message;
            }
        }
        public string HttpGet(string URI)
        {
            WebClient client = new WebClient();

            // Add a user agent header in case the 
            // requested URI contains a query.

            client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");

            Stream data = client.OpenRead(URI);
            StreamReader reader = new StreamReader(data);
            string s = reader.ReadToEnd();
            data.Close();
            reader.Close();

            return s;
        }
    }
}

这个类是一个对象,通过解析用户ID并发出请求,可以从表单中访问它。

我尝试了很多在谷歌上找到的例子,但不知道为什么无论如何都会得到这个消息"�"。

我对这种算法还很陌生,如果有人能分享一本书或教程,展示如何做这种事情(解释每一步),我将非常感激。


1
请查看http://zetcode.com/csharp/httpclient/,其中有非常简单明了的示例。 - barlop
5个回答

345
有时服务器会压缩其响应以节省带宽,当这种情况发生时,您需要在尝试读取内容之前对响应进行解压缩。
从.NET 6开始,HttpWebRequest已被弃用,并且是本答案中的原始示例,我将其保留在下面。
强烈建议您使用HttpClient进行后续操作。
使用HttpClient和AutomaticDecompression
public class HttpService
{
    private readonly HttpClient _client;

    public HttpService()
    {
        HttpClientHandler handler = new HttpClientHandler 
        { 
            AutomaticDecompression = DecompressionMethods.All 
        };
        
        _client = new HttpClient();
    }

    public async Task<string> GetAsync(string uri)
    {
        using HttpResponseMessage response = await _client.GetAsync(uri);

        return await response.Content.ReadAsStringAsync();
    }

    public async Task<string> PostAsync(string uri, string data, string contentType)
    {
        using HttpContent content = new StringContent(data, Encoding.UTF8, contentType);
        
        HttpRequestMessage requestMessage = new HttpRequestMessage() 
        { 
            Content = content,
            Method = HttpMethod.Post,
            RequestUri = new Uri(uri)
        };
        
        using HttpResponseMessage response = await _client.SendAsync(requestMessage);

        return await response.Content.ReadAsStringAsync();
    }
}

使用HttpWebRequestAutomaticDecompression 警告:.NET 6开始,此处使用的HttpWebRequest类已被弃用。强烈建议改用HttpClientGET
public string Get(string uri)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

    using(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    using(Stream stream = response.GetResponseStream())
    using(StreamReader reader = new StreamReader(stream))
    {
        return reader.ReadToEnd();
    }
}

异步获取

public async Task<string> GetAsync(string uri)
{
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

    using(HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
    using(Stream stream = response.GetResponseStream())
    using(StreamReader reader = new StreamReader(stream))
    {
        return await reader.ReadToEndAsync();
    }
}

POST 包含参数method,以便您可以使用其他HTTP方法,如PUT、DELETE等。
public string Post(string uri, string data, string contentType, string method = "POST")
{
    byte[] dataBytes = Encoding.UTF8.GetBytes(data);

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    request.ContentLength = dataBytes.Length;
    request.ContentType = contentType;
    request.Method = method;

    using(Stream requestBody = request.GetRequestStream())
    {
        requestBody.Write(dataBytes, 0, dataBytes.Length);
    }

    using(HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    using(Stream stream = response.GetResponseStream())
    using(StreamReader reader = new StreamReader(stream))
    {
        return reader.ReadToEnd();
    }
}

    

POST异步 包含参数method,以便您可以使用其他HTTP方法,如PUT、DELETE等。
public async Task<string> PostAsync(string uri, string data, string contentType, string method = "POST")
{
    byte[] dataBytes = Encoding.UTF8.GetBytes(data);

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
    request.ContentLength = dataBytes.Length;
    request.ContentType = contentType;
    request.Method = method;

    using(Stream requestBody = request.GetRequestStream())
    {
        await requestBody.WriteAsync(dataBytes, 0, dataBytes.Length);
    }

    using(HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
    using(Stream stream = response.GetResponseStream())
    using(StreamReader reader = new StreamReader(stream))
    {
        return await reader.ReadToEndAsync();
    }
}


4
顺便提醒一下,你可能需要展示一个如何解析 html 字符串中的 +1 的示例,以便获得更清晰的代码。 - MethodMan
谢谢,我之前不知道解压缩的事情。我是一个 PHP/Node.js 开发人员,这是我第一次开始开发桌面应用程序。 - Oscar Reyes
3
@ahmadmolaie 已将它们添加,以及如何进行POST请求的方法。 - Aydin
我想提交表单数据,如何调用POST方法?(我的POST参数值放在哪里?) - Banee Ishaque K
你好!我使用了你的答案,并制作了一个包装器,可以帮助你自动处理查询字符串 https://gist.github.com/earlpeterg/d3da31c2cf1d029215d5229c4ff74b74 HttpHelper.Get("https://api.example.com/status", new { userId = "1234" }); 将会得到 https://api.example.com/status?userId=1234 - Earlee
显示剩余4条评论

56
另一种方法是使用“HttpClient”,如下所示:

using System;
using System.Net;
using System.Net.Http;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Making API Call...");
            using (var client = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }))
            {
                client.BaseAddress = new Uri("https://api.stackexchange.com/2.2/");
                HttpResponseMessage response = client.GetAsync("answers?order=desc&sort=activity&site=stackoverflow").Result;
                response.EnsureSuccessStatusCode();
                string result = response.Content.ReadAsStringAsync().Result;
                Console.WriteLine("Result: " + result);
            }
            Console.ReadLine();
        }
    }
}

请查看stackoverflow上的HttpClient vs HttpWebRequest以及其他一些内容here.

更新于2020年6月22日: 不建议在'using'块中使用httpclient,因为它可能会导致端口耗尽。

private static HttpClient client = null;
    
ContructorMethod()
{
   if(client == null)
   {
        HttpClientHandler handler = new HttpClientHandler()
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        };        
        client = new HttpClient(handler);
   }
   client.BaseAddress = new Uri("https://api.stackexchange.com/2.2/");
   HttpResponseMessage response = client.GetAsync("answers?order=desc&sort=activity&site=stackoverflow").Result;
   response.EnsureSuccessStatusCode();
   string result = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine("Result: " + result);           
 }

如果使用 .Net Core 2.1+,请考虑使用 IHttpClientFactory 并在启动代码中注入。
 var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
            TimeSpan.FromSeconds(60));

 services.AddHttpClient<XApiClient>().ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
        }).AddPolicyHandler(request => timeout);

1
谢谢!对我非常有用。我只是稍微修改了一下,将响应和内容包含在“using”语句中: - codely
8
根据 https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ 的建议,不要在 using 语句中使用 HttpClient。 - sfors says reinstate Monica
5
永远不要说永远。看看代码。 HttpClient 实例在程序的整个生命周期中仅被使用一次,并在程序退出之前被处理。这是完全正确和合适的。 - Todd Menier
1
我不确定你如何反驳有关如何正确创建 HttpClient 实例的文章以及其他文章。使用私有静态变量,该变量不会被处理。因此,正如在那篇文章中引用的那样:(关于不使用 dispose)......“但 HttpClient 是不同的。虽然它实现了 IDisposable 接口,但它实际上是一个共享对象。这意味着,在幕后它是可重入和线程安全的。而不是为每次执行创建一个新的 HttpClient 实例,你应该为整个应用程序的生命周期共享单个 HttpClient 实例。” - sfors says reinstate Monica
我意识到我的评论晚了2年,但是托德并没有质疑这篇文章。托德只是说,给定完整的程序示例,一个单独的HttpClient将在应用程序的生命周期内使用。 - ProgrammingLlama

14

我认为最简单的方法

  var web = new WebClient();
  var url = $"{hostname}/LoadDataSync?systemID={systemId}";
  var responseString = web.DownloadString(url);

或者

 var bytes = web.DownloadData(url);

5
var request = (HttpWebRequest)WebRequest.Create("sendrequesturl");
var response = (HttpWebResponse)request.GetResponse();
string responseString;
using (var stream = response.GetResponseStream())
{
    using (var reader = new StreamReader(stream))
    {
        responseString = reader.ReadToEnd();
    }
}

6
代码没有处理对象的释放,可能会导致内存泄漏。需要使用using语句。 - StarTrekRedneck
你不能将 <null> 赋值给隐式类型变量! - Luca Ziegler
只是声明了 null。我知道,我会移除 null。 - Manish sharma

2

除了已经给出的回答之外,这是一个完整的示例,访问JSON PlaceHolder 网站。

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Publish
{
    class Program
    {
        static async Task Main(string[] args)
        {
            
            // Get Reqeust
            HttpClient req = new HttpClient();
            var content = await req.GetAsync("https://jsonplaceholder.typicode.com/users");
            Console.WriteLine(await content.Content.ReadAsStringAsync());

            // Post Request
            Post p = new Post("Some title", "Some body", "1");
            HttpContent payload = new StringContent(JsonConvert.SerializeObject(p));
            content = await req.PostAsync("https://jsonplaceholder.typicode.com/posts", payload);
            Console.WriteLine("--------------------------");
            Console.WriteLine(content.StatusCode);
            Console.WriteLine(await content.Content.ReadAsStringAsync());
        }
    }

    public struct Post {
        public string Title {get; set;}
        public string Body {get;set;}
        public string UserID {get; set;}

        public Post(string Title, string Body, string UserID){
            this.Title = Title;
            this.Body = Body;
            this.UserID = UserID;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接