C#中HttpWebRequest、WebClient、HttpClient的使用详解
命名空间System.Net,WebClient是一种更高级别的抽象,是HttpWebRequest为了简化最常见任务而创建的,使用过程中你会发现他缺少基本的header,timeoust的设置,不过这些可以通过继承httpwebrequest来实现。相对来说,WebClient比WebRequest更加简单,它相当于封装了request和response方法,不过需要说明的是,Webclient
C#中HttpWebRequest、WebClient、HttpClient的使用详解
**
HttpWebRequest:
**
命名空间: System.Net,这是.NET创建者最初开发用于使用HTTP请求的标准类。使用HttpWebRequest可以让开发者控制请求/响应流程的各个方面,如 timeouts, cookies, headers, protocols。另一个好处是HttpWebRequest类不会阻塞UI线程。例如,当您从响应很慢的API服务器下载大文件时,您的应用程序的UI不会停止响应。HttpWebRequest通常和WebResponse一起使用,一个发送请求,一个获取数据。HttpWebRquest更为底层一些,能够对整个访问过程有个直观的认识,但同时也更加复杂一些。
//POST方法
public static string HttpPost(string Url, string postDataStr)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
Encoding encoding = Encoding.UTF8;
byte[] postData = encoding.GetBytes(postDataStr);
request.ContentLength = postData.Length;
Stream myRequestStream = request.GetRequestStream();
myRequestStream.Write(postData, 0, postData.Length);
myRequestStream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream myResponseStream = response.GetResponseStream();
StreamReader myStreamReader = new StreamReader(myResponseStream, encoding);
string retString = myStreamReader.ReadToEnd();
myStreamReader.Close();
myResponseStream.Close();
return retString;
}
//GET方法
public static string HttpGet(string Url, string postDataStr)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Url + (postDataStr == "" ? "" : "?") + postDataStr);
request.Method = "GET";
request.ContentType = "text/html;charset=UTF-8";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream myResponseStream = response.GetResponseStream();
StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8"));
string retString = myStreamReader.ReadToEnd();
myStreamReader.Close();
myResponseStream.Close();
return retString;
}
WebClient:
命名空间System.Net,WebClient是一种更高级别的抽象,是HttpWebRequest为了简化最常见任务而创建的,使用过程中你会发现他缺少基本的header,timeoust的设置,不过这些可以通过继承httpwebrequest来实现。相对来说,WebClient比WebRequest更加简单,它相当于封装了request和response方法,不过需要说明的是,Webclient和WebRequest继承的是不同类,两者在继承上没有任何关系。使用WebClient可能比HttpWebRequest直接使用更慢(大约几毫秒),但却更为简单,减少了很多细节,代码量也比较少。
public class WebClientHelper
{
public static string DownloadString(string url)
{
WebClient wc = new WebClient();
//wc.BaseAddress = url; //设置根目录
wc.Encoding = Encoding.UTF8; //设置按照何种编码访问,如果不加此行,获取到的字符串中文将是乱码
string str = wc.DownloadString(url);
return str;
}
public static string DownloadStreamString(string url)
{
WebClient wc = new WebClient();
wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36");
Stream objStream = wc.OpenRead(url);
StreamReader _read = new StreamReader(objStream, Encoding.UTF8); //新建一个读取流,用指定的编码读取,此处是utf-8
string str = _read.ReadToEnd();
objStream.Close();
_read.Close();
return str;
}
public static void DownloadFile(string url, string filename)
{
WebClient wc = new WebClient();
wc.DownloadFile(url, filename); //下载文件
}
public static void DownloadData(string url, string filename)
{
WebClient wc = new WebClient();
byte [] bytes = wc.DownloadData(url); //下载到字节数组
FileStream fs = new FileStream(filename, FileMode.Create);
fs.Write(bytes, 0, bytes.Length);
fs.Flush();
fs.Close();
}
public static void DownloadFileAsync(string url, string filename)
{
WebClient wc = new WebClient();
wc.DownloadFileCompleted += DownCompletedEventHandler;
wc.DownloadFileAsync(new Uri(url), filename);
Console.WriteLine("下载中。。。");
}
private static void DownCompletedEventHandler(object sender, AsyncCompletedEventArgs e)
{
Console.WriteLine(sender.ToString()); //触发事件的对象
Console.WriteLine(e.UserState);
Console.WriteLine(e.Cancelled);
Console.WriteLine("异步下载完成!");
}
public static void DownloadFileAsync2(string url, string filename)
{
WebClient wc = new WebClient();
wc.DownloadFileCompleted += (sender, e) =>
{
Console.WriteLine("下载完成!");
Console.WriteLine(sender.ToString());
Console.WriteLine(e.UserState);
Console.WriteLine(e.Cancelled);
};
wc.DownloadFileAsync(new Uri(url), filename);
Console.WriteLine("下载中。。。");
}
}
HttpClient:
HttpClient是.NET4.5引入的一个HTTP客户端库,其命名空间为 System.Net.Http ,.NET 4.5之前我们可能使用WebClient和HttpWebRequest来达到相同目的。HttpClient利用了最新的面向任务模式,使得处理异步请求非常容易。它适合用于多次请求操作,一般设置好默认头部后,可以进行重复多次的请求,基本上用一个实例可以提交任何的HTTP请求。HttpClient有预热机制,第一次进行访问时比较慢,所以不应该用到HttpClient就new一个出来,应该使用单例或其他方式获取HttpClient的实例
单例模式:
单例模式(Singleton Pattern)这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例创建步骤:1、定义静态私有对象;2、定义私有构造函数;3、提供公共获取对象方法;
单例模式一般分为两种实现模式:懒汉模式、饿汉模式(以下为Java代码实现)
懒汉模式: 默认不会实例化,什么时候用什么时候new
public class Singleton {
private static Singleton instance = null;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
饿汉模式: 类初始化时,会立即加载该对象,线程天生安全,调用效率高
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
双检锁/双重校验锁(DCL,即 double-checked locking):这种方式采用双锁机制,安全且在多线程情况下能保持高性能
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
HttpClient:
public class HttpClientHelper
{
private static readonly object LockObj = new object();
private static HttpClient client = null;
public HttpClientHelper() {
GetInstance();
}
public static HttpClient GetInstance()
{
if (client == null)
{
lock (LockObj)
{
if (client == null)
{
client = new HttpClient();
}
}
}
return client;
}
public async Task<string> PostAsync(string url, string strJson)//post异步请求方法
{
try
{
HttpContent content = new StringContent(strJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
//由HttpClient发出异步Post请求
HttpResponseMessage res = await client.PostAsync(url, content);
if (res.StatusCode == System.Net.HttpStatusCode.OK)
{
string str = res.Content.ReadAsStringAsync().Result;
return str;
}
else
return null;
}
catch (Exception ex)
{
return null;
}
}
public string Post(string url, string strJson)//post同步请求方法
{
try
{
HttpContent content = new StringContent(strJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
//client.DefaultRequestHeaders.Connection.Add("keep-alive");
//由HttpClient发出Post请求
Task<HttpResponseMessage> res = client.PostAsync(url, content);
if (res.Result.StatusCode == System.Net.HttpStatusCode.OK)
{
string str = res.Result.Content.ReadAsStringAsync().Result;
return str;
}
else
return null;
}
catch (Exception ex)
{
return null;
}
}
public string Get(string url)
{
try
{
var responseString = client.GetStringAsync(url);
return responseString.Result;
}
catch (Exception ex)
{
return null;
}
}
}
HttpClient有预热机制,第一次请求比较慢;可以通过初始化前发送一次head请求解决:
_httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) };
//帮HttpClient热身
_httpClient.SendAsync(new HttpRequestMessage {
Method = new HttpMethod("HEAD"),
RequestUri = new Uri(BASE_ADDRESS + "/") })
.Result.EnsureSuccessStatusCode();
简单的Demo,用于了解WebAPI如何同时接收文件及数据,同时提供HttpClient模拟如何同时上传文件和数据的Demo,下面是HttpClient上传的Demo界面
、HttpClient部分:
HttpClient通过PostAsync提交数据时,第二个请求参数为抽象类HttpContent,当前我们需要通过multipart/form-data的方式模拟请求,multipart对应的请求HttpContent为MultipartContent及其子类MultipartFormDataContent,按名字明显可以看出MultipartFormDataContent对应multipart/form-data,MultipartFormDataContent可以通过Add方法添加具体的HttpContent,这里当然是添加ByteArrayContent了
下面是分别获取文件及键值对集合对应ByteArrayContent集合的代码
/// <summary>
/// 获取文件集合对应的ByteArrayContent集合
/// </summary>
/// <param name="files"></param>
/// <returns></returns>
private List<ByteArrayContent> GetFileByteArrayContent(HashSet<string> files)
{
List<ByteArrayContent> list = new List<ByteArrayContent>();
foreach (var file in files)
{
var fileContent = new ByteArrayContent(File.ReadAllBytes(file));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
FileName = Path.GetFileName(file)
};
list.Add(fileContent);
}
return list;
}
/// <summary>
/// 获取键值集合对应的ByteArrayContent集合
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
private List<ByteArrayContent> GetFormDataByteArrayContent(NameValueCollection collection)
{
List<ByteArrayContent> list = new List<ByteArrayContent>();
foreach (var key in collection.AllKeys)
{
var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(collection[key]));
dataContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
{
Name = key
};
list.Add(dataContent);
}
return list;
}
然后提交Api部分的代码如下(如需完整代码,请至底部点击源代码下载链接)
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/" + this.cmbResponseContentType.Text.ToLower()));//设定要响应的数据格式
using (var content = new MultipartFormDataContent())//表明是通过multipart/form-data的方式上传数据
{
var formDatas = this.GetFormDataByteArrayContent(this.GetNameValueCollection(this.gv_FormData));//获取键值集合对应的ByteArrayContent集合
var files = this.GetFileByteArrayContent(this.GetHashSet(this.gv_File));//获取文件集合对应的ByteArrayContent集合
Action<List<ByteArrayContent>> act = (dataContents) =>
{//声明一个委托,该委托的作用就是将ByteArrayContent集合加入到MultipartFormDataContent中
foreach (var byteArrayContent in dataContents)
{
content.Add(byteArrayContent);
}
};
act(formDatas);//执行act
act(files);//执行act
try
{
var result = client.PostAsync(this.txtUrl.Text, content).Result;//post请求
this.txtResponse.Text = result.Content.ReadAsStringAsync().Result;//将响应结果显示在文本框内
}
catch (Exception ex)
{
this.txtResponse.Text = ex.ToString();//将异常信息显示在文本框内
}
}
}
2、WebAPI部分
其实WebAPI这部分真的没什么,完全是参考了国外大牛的代码,不过某些不明了的地方在方法内有备注,有时间会去研究下如何才能实现无需保存文件至硬盘,即可获取相应的数据流
[HttpPost]
public async Task<Dictionary<string, string>> Post(int id = 0)
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Dictionary<string, string> dic = new Dictionary<string, string>();
string root = HttpContext.Current.Server.MapPath("~/App_Data");//指定要将文件存入的服务器物理位置
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{//接收文件
Trace.WriteLine(file.Headers.ContentDisposition.FileName);//获取上传文件实际的文件名
Trace.WriteLine("Server file path: " + file.LocalFileName);//获取上传文件在服务上默认的文件名
}//TODO:这样做直接就将文件存到了指定目录下,暂时不知道如何实现只接收文件数据流但并不保存至服务器的目录下,由开发自行指定如何存储,比如通过服务存到图片服务器
foreach (var key in provider.FormData.AllKeys)
{//接收FormData
dic.Add(key, provider.FormData[key]);
}
}
catch
{
throw;
}
return dic;
}
PS:补充由服务端指定文件存储位置博客地址
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
public class MultipartFormDataMemoryStreamProvider : MultipartFormDataStreamProvider
{
public MultipartFormDataMemoryStreamProvider()
: base("/")
{
}
public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
{
if (parent == null)
{
throw new ArgumentNullException("parent");
}
if (headers == null)
{
throw new ArgumentNullException("headers");
}
MemoryStream stream = new MemoryStream();
if (IsFileContent(parent, headers))
{
MultipartFileData item = new MultipartFileDataStream(headers, string.Empty, stream);
this.FileData.Add(item);
}
return stream;
}
private bool IsFileContent(HttpContent parent, HttpContentHeaders headers)
{
ContentDispositionHeaderValue contentDisposition = headers.ContentDisposition;
if (contentDisposition == null)
{
throw new InvalidOperationException("Content-Disposition error");
}
return !string.IsNullOrEmpty(contentDisposition.FileName);
}
}
public class MultipartFileDataStream : MultipartFileData, IDisposable
{
/// <summary>
/// file content stream
/// </summary>
public Stream Stream { get; private set; }
public MultipartFileDataStream(HttpContentHeaders headers, string localFileName, Stream stream)
: base(headers, localFileName)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
this.Stream = stream;
}
public void Dispose()
{
this.Stream.Dispose();
}
}
使用方式基本没变化,只是取Stream的地方有所调整
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
Dictionary<string, string> dic = new Dictionary<string, string>();
#region 原来使用MultipartFormDataStreamProvider的方式
//string root = HttpContext.Current.Server.MapPath("~/App_Data");//指定要将文件存入的服务器物理位置
//var provider = new MultipartFormDataStreamProvider(root);
#endregion
string root = AppDomain.CurrentDomain.BaseDirectory;
var provider = new MultipartFormDataMemoryStreamProvider();
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{//接收文件
Trace.WriteLine(file.Headers.ContentDisposition.FileName);//获取上传文件实际的文件名
Trace.WriteLine("Server file path: " + file.LocalFileName);//获取上传文件在服务上默认的文件名
var stream = ((MultipartFileDataStream)file).Stream;
using (StreamWriter sw = new StreamWriter(Path.Combine(root, file.Headers.ContentDisposition.FileName)))
{
stream.CopyTo(sw.BaseStream);
sw.Flush();
}
}
foreach (var key in provider.FormData.AllKeys)
{//接收FormData
dic.Add(key, provider.FormData[key]);
Console.WriteLine($"Key:{key} Value:{provider.FormData[key]}");
}
}
catch(Exception ex)
{
throw;
}
https://www.cjavapy.com/article/2131/
1、文件读写操作
using System;
using System.IO;
using System.Text;
class Test
{
public static void Main()
{
string path = @"c:\temp\MyTest.txt";
try
{
// 创建文件,如果文件存在则覆盖该文件。
using (FileStream fs = File.Create(path))
{
byte[] info = new UTF8Encoding(true).GetBytes("https://www.cjavapy.com");
// 向文件添加一些信息。
fs.Write(info, 0, info.Length);
}
// 打开流并读取它。
using (StreamReader sr = File.OpenText(path))
{
string s = "";
while ((s = sr.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
2、使用HttpClient实现请求提交上传的文件
static async Task Main(string[] args)
{
await TryUpload();
}
private const string Boundary = "EAD567A8E8524B2FAC2E0628ABB6DF6E";
private static readonly HttpClient HttpClient = new()
{
BaseAddress = new Uri("https://localhost:5001/")
};
private static async Task TryUpload()
{
var requestContent = new MultipartFormDataContent(Boundary);
requestContent.Headers.Remove("Content-Type");
requestContent.Headers.TryAddWithoutValidation("Content-Type", $"multipart/form-data; boundary={Boundary}");
var fileContent = await File.ReadAllBytesAsync(@"C:\\cjavapy.png");
var byteArrayContent = new ByteArrayContent(fileContent);
byteArrayContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
requestContent.Add(byteArrayContent, "avatar", "Unbenannt.PNG");
var postResponse = await HttpClient.PostAsync("/api/Image", requestContent);
}
3、后台文件上传API
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
// POST api/<ImageController>
[HttpPost]
public void Post([FromForm] UserModel info)
{
var memoryStream = new MemoryStream();
info.Avatar.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
}
}
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "name")]
public string Name { get; set; }
}
/// <summary>
/// 向webapi发送请求执行导入操作
/// </summary>
/// <param name="token">身份验证token</param>
/// <param name="reauestUrl">api请求完整路径</param>
/// <param name="bytes">文件byte[]</param>
/// <param name="fileName">文件名称</param>
/// <returns></returns>
private string PostImport(string token, string reauestUrl, byte[] bytes,string fileName)
{
HttpClient httpClient = new HttpClient();
//httpClient.BaseAddress = new Uri(reauestUrl);
//httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Authorization", token);
httpClient.DefaultRequestHeaders.Add("Authorization", token);
ByteArrayContent fileContent = new ByteArrayContent(bytes);
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "file", FileName = fileName };
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(fileContent);
var result = httpClient.PostAsync(reauestUrl, content);
return result.Result.Content.ReadAsStringAsync().Result;
}
using (var client = new HttpClient())
{
using (var multipartFormDataContent = new MultipartFormDataContent())
{
var values = new[]
{
new KeyValuePair<string, string>("c", "3"),
new KeyValuePair<string, string>("c", "2"),
new KeyValuePair<string, string>("d", "2")
//other values
};
foreach (var keyValuePair in values)
{
multipartFormDataContent.Add(new StringContent(keyValuePair.Value),
String.Format("\"{0}\"", keyValuePair.Key));
}
multipartFormDataContent.Add(new ByteArrayContent(System.IO.File.ReadAllBytes(@"D:\test.jpg")),
"\"pic\"",
"\"test.jpg\"");
var requestUri = "http://localhost:8080";
var html = client.PostAsync(requestUri, multipartFormDataContent).Result.Content.ReadAsStringAsync().Result;
}
}
本篇内容不适用于MVC Core,在Core中应当使用IFormFile来获取文件,上传文件方式不同,可以通过Action参数(对应multipart/form-data)或者Request.Forms.Files(对应ajax)方式获取
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)