C# 2008
I have developed the class below. I have to get the balance from the web server. Once that is done it will call back into my main app with the result.
However, sometime the web server fails for some unknown reason. Could be high volume of traffic or something else. However, I haven't implemented any exception handling in my class. As the app that uses this handles the exception.
However, the client has confirmed that when the web server does fail it displays a unhandled exception dialog box. Then they have to click continue to keep using my application.
So below I am not sure if I should implement the exception handling in my class. However, I am confused as to why the exception was not caught in my app that as below.
Many thanks for any suggestions, or if you see anything else wrong,
private void OnGetBalanceCompleted(object sender, SIPPhoneLibraryEventArgs e)
{
try
{
//If the balance starts with 'null' there has been an error trying to get the balance.
if (e.Balance.StartsWith("null"))
{
statusDisplay1.CurrentBalance = CATWinSIP_MsgStrings.BalanceError;
}
else
{
// Display the current balance and round to 2 decimal places.
statusDisplay1.CurrentBalance = Math.Round(Convert.ToDecimal(e.Balance), 2).ToString();
//If the balance is zero display in the status message
if (decimal.Parse(e.Balance) == 0)
{
this.statusDisplay1.CallStatus = "Zero Balance";
}
}
//Remove the event as no longer needed
siplibrary.GetBalanceCompletedEvent -= new EventHandler<SIPPhoneLibraryEventArgs>(OnGetBalanceCompleted);
}
catch (WebException ex)
{
MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
//Control library for all importing functions
public class Balance : IDisposable
{
//Constructor
WebClient wc;
public Balance()
{
using (wc = new WebClient())
{
//Create event handler for the progress changed and download completed events
wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
}
~Balance()
{
this.Dispose(false);
}
//Event handler and the method that handlers the event
public EventHandler<SIPPhoneLibraryEventArgs> GetBalanceCompletedEvent;
//The method that raises the event
public void OnGetBalanceCompleted(SIPPhoneLibraryEventArgs e)
{
if (GetBalanceCompletedEvent != null)
{
GetBalanceCompletedEvent(this, e);
}
}
//Get the current balance for the user that is logged in.
//If the balance returned from the server is NULL display error to the user.
//Null could occur if the DB has been stopped or the server is down.
public void GetBalance(string sipUsername)
{
//Remove the underscore ( _ ) from the username, as this is not needed to get the balance.
sipUsername = sipUsername.Remove(0, 1);
string strURL = string.Format("http://xxx.xxx.xx.xx:xx/voipbilling/servlet/advcomm.voipbilling.GetBalance?CustomerID={0}", sipUsername);
//Download only when the webclient is not busy.
if (!wc.IsBusy)
{
// Sleep for 1/2 second to give the server time to update the balance.
System.Threading.Thread.Sleep(500);
// Download the current balance.
wc.DownloadStringAsync(new Uri(strURL));
}
else
{
System.Windows.Forms.MessageBox.Show("Busy please try again");
}
}
//return and display the balance after the download has fully completed
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//Pass the result to the event handler
this.OnGetBalanceCompleted(new SIPPhoneLibraryEventArgs(e.Result));
}
//Progress state of balance.
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
//Write the details to the screen.
Console.WriteLine(e.TotalBytesToReceive);
Console.WriteLine(e.BytesReceived);
Console.WriteLine(e.ProgressPercentage);
}
//Dispose of the balance object
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
//Remove the event handlers
private bool isDisposed = false;
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
wc.DownloadProgressChanged -= new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadStringCompleted -= new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.Dispose();
}
isDisposed = true;
}
}
}
It seems that you are catching the exception on the OnGetBalanceCompleted event only, instead on the process of fetching the balance.
When there is any error on the fetching, the OnGetBalanceCompleted is not even called, that's why your exception handler is not called.
There is more information in the exception than just its Message property. You are throwing all of that information away by only displaying the Message property. Use ex.ToString() instead.
Is the code you posted part of the user interface? If not, then it has no business knowing anything about the user interface. In particular, it should not be using MessageBox.Show.
I'd remove all the UI stuff, and instead raise an event. The caller would listen to the event and do any UI work.
Related
I would like to know the best way to handle an http Request on Xamarin.Forms.
For now I was handling the request this way:
First I have a button on my forms like this:
btn_1.Clicked += (sender, e) => {
Confirm(name, password);
};
My Confirm() function checks the entrees and throws the event of the request. Also it do the logic after the request event is completed. For example:
private async void Confirm(string name, string password) {
UserController user_controller = new UserController();
if (name != null && password != null) {
User user = new User(name, password);
bool ok = user_controller.Login(user);
if(ok){
Navigation.InsertPageBefore(new NextPage(), this);
await Navigation.PopAsync();
} else {
//Show error code...
}
}
}
My UserController has two functions for each http request. The first one does the request. The second one calls the first one and handles the answer.
1º:
private async Task<HttpResponseMessage> user_login(User user){
try {
Uri uri = new Uri("http://localhost/user/login");
string user_json = JsonConvert.SerializeObject(user);
StringContent content = new StringContent(user_json, Encoding.UTF8, "application/json");
return await Utilities.client.PostAsync(uri, content).ConfigureAwait(false);
} catch {
return null;
}
}
2º:
public bool Login(User user) {
http_response = user_login(user).GetAwaiter().GetResult();
//If it doesn't reach the server...
if (http_response != null) {
//Depending of the status of the response
switch (http_response.StatusCode) {
case (System.Net.HttpStatusCode)200:
try {
string content = http_response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
Response response = JsonConvert.DeserializeObject<Response>(content);
return (bool) response.aux;
} catch {
}
break;
case (System.Net.HttpStatusCode)401:
...
break;
default:
...
break;
}
} else {
App.Current.MainPage.DisplayAlert("Error", "No server connection", "OK");
}
return false;
}
This completes my protocol for each request. My problem is:
1. I'm not sure if this is the correct way to do it
2. When I click several times the btn_1 it throws many times the request
How could I do to avoid this? I try to put a lock on my button but it doesn't work. I'm having many troubles with the asynchronous requests. I don't know which is the best way to handle the request to throw only one request at the time.
EDIT:
I have created this button extension:
public partial class LockableButton: Button {
public event EventHandler ThrowEvent;
public bool ShowLoading { get; set; }
public LockableSimpleButton() {
ShowLoading=false;
InitializeComponent();
this.Clicked += async (object sender,EventArgs e) => {
if (!Utilities.Unlocked) { return; }
Utilities.Unlocked=false;
try {
if (ShowLoading) {
await Navigation.PushModalAsync(new LoadingPopUp());
ThrowEvent(sender,e);
await Navigation.PopModalAsync();
} else {
ThrowEvent(sender,e);
}
} finally {
await Task.Delay(1000);
Utilities.Unlocked=true;
}
};
}
}
And now my buttons are like this:
btn_1.ThrowEvent += async (sender, e) => {
Navigation.InsertPageBefore(new Page(),this);
await Navigation.PopAsync(false);
};
How it is even posible that the error still persisting?
When I click several times the button it throws an error because it is trying to PopAsyc to many time the same page... It is the delay to short?
When I click several times the btn_1 it throws many times the request
This problem has nothing to do with handling an Async HTTP Request.
Here are two classic coding techniques for discarding extra button presses.
They are variations on having a flag set, and discarding any clicks that happen while that flag is set.
Common pseudo-code:
static bool _busy;
...click handler... {
if (_busy) return;
_busy = true;
// Special code as needed.
... handle the click ...
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
When you finish processing the first click, start a one-time timer. Until that timer fires, discard any additional clicks.
Pseudo-code:
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
var timer = new Timer(TimerTick, 250 milliseconds, one-time);
timer.Start();
}
}
void TimerTick() {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
//maybe timer.Stop();
}
When you start processing the first click, set a flag. Clear that flag when you are done processing. Discard any clicks that happen while that flag is set.
Pseudo-code:
// Must be `async` method, so events continue while processing.
// Otherwise, the second button press might simply be on hold,
// until after this finishes, so doesn't get suppressed.
...click handler... {
if (_busy) return;
_busy = true;
try {
... handle the click ...
} finally {
// This code must always be executed.
// If it isn't, the button action will never occur again.
_busy = false;
}
}
I'm trying to build a TAPI based phone call system using JulMar's Atapi x86. One of the functions is to pop a specific form on an inbound call. However, whenever the form pops, it comes up incorrect, as shown below (I have tried several forms as a test and they all do the same thing). There is no error, nothing in the output window to suggest what the issue is.
Code:
private void incomingcall(object sender, NewCallEventArgs e)
{
string phonenumber = e.Call.CallerId; //get the phone number of the call
SqlCommand getincoming = new SqlCommand(Querystrings.getincomingquery(), DB);
getincoming.Parameters.AddWithValue("##TELEPHONE", phonenumber);
DataTable results = new DataTable();
try
{
DB.Open();
using (var results = getincoming.ExecuteReader())
{
results.Load(results);
}
}
catch (Exception ex)
{
Inbound ib = new Inbound(phonenumber, null);
ib.Show();
}
finally
{
DB.Close();
}
if (results.Rows.Count == 1)
{
loadcontactrequest(Convert.ToInt32(results.Rows[0].ItemArray[0]), phonenumber);
}
else
{
loadinbound(phonenumber, results);
}
}
I have loaded these forms outside of this function at other points, meaning it is something to do with this function. Does anybody know where I'm going wrong?
EDIT:
private void loadcontactrequest(int ContactID, string phonenumber)
{
ContactRequest cr = new ContactRequest(ContactID, Global.loginbound("Single customer found", phonenumber));
cr.Show();
}
These functions have been tested elsewhere and work correctly individually, I believe it might be TAPI related.
EDIT 2 - Delegate:
public static void inittapi()
{
if (TestOptions.notapi)
return;
tapi = new TapiManager("Omitted");
tapi.Initialize();
foreach (TapiLine ad in tapi.Lines) //Get all lines available to this PC
{
if (ad.Name.ToUpper().Contains("Omitted"))
{
phoneline = ad;
phoneline.Open(MediaModes.All); //Open the phone line for making and receiving calls
phoneline.NewCall += new EventHandler<NewCallEventArgs>(new TAPI().incomingcall); //Add the incoming call event handler
}
}
}
It's possible that this event is triggered on a different thread than the UI thread of your application.
Modify the method like this to test whether this is the problem:
private void incomingcall(object sender, NewCallEventArgs e)
{
Form form;
if(Application.OpenForms.Count > 0)
{
form = Application.OpenForms[0];
}
if (form != null && form.InvokeRequired)
{
form.BeginInvoke(new Action(() => { incomingcall(sender, e); }));
return;
}
// Your current code goes here
}
This will identify that we are in a different thread than your main form (form) was created on and then execute the function again on the main form's thread.
We have a WPF with a tab control being the primary UI for displaying customer details, each open customer gets its own tab.
Within the customer tabs we have another tab control that allows switching between various subsets of information, 2 of these use the Webbrowser control with IHTMLChangeSink functionality to monitor for hidden divs meant to trigger logic in the app.
Previously we were experiencing a very large memory leak when a Customer tab was closed, the cause of this was found to be the event handler created by RegisterForDirtyRange. To resolve the memory leak the Dispose methods were modified to call UnregisterForDirtyRange, using AutoIT to rapidly open and close customer tabs we were able to prove that the memory leak was fixed; this was done on a developer class machine.
Once this change was rolled out to testers we started seeing the application crash, in the event log we saw that the call to UnregisterForDirtyRange was throwing a ComException with HRESULT E_FAIL. Since we never saw this come up on the developer hardware and on the testers machines there was no guaranteed way to produce the crash I am thinking that there is some kind of race condition that is amplified when run on less powerful hardware.
Given this information my question is with regards to the internal workings of the Unregister call, can anyone think of what might be causing this exception?
My initial thought was that maybe the Notify method was running at the time of dispose so I tried introducing a lock between the dispose and notify but this didn't change anything.
Here is a stripped down version of the tab control that wraps the Web Browser:
public partial class BrowserTabWidget : BrowserWidget, IHTMLChangeSink
{
private static Guid _markupContainer2Guid = typeof(IMarkupContainer2).GUID;
private IMarkupContainer2 _container;
private uint _cookie;
public BrowserTabWidget()
{
InitializeComponent();
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
Loaded += OnLoaded;
}
}
protected override void DisposeControls()
{
if (_container != null)
{
_container.UnRegisterForDirtyRange(_cookie);
Marshal.ReleaseComObject(_container);
}
WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted;
WebBrowser.Dispose();
}
public override string CurrentUri
{
get { return (string)GetValue(CurrentUriProperty); }
set
{
NavigateTo(value);
SetValue(CurrentUriProperty, value);
}
}
private void NavigateTo(string value)
{
WebBrowser.Navigate(new Uri(value));
}
public static readonly DependencyProperty CurrentUriProperty = DependencyProperty.Register("CurrentUri", typeof(string), typeof(BrowserTabWidget), new FrameworkPropertyMetadata(CurrentUriChanged));
public static void CurrentUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var widget = (BrowserTabWidget)d;
d.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => widget.NavigateTo(e.NewValue.ToString())));
}
private void InitializeWebBrowser()
{
WebBrowser.LoadCompleted += OnWebBrowserLoadCompleted;
WebBrowser.Navigate(new Uri(viewModel.InitialUrl));
}
void OnWebBrowserLoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
_container = GetMarkupContainer();
_container.RegisterForDirtyRange(this, out _cookie);
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
Load();
}
private void Load()
{
InitializeWebBrowser();
}
private IMarkupContainer2 GetMarkupContainer()
{
var oDocument = WebBrowser.Document as IHTMLDocument2;
var pDocument = Marshal.GetIUnknownForObject(oDocument);
IntPtr pMarkupContainer;
Marshal.QueryInterface(pDocument, ref _markupContainer2Guid, out pMarkupContainer);
var oMarkupContainer = Marshal.GetUniqueObjectForIUnknown(pMarkupContainer);
Marshal.Release(pDocument);
Marshal.Release(pMarkupContainer);
return (IMarkupContainer2)oMarkupContainer;
}
public void Notify()
{
var document = WebBrowser.Document as HTMLDocument;
if (document != null)
{
//Parse Dom for hidden elements and trigger appropriate event handler
}
}
}
Hmya, E_FAIL, the curse of COM. Useless to ever diagnose anything, it is just a teacher's grade for the quality of the error reporting. I wrote the same code in Winforms to get something to testable, no repro. It is nevertheless very easy to force a repro. Given that the method takes only one argument, there's only one thing that can go wrong:
if (_container != null)
{
_container.UnRegisterForDirtyRange(_cookie);
_container.UnRegisterForDirtyRange(_cookie); // Kaboom!!!
Marshal.ReleaseComObject(_container);
}
Bad cookie. Criminal that they don't return E_INVALIDARG btw.
I could not test your exact code of course, it does have problems. Most severe one I see and the repro case is that there is no protection against calling DisposeControls() more than once. In general it is never wrong to dispose objects more than once, I have no insight if that's a realistic failure mode in your project. Otherwise very simple to protect yourself against this. Including catch-and-swallow code:
protected override void DisposeControls()
{
if (_container == null) return;
try {
_container.UnRegisterForDirtyRange(_cookie);
}
catch (System.Runtime.InteropServices.COMException ex) {
if (ex.ErrorCode != unchecked((int)0x80004005)) throw;
// Log mishap...
}
finally {
Marshal.ReleaseComObject(_container);
_container = null;
_cookie = 0;
WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted;
WebBrowser.Dispose();
}
}
Another thing I noticed in my test version of your code is that you don't appear to have any protection against the browser navigating to another page by any other means than WebBrowser.Navigate(). Or the LoadCompleted event firing more than once, it does for the stackoverflow.com home page for example. Or any web page that uses frames. That's a leak. Make it resilient by having your OnWebBrowserLoadCompleted() event handler also unregister the cookie if it is set.
I am having trouble detecting the Exception that is thrown by the in-app-purchase store during Unit Test (Beta app) for Windows Phone 8 when I press the Cancel or Back button on the phone. The app simply exits.
There are no errors when I use the MockIAP. Cancel or Back Button returns an empty receipt variable during the await receipt = Store... It is handled correctly in MockIAP. But apparently Unit Test and the real app Store handleds Cancel or Back events differently. The app simply exits, which I believe because it is throwing an unhandled error.
My app is a Phonegap 2.3 and the purchase part is handled by the plugin. Unlike the MockIAP, I can't see (i.e. attach break points) what is happening on the wrapper side when Cancel or Back button is pressed during purchase. I have tried showing MessageBox.Show for every step of the purchase. The MessageBox.Show code is working when I press confirm purchase but not when I press Cancel or Back Button. I have made it synchronous already with EventWaitHandle.
In addition, I have set e.Handled = true for the unhandled Exception event to try to stop it from exit the app with no luck.
From online, my purchase code is boilerplate, so I dont' understand why other people hasn't come across this problem before, and why there are no solutions online. Does anyone have any idea how to fix this?
Purchase.cs (Plugin):
private static string receipt;
private async void purchaseProduct()
{
bool canBuy = false;
try
{
li = await Store.CurrentApp.LoadListingInformationAsync();
if (li.ProductListings.ContainsKey(package_id))
{
canBuy = true;
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(async () =>
{
// Here is the problem.. Don't know what is passed back to receipt when Cancel or Back is pressed, which is causing the app to close during Unit Test but not MockIAP
receipt = await Store.CurrentApp.RequestProductPurchaseAsync(package_id, true);
receipt = receipt.ToString();
Wait.Set();
});
Wait.WaitOne();
}
}
catch(Exception e)
{
var eMsg = e.Message.ToString();
errorMsg("Catch Exception: ", eMsg);
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
finally
{
errorMsg("Receipt with await: ", receipt);
if (canBuy && receipt!= "")
{
errorMsg("Hitting the parsing", "");
parseXML(receipt);
prepData();
httpPostData();
Store.CurrentApp.ReportProductFulfillment(package_id);
}
else
{
errorMsg("Else Finally", "");
DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR));
}
}
}
private static void errorMsg(String caption, String msg)
{
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(caption + msg);
Wait.Set();
});
Wait.WaitOne();
}
App.cs
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
EventWaitHandle Wait = new AutoResetEvent(false);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("Unhandled Exception: " + ex.Message);
Wait.Set();
});
Wait.WaitOne();
// Stop from exiting..
e.Handled = true;
if (System.Diagnostics.Debugger.IsAttached)
{
// An unhandled exception has occurred; break into the debugger
//System.Diagnostics.Debugger.Break();
}
}
to fix this enclose try/catch around RequestProductPurchaseAsync method call even though you had a try/catch for entire method...
try
{
receipt = await CurrentApp.RequestProductPurchaseAsync("MyItem", false);
}
catch (Exception){}
.... other code
I am maintaining a program with customer informations. It consists of many forms that each show some relevant info from the database. This error is in a single form after doing the following
Open the customer search form
View random customer A info in the customerinfo form
open the crm form and it automatically shows customer A. Then add a file to him via draganddrop.
Close the last two forms and select random customer B and do the same.
Close the last two forms and select customer A and add a new file. Error!!!
Here is the code that fails:
private void FireFileCountChanged() {
if (FileCountChanged != null)
BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2); // FAILS
"An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Additional information: Invoke or BeginInvoke cannot be called on a control until the window handle has been created."
I tried adding the following:
private void FireFileCountChanged() {
if (FileCountChanged != null && this.Handle != null) // CHANGED AND FAILS.
BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);
}
But the this.handle gives:
'this.Handle' threw an exception of type 'System.ObjectDisposedException' and
"Cannot access a disposed object.\r\nObject name: 'AttachmentsControl'."
Then I added a timeout of 10 seconds as the first line in the method, but the handle is still not created. Has the handle somehow been disposed when one of the window were closed? And what can be done about this? Any help is appreciated. I'm kind of stuck.
private void FireFileCountChangedDeferred(int repostCount) {
if (FileCountChanged != null) {
if (repostCount > 0) {
//black magic is somehow involved in getting this event to fire *after* the filewatcher reports the change.
System.Threading.Thread.Sleep(10);
BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), repostCount - 1);
} else
FileCountChanged(this, null);
}
}
private void CopyFiles(string[] files, bool reload) {
if (CreatePath()) {
foreach (string src in files) {
try {
string dest = MakeSafeFilename(src);
File.Copy(src, dest);
FireFileCountChanged();
} catch (Exception ex) {
//Util.Print("Copy ex: {0}", ex.Message);
ErrMsg("Error while copying:{1}{0}", ex.Message, environment.NewLine);
}
}
}
}
private void Lstv_DragDrop(object sender, DragEventArgs ea) {
if (m_CanAdd) {
string[] files = GetDraggedFiles(ea);
if (files != null)
CopyFiles(files, true);
else if (OutlookDataObject.HoldsOutlookData(ea) && CreatePath()) {
try {
OutlookDataObject.CopyDroppedFiles(ea, m_Path, OutlookFilenameCallback);
} catch (Exception ex) {
//Util.Print("Copy ex: {0}", ex.Message);
ErrMsg("Error copying from Outlook:{1}{0}", ex.Message, Environment.NewLine);
}
}
}
}
Solution
private void FireFileCountChanged() {
while (!this.IsHandleCreated) // added
System.Threading.Thread.Sleep(100); //added
if (FileCountChanged != null)
BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);
You need to check the IsHandleCreated property, not compare the Handle to null. Reading the Handle property is considered a UI operation itself.
private void FireFileCountChanged() {
if (FileCountChanged != null && this.IsHandleCreated)
BeginInvoke(new DeferEvent(FireFileCountChangedDeferred), 2);
}
However, based on the complex steps that you need to take to reproduce the bug, I suspect that there are some form instance re-use issues or other more complex issues at play here, and it isn't just a matter of making this call to BeginInvoke work.