Async CTP for a PostSubmitter with Cancelltion Support (CancellationTokenSource) and Progress report - winforms

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> and CancellationToken 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 call IProgress<T>.Report to report the progress. There are two things to be aware of for IProgress<T>:
The IProgress<T> parameter may be null.
IProgress<T>.Report operates asynchronously. This means that you must either: A) use a value type for T in IProgress<T>; B) perform a deep copy of every T object passed to IProgress<T>.Report; or C) create a new T object each time you call IProgress<T>.Report.

Related

WPF ListView with large collection hangs GUI

++++++ Link to example project ++++++
I have a file that can contain thousands of lines of logged messages. I am parsing this file and adding each line (as a log event) to a collection. This collection should then be shown in a ListView.
As below:
<ListView
Grid.Row="0"
Margin="5"
ItemsSource="{Binding SelectedSerilogFileLog.LogEvents}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedLogEvent}"
SelectionMode="Single">
The parsing of the file (this one contains 2500+ log events) and adding to the collection takes around 100ms. Then when the bound collection is updated with the ReplaceContent method (this suppresses the collectionchanged event firing on every item added) the GUI hangs, but I cannot see why or what can be causing this.
MainWindow.cs
...
/// <summary>
///
/// </summary>
public SerilogFileLog SelectedSerilogFileLog
{
get => selectedSerilogFileLog; set
{
if (selectedSerilogFileLog != null)
{
SelectedSerilogFileLog.OnSerilogParserFinished -= OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged -= OnSerilogParserProgressChanged;
}
selectedSerilogFileLog = value;
if (selectedSerilogFileLog != null)
{
ParserProgress = 0;
SelectedSerilogFileLog.OnSerilogParserFinished += OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged += OnSerilogParserProgressChanged;
sw.Start();
SelectedSerilogFileLog.Parse();
}
NotifyPropertyChanged(nameof(SelectedSerilogFileLog));
}
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
SelectedSerilogFileLog = null;
SelectedSerilogFileLog = new SerilogFileLog() { FilePath = "Application20210216.log" };
}
The parsing and loading of the items occurs in a separate Task.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LargeListViewTest.Classes
{
public class SerilogFileLog : INotifyPropertyChanged
{
private LogEvent lastLogEvent;
private ObservableCollectionEx<LogEvent> logEvents;
private string name;
private string description;
private string filePath;
private Regex patternMatching;
private string matchExpression = #"^(?<DateTime>[^|]+)\| (?<Level>[^|]+) \| (?<MachineName>[^|]+) \| (?<Source>[^|]+) \| (?<Message>[^$]*)$";
public delegate void SerilogParserProgressHandler(int Percentage);
public delegate void SerilogParserFinishedHandler();
/// <summary>
///
/// </summary>
public event SerilogParserProgressHandler OnSerilogParserProgressChanged;
/// <summary>
///
/// </summary>
public event SerilogParserFinishedHandler OnSerilogParserFinished;
/// <summary>
/// Gets or sets the LogEvents.
/// </summary>
public ObservableCollectionEx<LogEvent> LogEvents { get => logEvents; private set { logEvents = value; NotifyPropertyChanged(nameof(LogEvents)); } }
/// <summary>
/// Gets or sets the Name.
/// </summary>
public string Name { get => name; private set { name = value; NotifyPropertyChanged(nameof(Name)); } }
/// <summary>
/// Gets or sets the Description.
/// </summary>
public string Description { get => description; private set { description = value; NotifyPropertyChanged(nameof(Description)); } }
/// <summary>
/// Gets or sets the FilePath.
/// </summary>
public string FilePath
{
get => filePath;
set
{
filePath = value;
Name = Path.GetFileNameWithoutExtension(value);
Description = FilePath;
}
}
/// <summary>
///
/// </summary>
public SerilogFileLog()
{
LogEvents = new ObservableCollectionEx<LogEvent>();
patternMatching = new Regex(matchExpression, RegexOptions.Singleline | RegexOptions.Compiled);
}
/// <summary>
///
/// </summary>
public void Parse()
{
Task task = Task.Factory.StartNew(() => { InternalParse(); });
}
/// <summary>
///
/// </summary>
private void InternalParse()
{
OnSerilogParserProgressChanged?.Invoke(0);
try
{
if (!string.IsNullOrWhiteSpace(FilePath))
{
Console.WriteLine("Starting parse for {0}", FilePath);
long currentLength = 0;
FileInfo fi = new FileInfo(FilePath);
if (fi.Exists)
{
Console.WriteLine("Parsing Serilog file: {0}.", FilePath);
fi.Refresh();
List<LogEvent> parsedLogEvents = new List<LogEvent>();
StringBuilder sb = new StringBuilder();
using (FileStream fileStream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
while (streamReader.Peek() != -1)
{
sb.Append(streamReader.ReadLine());
LogEvent newLogEvent = ParseLogEvent(sb.ToString());
if (newLogEvent != null)
{
parsedLogEvents.Add(newLogEvent);
lastLogEvent = newLogEvent;
}
OnSerilogParserProgressChanged?.Invoke((int)(currentLength * 100 / fi.Length));
currentLength = currentLength + sb.ToString().Length;
sb.Clear();
}
}
LogEvents.ReplaceContent(parsedLogEvents);
}
Console.WriteLine("Finished parsing Serilog {0}.", FilePath);
}
}
catch (Exception ex)
{
Console.WriteLine("Error parsing Serilog." + ex.Message);
}
OnSerilogParserProgressChanged?.Invoke(100);
SerilogParserFinishedHandler onSerilogParserFinished = OnSerilogParserFinished;
if (onSerilogParserFinished == null)
return;
OnSerilogParserFinished();
}
/// <summary>
///
/// </summary>
/// <param name="mes"></param>
/// <returns></returns>
private LogEvent ParseLogEvent(string mes)
{
LogEvent logEvent = new LogEvent();
Match matcher = patternMatching.Match(mes);
try
{
if (matcher.Success)
{
logEvent.Message = matcher.Groups["Message"].Value;
DateTime dt;
if (!DateTime.TryParse(matcher.Groups["DateTime"].Value, out dt))
{
Console.WriteLine("Failed to parse date {Value}", matcher.Groups["DateTime"].Value);
}
logEvent.DateTime = dt;
logEvent.Level = matcher.Groups["Level"].Value;
logEvent.MachineName = matcher.Groups["MachineName"].Value;
logEvent.Source = matcher.Groups["Source"].Value;
}
else
{
if ((string.IsNullOrEmpty(mes) || (!Char.IsDigit(mes[0])) || !Char.IsDigit(mes[1])) && lastLogEvent != null)
{
// seems to be a continuation of the previous line, add it to the last event.
lastLogEvent.Message += Environment.NewLine;
lastLogEvent.Message += mes;
logEvent = null;
}
else
{
Console.WriteLine("Message parsing failed.");
}
if (logEvent != null)
logEvent.Message = mes;
}
}
catch (Exception ex)
{
Console.WriteLine("ParseLogEvent exception." + ex.Message);
}
return logEvent;
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
#endregion
}
}
I have an ObservableCollectionEx class that extends the default ObservableCollection, this class suppresses the collection changed event until all the items have been added/replaced.
/// <summary>
/// Adds the supplied items to the collection and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to add.</param>
public void AddRange(IEnumerable<T> items, bool notifyAfter = true)
{
if (null == items)
{
throw new ArgumentNullException("items");
}
if (items.Any())
{
try
{
SuppressChangeNotification();
CheckReentrancy();
foreach (var item in items)
{
Add(item);
}
}
finally
{
if (notifyAfter)
FireChangeNotification();
suppressOnCollectionChanged = false;
}
}
}
/// <summary>
/// Replaces the content of the collection with the supplied items and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to replace the current content.</param>
public void ReplaceContent(IEnumerable<T> items)
{
SuppressChangeNotification();
ClearItems();
AddRange(items);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!suppressOnCollectionChanged)
{
#if NoCrossThreadSupport
base.OnCollectionChanged(e);
#else
using (BlockReentrancy())
{
NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler == null)
return;
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk the invocation list
foreach (NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread
if (dispatcherObject != null && !dispatcherObject.CheckAccess())
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
else // Execute handler as is
handler(this, e);
}
}
#endif
}
}
I have tried using a List but I got the same behaviour.
Any ideas?

Can't do API call from Module in WinForms

When my startup is Module, the api call just kills the application altogether. I need my entry point to be a module. How can i accomplish this?
Module EDIDownloaderModule
Sub Main(args As String())
ProcessApi()
End Sub
Private Async Sub ProcessApi()
Dim apiUrl As String = "http://localhost:3554/MWAPI/Projects/GetProjectByCustomerAndOrderIds?customerId=abc&customerOrderId=xyz"
Dim apiResult As ApiCallResult(Of Project) = Await ApiCrudCallHelper.Get(Of Project)(apiUrl)
Dim msg As String = apiResult.Message
End Sub
End Module
When my startup is a form, i can do api calls without any issue
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ProcessApi()
End Sub
Private Async Sub ProcessApi()
Dim apiUrl As String = "http://localhost:3554/API/Projects/GetByCustomerAndOrder?customerId=abc&customerOrderId=xyz"
Dim apiResult As ApiCallResult(Of Project) = Await ApiCrudCallHelper.Get(Of Project)(apiUrl)
Dim msg As String = apiResult.Message
End Sub
End Class
And here is the Helper code for the API calls
public class ApiCallResult<X> where X : class
{
public HttpStatusCode StatusCode { get; set; }
public string ReasonPhrase { get; set; }
public bool IsError { get; set; }
public bool IsException { get; set; }
public string Message { get; set; }
public X ResponseObject { get; set; } //deserialized object, could be List, int string or just a single object
}
public static class ApiCrudCallHelper
{
/// <summary>
/// Performs Post and returns ApiCallResult
/// </summary>
/// <typeparam name="T">model to Post, could be null, T, List T</typeparam>
/// <typeparam name="X">return model by API, could be X, List X, string </typeparam>
/// <param name="data">data to post of type T, List T</param>
/// <param name="apiUrl">api full URL like http://localhost:65152/API/Test if executing custom action, provide that as well at the end </param>
/// <returns>
/// ApiCallResult
/// StatusCode: status code returned by the API
/// ReasonPhrase: reason phrase returned by the API
/// IsError: true/false
/// IsException: true/false
/// Message: error message, exception message, or result of OK etc results by API
/// X ResponseObject: model returned by the API, it might not be available in all cases. Could be X, List X or string as provided by X above
/// </returns>
public static async Task<ApiCallResult<X>> Post<T, X>(T data, string apiUrl) where X : class
{
var apiCallResult = new ApiCallResult<X> { IsError = true, Message = "No run" };
try
{
//json string
var jsonString = JsonConvert.SerializeObject(data);
using (var client = new HttpClient())
{
var httpContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
var response = await client.PostAsync(apiUrl, httpContent);
var jsonResponseString = await response.Content.ReadAsStringAsync();
//fill
if (response.IsSuccessStatusCode)
{
//deserialize
if (!typeof(X).Equals(typeof(string)))
{
apiCallResult.ResponseObject = JsonConvert.DeserializeObject<X>(jsonResponseString);
}
apiCallResult.IsError = false;
}
else
{
try
{
ApiErrorMessage myMessage = JsonConvert.DeserializeObject<ApiErrorMessage>(jsonResponseString);
if (!string.IsNullOrWhiteSpace(myMessage?.Message))
{
jsonResponseString = myMessage.Message;
}
}
catch (Exception e)
{}
jsonResponseString = jsonResponseString.Trim('"');
}
apiCallResult.StatusCode = response.StatusCode;
apiCallResult.ReasonPhrase = response.ReasonPhrase;
apiCallResult.Message = jsonResponseString;
}
}
catch (Exception ex)
{
apiCallResult.IsException = true;
apiCallResult.Message = ex.Message;
}
return apiCallResult;
}
/// <summary>
/// Performs Put and returns ApiCallResult
/// </summary>
/// <typeparam name="T">model to Post, could be null, T, List T</typeparam>
/// <typeparam name="X">return model by API, could be X, List X, string </typeparam>
/// <param name="data">data to post of type T, List T</param>
/// <param name="apiUrl">api full URL including the Id like http://localhost:65152/API/Test/12345 if executing custom action, provide that as well </param>
/// <returns>
/// ApiCallResult
/// HttpStatusCode StatusCode: status code returned by the API
/// string ReasonPhrase: reason phrase returned by the API
/// bool IsError: true/false
/// bool IsException: true/false
/// string Message: error message, exception message, or result of OK etc results by API
/// X ResponseObject: model returned by the API, it might not be available in all cases. Could be X, List X or string as provided by X above
/// </returns>
public static async Task<ApiCallResult<X>> Put<T, X>(T data, string apiUrl) where X : class
{
var apiCallResult = new ApiCallResult<X> { IsError = true, Message = "No run" };
try
{
//json string
var jsonString = JsonConvert.SerializeObject(data);
using (var client = new HttpClient())
{
var httpContent = new StringContent(jsonString, Encoding.UTF8, "application/json");
var response = await client.PutAsync(apiUrl, httpContent);
var jsonResponseString = await response.Content.ReadAsStringAsync();
//fill
if (response.IsSuccessStatusCode)
{
//deserialize
if (!typeof(X).Equals(typeof(string)))
{
apiCallResult.ResponseObject = JsonConvert.DeserializeObject<X>(jsonResponseString);
}
apiCallResult.IsError = false;
}
else
{
try
{
ApiErrorMessage myMessage = JsonConvert.DeserializeObject<ApiErrorMessage>(jsonResponseString);
if (!string.IsNullOrWhiteSpace(myMessage?.Message))
{
jsonResponseString = myMessage.Message;
}
}
catch (Exception e)
{ }
jsonResponseString = jsonResponseString.Trim('"');
}
apiCallResult.StatusCode = response.StatusCode;
apiCallResult.ReasonPhrase = response.ReasonPhrase;
apiCallResult.Message = jsonResponseString;
}
}
catch (Exception ex)
{
apiCallResult.IsException = true;
apiCallResult.Message = ex.Message;
}
return apiCallResult;
}
/// <summary>
/// Performs Delete and returns ApiCallResult
/// </summary>
/// <typeparam name="X">return model by API, could be X, List X, string. Usually you'll only get Ok result etc for delete, so specify string </typeparam>
/// <param name="apiUrl">api full URL including the Id like http://localhost:65152/API/Test/12345 if executing custom action, provide that as well </param>
/// <returns>
/// ApiCallResult
/// HttpStatusCode StatusCode: status code returned by the API
/// string ReasonPhrase: reason phrase returned by the API
/// bool IsError: true/false
/// bool IsException: true/false
/// string Message: error message, exception message, or result of OK etc results by API
/// X ResponseObject: will only be available if api is returning a model (should not), in most cases it will not be available. Could be X, List X or string as provided by X above
/// </returns>
public static async Task<ApiCallResult<X>> Delete<X>(string apiUrl) where X : class
{
var apiCallResult = new ApiCallResult<X> { IsError = true, Message = "No run" };
try
{
using (var client = new HttpClient())
{
var response = await client.DeleteAsync(apiUrl);
var jsonResponseString = await response.Content.ReadAsStringAsync();
//fill
if (response.IsSuccessStatusCode)
{
//deserialize
if (!typeof(X).Equals(typeof(string)))
{
apiCallResult.ResponseObject = JsonConvert.DeserializeObject<X>(jsonResponseString);
}
apiCallResult.IsError = false;
}
else
{
try
{
ApiErrorMessage myMessage = JsonConvert.DeserializeObject<ApiErrorMessage>(jsonResponseString);
if (!string.IsNullOrWhiteSpace(myMessage?.Message))
{
jsonResponseString = myMessage.Message;
}
}
catch (Exception e)
{ }
jsonResponseString = jsonResponseString.Trim('"');
}
apiCallResult.StatusCode = response.StatusCode;
apiCallResult.ReasonPhrase = response.ReasonPhrase;
apiCallResult.Message = jsonResponseString;
}
}
catch (Exception ex)
{
apiCallResult.IsException = true;
apiCallResult.Message = ex.Message;
}
return apiCallResult;
}
/// <summary>
/// Performs Get and returns ApiCallResult
/// </summary>
/// <typeparam name="X">return model by API, could be X, List X, string. </typeparam>
/// <param name="apiUrl">api full URL </param>
/// <returns>
/// ApiCallResult
/// HttpStatusCode StatusCode: status code returned by the API
/// string ReasonPhrase: reason phrase returned by the API
/// bool IsError: true/false
/// bool IsException: true/false
/// string Message: error message, exception message, or result of OK etc results by API
/// X ResponseObject: Could be X, List X or string as provided by X above
/// </returns>
public static async Task<ApiCallResult<X>> Get<X>(string apiUrl) where X : class
{
var apiCallResult = new ApiCallResult<X> { IsError = true, Message = "No run" };
try
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(apiUrl);
var jsonResponseString = await response.Content.ReadAsStringAsync();
//fill
if (response.IsSuccessStatusCode)
{
//deserialize
if (!typeof(X).Equals(typeof(string)))
{
apiCallResult.ResponseObject = JsonConvert.DeserializeObject<X>(jsonResponseString);
}
apiCallResult.IsError = false;
}
else
{
try
{
ApiErrorMessage myMessage = JsonConvert.DeserializeObject<ApiErrorMessage>(jsonResponseString);
if (!string.IsNullOrWhiteSpace(myMessage?.Message))
{
jsonResponseString = myMessage.Message;
}
}
catch (Exception e)
{ }
jsonResponseString = jsonResponseString.Trim('"');
}
apiCallResult.StatusCode = response.StatusCode;
apiCallResult.ReasonPhrase = response.ReasonPhrase;
apiCallResult.Message = jsonResponseString;
}
}
catch (Exception ex)
{
apiCallResult.IsException = true;
apiCallResult.Message = ex.Message;
}
return apiCallResult;
}
}
Two changes worked...
Had to change the process sub to Function
In Main introduced .Wait()
Module EDIDownloaderModule
Sub Main(args As String())
ProcessApi().Wait()
End Sub
Private Async Function ProcessApi() As Task(Of Boolean)
Dim isRun As Boolean = Await TestHelper.ProcessApi()
Return isRun
End Function
End Module

Why Isn't My Dispatcher Displaying my Status Message?

I am trying to display a status message when the delete operation starts, something like Deleting Items... so the user knows what's going on. I have a DeleteManager class that handles the display of messages when deleting any POCO:
public class DeleteManager : IDisposable
{
private readonly IStatusMessageService _status;
private readonly string _objectInstanceName;
private readonly string _objectTypeName;
private readonly string _singularFormat = "Are you sure you want to delete this {0}?";
private readonly string _pluralFormat = "Are you sure you want to delete these {0}?";
public readonly bool Confirmed;
private bool _multipleItems;
private readonly Dispatcher _dispatcher;
/// <summary>
/// Displays status messages and performs confirmation for deleting an object
/// </summary>
/// <param name="status">the instance of IStatusMessageService that will display messages</param>
/// <param name="objectInstanceName">The name of the instance of the object to delete</param>
/// <param name="objectTypeName">The type of object to delete - specify the plural if needed. </param>
/// <param name="multipleItems">Whether or not there are multiple items, to use plural strings.</param>
public DeleteManager(IStatusMessageService status, string objectTypeName, string objectInstanceName = null, bool multipleItems = false)
{
_dispatcher = Wpf.Application.Current.Dispatcher;
_status = status;
_objectInstanceName = objectInstanceName;
_objectTypeName = objectTypeName;
_multipleItems = multipleItems;
Confirmed = ConfirmDelete();
if (Confirmed)
{
var message = _multipleItems
? string.Format("Deleting multiple {0}...", _objectTypeName)
: string.Format("Deleting {0}: {1}...", _objectTypeName, _objectInstanceName);
// this is the message that doesn't display... dunno why
_dispatcher.BeginInvoke(new Action(() => _status.Status.StaticMessage(message)));
}
}
private bool ConfirmDelete()
{
var format = _multipleItems
? _pluralFormat
: _singularFormat;
var message = string.Format(format, (_objectInstanceName == null)
? _objectTypeName
: string.Format("{0} : \"{1}\"", _objectTypeName, _objectInstanceName));
return (bool)_dispatcher.Invoke(
new Func<bool>(() =>
// StatusControl is a modal window
StatusControl.ConfirmMessage("Delete Warning", message)));
}
public void Dispose()
{
if (Confirmed)
{
var message = _multipleItems
? string.Format("Multipe {0} Deleted.", _objectTypeName)
: string.Format("{0} Deleted: {1}.", _objectTypeName, _objectInstanceName);
// THIS message displays just fine.
_dispatcher.BeginInvoke(new Action(() => _status.Status.InfoMessage(message)));
}
}
}
... and it's being used like this:
var deleteId = Poco.Id;
using (var delete = new DeleteManager(StatusMessageService, "Poco", Poco.Name))
{
if (delete.Confirmed)
{
var success = _dataProvider.DeleteCurriculum(deleteId);
if (success)
{ EventAggregator.GetEvent<DeletedNotification>().Publish(deleteId); }
}
}
The InfoMessage() call in Dispose() displays just fine, but the StaticMessage() call in the constructor never displays... even if I change it to use InfoMessage() instead... I cannot determine why, nor how to fix it.

How to download shared image in Mirror API using .NET

I have subscribed timeline notification in my Glassware app. I got notification when user shared an image with Glassware contest. Now I need to download that image to my Glassware application to do the processing.
Notification notification =
new NewtonsoftJsonSerializer().Deserialize<Notification>(Request.InputStream);
String userId = notification.UserToken;
MirrorService service = new MirrorService(new BaseClientService.Initializer()
{
Authenticator = Utils.GetAuthenticatorFromState(Utils.GetStoredCredentials(userId))
});
if (notification.Collection == "timeline")
{
foreach (UserAction action in notification.UserActions)
{
if (action.Type == "SHARE")
{
TimelineItem item = service.Timeline.Get(notification.ItemId).Fetch();
//i have to download content here.
break;
}
else
{
Console.WriteLine(
"I don't know what to do with this notification: " + action.ToString());
}
}
}
Downloading an attachment is described in the reference guide:
using System;
using Google.Apis.Mirror.v1;
using Google.Apis.Mirror.v1.Data;
using System.Net;
using System.IO;
public class MyClass {
// ...
/// <summary>
/// Print an attachment's metadata.
/// </summary>
/// <param name="service">Authorized Mirror service.</param>
/// <param name="itemId">ID of the timeline item the attachment belongs to.</param>
/// <param name="attachmentId">ID of the attachment to print metadata for.</param>
public static void PrintAttachmentMetadata(
MirrorService service, String itemId, String attachmentId) {
try {
Attachment attachment = service.Timeline.Attachments.Get(itemId, attachmentId).Fetch();
Console.WriteLine("Attachment content type: " + attachment.ContentType);
Console.WriteLine("Attachment content URL: " + attachment.ContentUrl);
} catch (Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
}
}
/// <summary>
/// Download a timeline items's attachment.
/// </summary>
/// <param name="service">Authorized Mirror service.</param>
/// <param name="attachment">Attachment to download content for.</param>
/// <returns>Attachment's content if successful, null otherwise.</returns>
public static System.IO.Stream DownloadAttachment(
MirrorService service, Attachment attachment) {
try {
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
new Uri(attachment.ContentUrl));
service.Authenticator.ApplyAuthenticationToRequest(request);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK) {
return response.GetResponseStream();
} else {
Console.WriteLine(
"An error occurred: " + response.StatusDescription);
return null;
}
} catch (Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
return null;
}
}
// ...
}

The remote server returned an error: (400) Bad Request.

I am trying to post a message on a Facebook user's profile using Facebook API. It happens sometimes after authentication to send a message but the second time it always returns this error (just to mention that I use this for asp.net webapplication):
An error occurred:The remote server returned an error: (400) Bad Request.
System.Net.WebException: The remote server returned an error: (400) Bad Request. at System.Net.HttpWebRequest.GetResponse() at Facebook.FacebookAPI.MakeRequest(Uri url, HttpVerb httpVerb, Dictionary`2 args) in \wwwroot\App_Code\FacebookAPI.cs:line 185 at Facebook.FacebookAPI.Call(String relativePath, HttpVerb httpVerb, Dictionary`2 args) in \wwwroot\App_Code\FacebookAPI.cs:line 134 at Facebook.FacebookAPI.Post(String relativePath, Dictionary`2 args) in \wwwroot\App_Code\FacebookAPI.cs:line 107 at Views_Publish_to_facebook.btnSend_Click(Object sender, EventArgs e) in \wwwroot\Views\MarketingTool\Publish_to_facebook.aspx.cs:line 90
What could I do?
this is the login button
<fb:login-button autologoutlink='true' perms='read_stream,publish_stream,offline_access' onlogin='window.location.reload()'>
</fb:login-button>
Facebook C# SDK
This is my Facebook API code.
/*
* Copyright 2010 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;
namespace Facebook
{
enum HttpVerb
{
GET,
POST,
DELETE
}
/// <summary>
/// Wrapper around the Facebook Graph API.
/// </summary>
public class FacebookAPI
{
/// <summary>
/// The access token used to authenticate API calls.
/// </summary>
public string AccessToken { get; set; }
/// <summary>
/// Create a new instance of the API, with public access only.
/// </summary>
public FacebookAPI()
: this(null) { }
/// <summary>
/// Create a new instance of the API, using the given token to
/// authenticate.
/// </summary>
/// <param name="token">The access token used for
/// authentication</param>
public FacebookAPI(string token)
{
AccessToken = token;
}
/// <summary>
/// Makes a Facebook Graph API GET request.
/// </summary>
/// <param name="relativePath">The path for the call,
/// e.g. /username</param>
public JSONObject Get(string relativePath)
{
return Call(relativePath, HttpVerb.GET, null);
}
/// <summary>
/// Makes a Facebook Graph API GET request.
/// </summary>
/// <param name="relativePath">The path for the call,
/// e.g. /username</param>
/// <param name="args">A dictionary of key/value pairs that
/// will get passed as query arguments.</param>
//public JSONObject Get(string relativePath,
// Dictionary<string, string> args)
public JSONObject Get(string relativePath,
Dictionary<string, string> args)
{
return Call(relativePath, HttpVerb.GET, args);
}
/// <summary>
/// Makes a Facebook Graph API DELETE request.
/// </summary>
/// <param name="relativePath">The path for the call,
/// e.g. /username</param>
public JSONObject Delete(string relativePath)
{
return Call(relativePath, HttpVerb.DELETE, null);
}
/// <summary>
/// Makes a Facebook Graph API POST request.
/// </summary>
/// <param name="relativePath">The path for the call,
/// e.g. /username</param>
/// <param name="args">A dictionary of key/value pairs that
/// will get passed as query arguments. These determine
/// what will get set in the graph API.</param>
public JSONObject Post(string relativePath,
Dictionary<string, string> args)
{
return Call(relativePath, HttpVerb.POST, args);
}
/// <summary>
/// Makes a Facebook Graph API Call.
/// </summary>
/// <param name="relativePath">The path for the call,
/// e.g. /username</param>
/// <param name="httpVerb">The HTTP verb to use, e.g.
/// GET, POST, DELETE</param>
/// <param name="args">A dictionary of key/value pairs that
/// will get passed as query arguments.</param>
private JSONObject Call(string relativePath,
HttpVerb httpVerb,
Dictionary<string, string> args)
{
Uri baseURL = new Uri("https://graph.facebook.com");
//relativePath = "/me";
Uri url = new Uri(baseURL, relativePath);
if (args == null)
{
args = new Dictionary<string, string>();
}
if (!string.IsNullOrEmpty(AccessToken))
{
args["access_token"] = AccessToken;
}
JSONObject obj = JSONObject.CreateFromString(MakeRequest(url,
httpVerb,
args));
if (obj.IsDictionary && obj.Dictionary.ContainsKey("error"))
{
throw new FacebookAPIException(obj.Dictionary["error"]
.Dictionary["type"]
.String,
obj.Dictionary["error"]
.Dictionary["message"]
.String + " -> " + url + " " + httpVerb + " " + args);
}
return obj;
}
/// <summary>
/// Make an HTTP request, with the given query args
/// </summary>
/// <param name="url">The URL of the request</param>
/// <param name="verb">The HTTP verb to use</param>
/// <param name="args">Dictionary of key/value pairs that represents
/// the key/value pairs for the request</param>
private string MakeRequest(Uri url, HttpVerb httpVerb,
Dictionary<string, string> args)
{
if (args != null && args.Keys.Count > 0 && httpVerb == HttpVerb.GET)
{
url = new Uri(url.ToString() + EncodeDictionary(args, true));
}
//throw new Exception(url.AbsoluteUri + " ---- " + url.AbsolutePath);
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = httpVerb.ToString();
if (httpVerb == HttpVerb.POST)
{
string postData = EncodeDictionary(args, false);
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] postDataBytes = encoding.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = postDataBytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(postDataBytes, 0, postDataBytes.Length);
requestStream.Close();
}
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
try
{
//using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
//throw new Exception(response.ResponseUri + " tra " + response.Server);
{
StreamReader reader
= new StreamReader(response.GetResponseStream());
return reader.ReadToEnd();
}
}
catch (WebException e)
{
throw new FacebookAPIException("Server Error", e.Message + " resp: " + request.GetResponse() + " " + response.ResponseUri + " tra " + response.Server);
}
}
/// <summary>
/// Encode a dictionary of key/value pairs as an HTTP query string.
/// </summary>
/// <param name="dict">The dictionary to encode</param>
/// <param name="questionMark">Whether or not to start it
/// with a question mark (for GET requests)</param>
private string EncodeDictionary(Dictionary<string, string> dict,
bool questionMark)
{
StringBuilder sb = new StringBuilder();
if (questionMark)
{
sb.Append("?");
}
foreach (KeyValuePair<string, string> kvp in dict)
{
sb.Append(HttpUtility.UrlEncode(kvp.Key));
sb.Append("=");
//NOTE: This line causes problems with access_token. The url encoding messes up the access_token, so for now I'm just adding it directly
//if the key == "access_token"
//sb.Append(HttpUtility.UrlEncode(kvp.Value));
if (kvp.Key.ToLower() == "access_token")
{
sb.Append(kvp.Value);
//sb.Append(HttpUtility.UrlEncode(HttpUtility.UrlDecode(kvp.Value)));
}
else
{
sb.Append(HttpUtility.UrlEncode(kvp.Value));
}
sb.Append("&");
}
sb.Remove(sb.Length - 1, 1); // Remove trailing &
return sb.ToString();
}
}
}

Resources