Hi everyone im trying to create a method that will always return a url source, if for example internet goes off it will continue working until it comes up and return the url source and so on if something else occurs. So far in my method when i "turn off" the internet and "turn it on" back procedures continue normaly but im having an issue when a timeout occurs and im "falling" in a loop i know that the while(true) is not the right approach but im using it for my tests.
So how can i skip the timeout exception and "retry" my method?
public static async Task<string> GetUrlSource(string url)
{
string source = "";
while (true)
{
HttpWebRequest hwr = (HttpWebRequest)WebRequest.Create(url);
hwr.AllowAutoRedirect = true;
hwr.UserAgent = UserAgent;
hwr.Headers.Add(hd_ac_lang[0], hd_ac_lang[1]);
hwr.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
hwr.Timeout = 14000;
try
{
using (var response = hwr.GetResponse())
{
using (var reader = new StreamReader(response.GetResponseStream()))
{
source = await reader.ReadToEndAsync();
if (check_source(source))
{
return source;
}
}
}
}
catch (WebException ex)
{
hwr.Abort();
if (ex.Status == WebExceptionStatus.ProtocolError)
{
if (((HttpWebResponse)ex.Response).StatusCode == HttatusCode.NotFound)
{
// handle the 404 here
return "404";
}
}
else
{
Console.WriteLine(ex.Status.ToString());
}
}
}
}
Note: i used to have the hwr.Abort(); into a finnaly clause but it didnt help.
Edit: the console is writting this message every 14 seconds as my timeout i think its something related with that.
ِAn alternative solution to get rid of timeout problem can be is to use WebBrowser component and to navigate to the required url(webbrowser1.Navigate(url);) ,and to wait in a loop until the documentcompleted event is raised and then to get the source code by this line :
string source = webbrowser1.DocumentText;
Well it seems that i found a solution that was related with the service point of the request.
So in my catch when a timeout occurs im using this to release the connection.
hwr.ServicePoint.CloseConnectionGroup(hwr.ConnectionGroupName);
I'll keep this updated.
Related
I'm new to C# .Net and Visual Studio 2022 - What I'm trying to achieve is to have a timer running every second to check that a website url is valid/is up. If the url IS reachable and the current WebView2 is not showing that website, then it should navigate to it. If it's already showing that website, it should do nothing else. If it was showing that website, but now it's no longer valid, the WebView should navigate to my custom error page. If whilst on the custom error page the website becomes available again, it should (re)load the website.
In my particular scenario I'm making a webView load localhost (127.0.0.1) for now. I want to continuously check the website is ip, and if it goes down, show custom error, if it comes back, show the website.
Not sure I'm explaining that very well. From the research I have done, I believe I need Task and also await using async method.
Here's my current timer and checkurl code as well as navigtionstarted and navigationcompeted:
private void webView_NavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e)
{
timerCheckRSLCDURL.Enabled = false;
}
private void webView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
Debug.WriteLine("JT:IsSuccess");
((Microsoft.Web.WebView2.WinForms.WebView2) sender).ExecuteScriptAsync("document.querySelector('body').style.overflow='hidden'");
}
else if (!e.IsSuccess)
{
Debug.WriteLine("JT:IsNOTSuccess");
webView.DefaultBackgroundColor = Color.Blue;
//webView.CoreWebView2.NavigateToString(Program.htmlString);
}
timerCheckRSLCDURL.Enabled = true;
}
private void timerCheckRSLCDURL_Tick(object sender, EventArgs e)
{
Debug.WriteLine("Timer Fired! Timer.Enabled = " + timerCheckRSLCDURL.Enabled);
CheckURL(Properties.Settings.Default.URL, Properties.Settings.Default.Port);
}
private async void CheckURL(string url, decimal port)
{
timerCheckRSLCDURL = false;
Program.isWebSiteUp = false;
string webViewURL = BuildURL();
Debug.WriteLine("Checking URL: " + webViewURL);
try
{
var request = WebRequest.Create(webViewURL);
request.Method = "HEAD";
var response = (HttpWebResponse) await Task.Factory.FromAsync < WebResponse > (request.BeginGetResponse, request.EndGetResponse, null);
if (response.StatusCode == HttpStatusCode.OK)
{
Program.isWebSiteUp = true;
}
}
catch (System.Net.WebException exception)
{
Debug.WriteLine("WebException: " + exception.Message);
if (exception.Message.Contains("(401) Unauthorized"))
{
Program.isWebSiteUp = false;
}
else
{
Program.isWebSiteUp = false;
} // This little block is unfinished atm as it doesn't really affect me right now
}
catch (Exception exception)
{
Debug.WriteLine("Exception: " + exception.Message);
Program.isWebSiteUp = false;
}
if (Program.isWebSiteUp == true && webView.Source.ToString().Equals("about:blank"))
{
Debug.WriteLine("JT:1");
Debug.WriteLine("isWebSiteUp = true, webView.Source = about:blank");
webView.CoreWebView2.Navigate(webViewURL);
}
else if (Program.isWebSiteUp == true && !webView.Source.ToString().Equals(webViewURL))
{
Debug.WriteLine("JT:2");
Debug.WriteLine("isWebSiteUp = true\nwebView.Source = " + webView.Source.ToString() + "\nwebViewURL = " + webViewURL + "\nWebView Source == webViewURL: " + webView.Source.ToString().Equals(webViewURL) + "\n");
webView.CoreWebView2.Navigate(webViewURL);
}
else if (Program.isWebSiteUp == false && !webView.Source.ToString().Equals("about:blank"))
{
Debug.WriteLine("JT:3");
Debug.WriteLine("This SHOULD be reloading the BSOD page!");
webView.CoreWebView2.NavigateToString(Program.htmlString);
}
}
private string BuildURL()
{
string webViewURL;
string stringURL = Properties.Settings.Default.URL;
string stringPort = Properties.Settings.Default.Port.ToString();
string stringURLPORT = $ "{stringURL}:{stringPort}";
if (stringPort.Equals("80"))
{
webViewURL = stringURL;
}
else
{
webViewURL = stringURLPORT;
}
if (!webViewURL.EndsWith("/"))
{
webViewURL += "/";
}
//For now, the URL will always be at root, so don't need to worry about accidentally
//making an invalid url like http://example.com/subfolder/:port
//although potentially will need to address this at a later stage
Debug.WriteLine("BuildURL returns: " + webViewURL);
return webViewURL;
}
So the timer is fired every 1000ms (1 second) because I need to actively check the URL is still alive. I think the way I'm controlling the timer is wrong - and I imagine there's a better way of doing it, but what I want to do is this...
Check website URL every 1 second
To avoid repeating the same async task, I'm trying to disable the timer so it does not fire a second time whilst the async checkurl is running
Once the async/await task of checking the url has finished, the timer should be re-enabled to continue monitoring is the website url is still up
If the website is down, it should show my custom error page (referred to as BSOD) which is some super basic html loaded from resources and 'stored' in Program.htmlString
if the the website is down, and the webview is already showing the BSOD, the webview should do nothing. The timer should continue to monitor the URL.
if the website is up and the webview is showing the BSOD, then it should navigate to the checked url that is up. If the website is up, and the webview is already showing the website, then the webview should do nothing. The timer should continue to monitor the URL.
From other research, I'm aware I shouldn't be using private async void - eg shouldn't be using it as a void. But I've not yet figured out / understood the correct way to do this
In the Immediate Window, it appears that webView_NavigationCompleted is being fired twice (or sometimes even a few times) instantly as the immediate window output will show JT:IsSuccess or JT:IsNOTSuccess a few times repeated in quick succession. Is that normal? I'm assuming something isn't correct there.
The main problem appears to be due to the timer being only 1 second. If I change the timer to fire every 30 seconds for example, it seems to work ok, but when it's every second (I may even need it less than that at some point) it's not really working as expected. Sometimes the BSOD doesn't load at all for example, as well as the webView_NavigationCompleted being fire multiple times in quick succession etc.
Could someone pretty please help me make this code better and correct.
I've searched countless websites etc and whilst there is some good info, some of it seems overwhelming / too technical so to speak. I had to lookup what "antecedent" meant earlier as it's a completely new word to me! :facepalm:
Many thanks inadvance
This answer will focus on the Task timer loop to answer the specific part of your question "check a url is valid every second". There are lots of answers about how to perform the actual Ping (like How do you check if a website is online in C#) and here's the Microsoft documentation for Ping if you choose to go that route.
Since it's not uncommon to set a timeout value of 120 seconds for a ping request, it calls into question whether it would have any value to do this on a steady tick of one second. My suggestion is that it would make more sense to:
Make a background thread
Perform a synchronous ping (wait for the result) on the background thread.
Marshal the ping result onto the UI thread to perform the other tasks you have laid out.
Synchronously wait a Task.Delay on the background thread before performing the next ping.
Here is how I personally go about doing that in my own production code:
void execPing()
{
Task.Run(() =>
{
while (!DisposePing.IsCancellationRequested)
{
var pingSender = new Ping();
var pingOptions = new PingOptions
{
DontFragment = true,
};
// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=net-6.0#examples
// Create a buffer of 32 bytes of data to be transmitted.
string data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
try
{
// https://stackoverflow.com/a/25654227/5438626
if (Uri.TryCreate(textBoxUri.Text, UriKind.Absolute, out Uri? uri)
&& (uri.Scheme == Uri.UriSchemeHttp ||
uri.Scheme == Uri.UriSchemeHttps))
{
PingReply reply = pingSender.Send(
uri.Host,
timeout, buffer,
pingOptions);
switch (reply.Status)
{
case IPStatus.Success:
Invoke(() => onPingSuccess());
break;
default:
Invoke(() => onPingFailed(reply.Status));
break;
}
}
else
{
Invoke(() => labelStatus.Text =
$"{DateTime.Now}: Invalid URI: try 'http://");
}
}
catch (Exception ex)
{
// https://stackoverflow.com/a/60827505/5438626
if (ex.InnerException == null)
{
Invoke(() => labelStatus.Text = ex.Message);
}
else
{
Invoke(() => labelStatus.Text = ex.InnerException.Message);
}
}
Task.Delay(1000).Wait();
}
});
}
What works for me is initializing it when the main window handle is created:
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!(DesignMode || _isHandleInitialized))
{
_isHandleInitialized = true;
execPing();
}
}
bool _isHandleInitialized = false;
Where:
private void onPingSuccess()
{
labelStatus.Text = $"{DateTime.Now}: {IPStatus.Success}";
// Up to you what you do here
}
private void onPingFailed(IPStatus status)
{
labelStatus.Text = $"{DateTime.Now}: {status}";
// Up to you what you do here
}
public CancellationTokenSource DisposePing { get; } = new CancellationTokenSource();
Example 404:
I've encountered a few dificulties trying to handle some errors in the best way possible. One of my cases for example is NullReferenceException.
To be more clear let me explain it in a few words. When I make a call to a server to receive some pieces of information, in some cases the server might have some problems and it will return of course null.
What I've done is I display an alert to let the user know that he can try later again. After this I try to send him in the previous page for example. And after all this my app still crushes.
What I'd like to do is to simply display the alert and then let the user stay in the same page without the application crushing.
These are a few pieces of my code:
tasks.cs
public async Task<List<Idea>> GetIdeaAsync(string accesToken)
{
List<Idea> ideas = null;
try
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesToken);
var json = await client.GetStringAsync("http://www.getdata.de/api/ideas/");
var ideas = JsonConvert.DeserializeObject<List<Idea>>(json);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Server Error", "There has been an server error. Please try later.", "OK");
if (ideas == null)
{
await Application.Current.MainPage.Navigation.PopAsync(); //actually I would like to stay in the same page
}
}
return ideas;
}
view.xaml.cs
private async void Button_Clicked(object sender, EventArgs e)
{
Tasks ts = new Tasks();
var ideas = await ts.GetIdeasAsync();
if (ideas == null)
{
Debug.WriteLine("hello");
//do nothing since the display alert is already shown
}
else
{
//code here
}
I would really appreciate if anyone can guide me to a "best-practice" approach. Thanks :)
You are declaring ideas in the try block, and then trying to access it in the catch block, where it is out of scope. (Visual Studio should give an Intellisense error)
Also, whenever manipulating the UI, you should always do it on the main thread. so move your DisplayAlert() code into
Device.BeginInvokeOnMainThread(async () =>
{
// await DisplayAlert(); move it into here
});
In addition, any PopAsync or PushAsync calls should also be done on the main UI thread. But calling PopAsync after an asynchronous call to an API not be a good idea, as the user may have already pressed the back button by the time the call returns.
As for the NullReferenceException, check to see if json is null before passing it to the DeserializeObject() function.
The problem it was pretty obvious actually since I after catch-ing the exception I would later continue with the code. So what I did was :
public async Task<List<Idea>> GetIdeaAsync(string accesToken)
{
List<Idea> ideas = null;
try
{
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accesToken);
var json = await client.GetStringAsync("http://www.getdata.de/api/ideas/");
var ideas = JsonConvert.DeserializeObject<List<Idea>>(json);
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Server Error", "There has been an server error. Please try later.", "OK");
if (ideas == null)
{
//actually I would like to stay in the same page
return null; //-- added this line
}
}
return ideas;
}
Maybe it's not the best idea but it's working for me. Any other approach would be highly appreciated. :)
My app produces the following error randomly. I havent been able to re-produce it on my machine, but on users who have installed it, it happens to them.
System.Net.WebException: The request was aborted: The request was canceled.
at System.Net.ConnectStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.IO.StreamReader.ReadBuffer()
at System.IO.StreamReader.ReadToEnd()
Whats odd is, ReadToEnd() cant product a WebException error (Found out by hovering over it and seeing what type of exceptions it can cause), yet from this Crash Dump it is?, To make sure I even put a WebException try catch and it still happens.
I read online a tiny bit and see it might be caused by ServicePointManager.DefaultConnectionLimit so I added that in with 1000 int value, and now im not sure if it fixed it - I havent seen any reports, but that doesnt mean its not happening.
using (HttpWebResponse resp = (HttpWebResponse)r.GetResponse())
using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
string s = "";
try { s = sr.ReadToEnd(); }
catch (IOException) { return "2"; }
catch (WebException) { return "2"; }
}
This is the code im using, if needed, I can provide r's values. Just know that I use quite a lot.
EDIT: I can confirm on the client's side that even with the ServicePointManager.DefaultConnectionLimit set to 1000 it still occurs.
I had a similar problem to this some time ago. Can you handle the WexException doing something like this:
public static HttpWebResponse GetHttpResponse(this HttpWebRequest request)
{
try
{
return (HttpWebResponse) request.GetResponse();
}
catch (WebException ex)
{
if(ex.Response == null || ex.Status != WebExceptionStatus.ProtocolError)
throw;
return (HttpWebResponse)ex.Response;
}
}
I borrowed the code above from the answerer here: HttpWebRequest.GetResponse throws WebException on HTTP 304
Then the first line of your code would do this:
using (HttpWebResponse resp = GetHttpResponse(r))
Found out what managed to fix it, no idea WHY, but this works:
try
{
using (HttpWebResponse resp = httpparse.response(r))
{
if(resp != null)
{
using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
string s = "";
try { s = sr.ReadToEnd(); }
catch (IOException) { return "2"; }
}
} else
{
return "2";
}
}
}
catch (WebException)
{
return "2";
}
Makes no sense, the error occurs at sr.ReadToEnd(), yet putting the Try Catch over the response() makes it fixed?
I'm using this code, to fetch the latest version of my app in *Form1_Load*:
string result1 = null;
string url1 = "http://site.com/version.html";
WebResponse response1 = null;
StreamReader reader1 = null;
try
{
HttpWebRequest request1 = (HttpWebRequest)WebRequest.Create(url1);
request1.Method = "GET";
response1 = request1.GetResponse();
reader1 = new StreamReader(response1.GetResponseStream(), Encoding.UTF8);
result1 = reader1.ReadToEnd();
}
catch (Exception ex)
{
// show the error if any.
}
finally
{
if (reader1 != null)
reader1.Close();
if (response1 != null)
response1.Close();
}
The problem is that when I shut the server down the whole application is stucking and a window is popping out,saying:
Unable to connect to the remote server
Which seems legit.
Is there a way to bypass this crash (when the server is down) and break out of the version checking?
Add an additional catch block that catches the specific Exception type that you're seeing... the code will look like...
try
{
//*yadda yadda yadda*
}
catch (System.Net.WebException WebEx)
{
//*Correctly set up a situation where the rest of your program will know there was a connection problem to the website.*
}
catch (Exception ex)
{
//*Do the error catching you do now*
}
finally
{
//*yadda yadda*
}
This construction will allow you to handle WebExceptions differently from other kinds of exceptions: note that all Exceptions derive from one base class, Exception, and you can make your own for uses like this.
I have some C# code that pulls down a remote website using the HttpWebRequest class. I'm handling errors with a try/catch, but some errors (like Webrequest and IOException) don't seem to be getting "caught" with the way I have it setup:
try
{
StartScrap("http://www.domain.com");
}
catch (Exception ex)
{
LogError(ex.ToString();
}
private void StartScrap(string url)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();
if (response.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = response.GetResponseStream();
string responseText = String.Empty;
using (StreamReader readerStream = new StreamReader(responseStream, System.Text.Encoding.UTF8))
{
responseText = readerStream.ReadToEnd(); <-- I will sometimes get a Webexception error here that won't get caught above and stops the code
}
}
}
Update: There is more to the code, so maybe it is something outside of the code I posted? I am basically using this code in a Windows Application on a form that has a NotifyIcon. I'm using the Timer class to run the code at a certain timer interval. This is how I have it setup:
public TrayIcon()
{
InitializeComponent();
}
private void TrayIcon_Load(object sender, EventArgs e)
{
try
{
StartScrap("http://www.domain.com");
}
catch (Exception ex)
{
LogError(ex.ToString());
}
finally
{
StartTimer();
}
}
private void StartTimer()
{
Timer Clock = new Timer();
Clock.Interval = 600000;
Clock.Start();
Clock.Tick += new EventHandler(TrayIcon_Load);
}
What exactly do you mean by "stops the code"? Are you running in a debugger by any chance? My guess is that if you run outside the debugger - or just hit "run" again in the debugger - you'll get into the catch block with no problems. Alternatively, go into the debugger settings and change at which point the debugger steps in.
Of course, if this isn't happening in the debugger, we just need more information about exactly what you're seeing.
Could it be that LogError is throwing an exception?
Frankly speaking I am not sure what exactly happening but I will suggest you to go with ELMAH.(Error Logging Modules and Handlers)
Here is a step by step How to for ELMAH.
Nevermind, I found out I was calling the wrong function my Timer class and it was bypassing the event handler.