具有取消支持 (CancellationTokenSource) 和进度报告的 PostSubmitter 的异步 CTP
各位开发者!
我有一个使用 POST 或 GET 发布到网站并阅读响应的课程。现在都是异步的,不会导致 UI 挂起。
我现在需要升级它才能处理取消。使用的所有异步方法都不接受取消令牌。我需要了解原因以及我的选择是什么。如果可能的话,我应该在类中创建 CancellationTokenSource 对象还是从 UI 中对其进行参数化?
其次,我需要公开 PostData() 方法的进度。我该怎么做呢?
类:
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Windows.Forms;
using System.Collections.Generic;
using RESTClient.Core.UploadFile;
using System.Threading;
namespace RESTClient.Core {
/// <summary>
/// Submits post data to a url.
/// </summary>
public class PostSubmitter {
#region Backing Store
private string _URL = string.Empty;
private NameValueCollection _PostValues = new NameValueCollection();
private PostTypeEnum _PostType = PostTypeEnum.GET;
#endregion
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public PostSubmitter() {
}
/// <summary>
/// Constructor that accepts a url as a parameter
/// </summary>
/// <param name="url">The url where the post will be submitted to.</param>
public PostSubmitter(string url)
: this() {
_URL = url;
}
/// <summary>
/// Constructor allowing the setting of the url and items to post.
/// </summary>
/// <param name="url">the url for the post.</param>
/// <param name="values">The values for the post.</param>
public PostSubmitter(string url, NameValueCollection values)
: this(url) {
_PostValues = values;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the url to submit the post to.
/// </summary>
public string Url {
get {
return _URL;
}
set {
_URL = value;
}
}
/// <summary>
/// Gets or sets the name value collection of items to post.
/// </summary>
public NameValueCollection PostItems {
get {
return _PostValues;
}
set {
_PostValues = value;
}
}
/// <summary>
/// Gets or sets the type of action to perform against the url.
/// </summary>
public PostTypeEnum Type {
get {
return _PostType;
}
set {
_PostType = value;
}
}
#endregion
/// <summary>
/// Posts the supplied data to specified url.
/// </summary>
/// <returns>a string containing the result of the post.</returns>
public async Task<String> Post() {
StringBuilder parameters = new StringBuilder();
for (int i = 0; i < _PostValues.Count; i++) {
EncodeAndAddItem(ref parameters, _PostValues.GetKey(i), _PostValues[i]);
}
string result = await PostData(_URL, parameters.ToString());
return result;
}
/// <summary>
/// Posts the supplied data to specified url.
/// </summary>
/// <param name="url">The url to post to.</param>
/// <returns>a string containing the result of the post.</returns>
public async Task<String> Post(string url) {
_URL = url;
return await this.Post();
}
/// <summary>
/// Posts the supplied data to specified url.
/// </summary>
/// <param name="url">The url to post to.</param>
/// <param name="values">The values to post.</param>
/// <returns>a string containing the result of the post.</returns>
public async Task<String> Post(string url, NameValueCollection values) {
_PostValues = values;
return await this.Post(url);
}
/// <summary>
/// Posts data to a specified url. Note that this assumes that you have already url encoded the post data.
/// </summary>
/// <param name="postData">The data to post.</param>
/// <param name="url">the url to post to.</param>
/// <returns>Returns the result of the post.</returns>
private async Task<String> PostData(string url, string postData) {
HttpWebRequest request = null;
if (_PostType == PostTypeEnum.POST) {
Uri uri = new Uri(url);
request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postData.Length;
using (Stream writeStream = await request.GetRequestStreamAsync()) {
UTF8Encoding encoding = new UTF8Encoding();
byte[] bytes = encoding.GetBytes(postData);
writeStream.Write(bytes, 0, bytes.Length);
}
}
else {
Uri uri = new Uri(url + "?" + postData);
request = WebRequest.Create(uri) as HttpWebRequest;
request.Method = "GET";
}
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) {
using (Stream responseStream = response.GetResponseStream()) {
using (StreamReader readStream = new StreamReader(responseStream, Encoding.UTF8)) {
return await readStream.ReadToEndAsync();
}
}
}
}
/// <summary>
/// Encodes an item and ads it to the string.
/// </summary>
/// <param name="baseRequest">The previously encoded data.</param>
/// <param name="dataItem">The data to encode.</param>
/// <returns>A string containing the old data and the previously encoded data.</returns>
private void EncodeAndAddItem(ref StringBuilder baseRequest, string key, string dataItem) {
if (baseRequest == null) {
baseRequest = new StringBuilder();
}
if (baseRequest.Length != 0) {
baseRequest.Append("&");
}
baseRequest.Append(key);
baseRequest.Append("=");
baseRequest.Append(HttpUtility.UrlEncode(dataItem));
}
public async void HttpUploadFile(string url, string file, string paramName, string contentType, NameValueCollection nvc) {
//log.Debug(string.Format("Uploading {0} to {1}", file, url));
string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
HttpWebRequest wr = WebRequest.Create(url) as HttpWebRequest;
wr.ContentType = "multipart/form-data; boundary=" + boundary;
wr.Method = "POST";
wr.KeepAlive = true;
wr.Credentials = CredentialCache.DefaultCredentials;
Stream rs = await wr.GetRequestStreamAsync();
string formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
foreach (string key in nvc.Keys) {
await rs.WriteAsync(boundarybytes, 0, boundarybytes.Length);
string formitem = string.Format(formdataTemplate, key, nvc[key]);
byte[] formitembytes = Encoding.UTF8.GetBytes(formitem);
await rs.WriteAsync(formitembytes, 0, formitembytes.Length);
}
await rs.WriteAsync(boundarybytes, 0, boundarybytes.Length);
string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n";
string header = string.Format(headerTemplate, paramName, file, contentType);
byte[] headerbytes = Encoding.UTF8.GetBytes(header);
rs.WriteAsync(headerbytes, 0, headerbytes.Length);
FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) != 0) {
await rs.WriteAsync(buffer, 0, bytesRead);
}
fileStream.Close();
byte[] trailer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
await rs.WriteAsync(trailer, 0, trailer.Length);
rs.Close();
WebResponse wresp = null;
try {
wresp = await wr.GetResponseAsync();
Stream stream2 = wresp.GetResponseStream();
StreamReader reader2 = new StreamReader(stream2);
//log.Debug(string.Format("File uploaded, server response is: {0}", reader2.ReadToEnd()));
}
catch (Exception ex) {
//log.Error("Error uploading file", ex);
if (wresp != null) {
wresp.Close();
wresp = null;
}
}
finally {
wr = null;
}
/**
NameValueCollection nvc = new NameValueCollection();
nvc.Add("id", "TTR");
nvc.Add("btn-submit-photo", "Upload");
HttpUploadFile("http://your.server.com/upload", @"C:\test\test.jpg", "file", "image/jpeg", nvc);
**/
}
public async Task<String> ExecutePostRequest(Uri url, Dictionary<string, string> postData, FileInfo fileToUpload, string fileMimeType, string fileFormKey) {
HttpWebRequest request = WebRequest.Create(url.AbsoluteUri) as HttpWebRequest;
request.Method = "POST";
request.KeepAlive = true;
String boundary = Utility.CreateFormDataBoundary();
request.ContentType = "multipart/form-data; boundary=" + boundary;
Stream requestStream = await request.GetRequestStreamAsync();
postData.WriteMultipartFormData(requestStream, boundary);
if (fileToUpload != null) {
//TODO: Need async here...
fileToUpload.WriteMultipartFormData(requestStream, boundary, fileMimeType, fileFormKey);
}
byte[] endBytes = Encoding.UTF8.GetBytes("--" + boundary + "--");
await requestStream.WriteAsync(endBytes, 0, endBytes.Length);
requestStream.Close();
using (WebResponse response = await request.GetResponseAsync()) {
using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
return await reader.ReadToEndAsync();
}
}
}
}
}
注意:最后有3个方法用于文件上传。我仍然需要弄清楚,在此之前,我需要了解取消和进度报告。
相关问题Async CTP for a PostSubmitter
任何帮助将不胜感激。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以通过采用
IProgress
和CancellationToken
参数来支持进度和取消。对于取消,请定期检查是否已通过调用 CancellationToken.ThrowIfCancellationRequested 请求取消。有关详细信息,请参阅 MSDN 上的取消。
为了进步,你需要首先决定什么样的“进步”才有意义。例如,如果“进度”只是传输的字节数,则可以使用
IProgress
。确定进度类型后,请调用IProgress.Report
报告进度。对于IProgress
有两件事需要注意:IProgress
参数可能为null
。IProgress.Report
异步运行。这意味着您必须: A) 在IProgress
中使用T
的值类型; B) 对传递给IProgress.Report
的每个T
对象执行深度复制;或 C) 每次调用IProgress.Report
时创建一个新的T
对象。You support progress and cancellation by taking
IProgress<T>
andCancellationToken
parameters.For cancellation, periodically check whether cancellation has been requested by calling
CancellationToken.ThrowIfCancellationRequested
. For more information, see Cancellation on MSDN.For progress, you need to first decide what kind of "progress" makes sense. E.g., if "progress" is just a number of bytes transferred, then you can use
IProgress<int>
. Once you've decided on your progress type, then callIProgress<T>.Report
to report the progress. There are two things to be aware of forIProgress<T>
:IProgress<T>
parameter may benull
.IProgress<T>.Report
operates asynchronously. This means that you must either: A) use a value type forT
inIProgress<T>
; B) perform a deep copy of everyT
object passed toIProgress<T>.Report
; or C) create a newT
object each time you callIProgress<T>.Report
.