fellow devs!
I have a class for posting to website using a POST or GET and read the response. It’s all Async now and doesn’t cause the UI to hang.
I need to upgrade it to handle cancellation now. All the Async methods being used are NOT accepting the cancellation token. I need to understand why and what are my alternatives. If its possible, should i create the CancellationTokenSource object within the class or parametrize it from the UI?
Secondly, I need to expose the progress of the PostData() method. How would I do that?
The class:
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();
}
}
}
}
}
Note: There are three method in the end that are for file uploading. I still need to figure then out and before I do, I need to understand the Cancellation and Progress reporting.
Related question Async CTP for a PostSubmitter
Any help would be much appreciated.
You support progress and cancellation by taking
IProgress<T>andCancellationTokenparameters.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>.Reportto report the progress. There are two things to be aware of forIProgress<T>:IProgress<T>parameter may benull.IProgress<T>.Reportoperates asynchronously. This means that you must either: A) use a value type forTinIProgress<T>; B) perform a deep copy of everyTobject passed toIProgress<T>.Report; or C) create a newTobject each time you callIProgress<T>.Report.