I am trying to load some files in my .NET MAUI application, I am using HttpClient inside my Application constructor (I know that I should be using App lifecycle events) :
public partial class App : Application
{
public App()
{
InitializeComponent();
TestAsync();
}
private async Task TestAsync()
{
HttpClient lClient = new HttpClient();
var lReponse = await lClient.GetAsync(new Uri("https://proof.ovh.net/files/1Mb.dat"));
using (var fs = new FileStream(#"C:\test.dat", FileMode.CreateNew))
{
await lReponse.Content.CopyToAsync(fs);
}
}
}
I always end up with the following error on Windows (An unhandled win32 exception occurred) on the var lReponse = await lClient.GetAsync part :
In a .NET 6 WPF project this is working fine :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TestAsync();
}
private async Task TestAsync()
{
HttpClient lClient = new HttpClient();
var lReponse = await lClient.GetAsync(new Uri("https://proof.ovh.net/files/1Mb.dat"));
using (var fs = new FileStream(#"C:\test.dat", FileMode.CreateNew))
{
await lReponse.Content.CopyToAsync(fs);
}
}
}
Is there something specific in the lifecycle of the Application class that impact async/await (something related to the SynchronizationContext ?) ?
Thanks for your help !
The error was caused by the missing MainPage initalization. In .NET MAUI you have to set MainPage in the constructor of App, this will fix my code sample :
public App()
{
InitializeComponent();
TestAsync();
MainPage = new AppShell();
}
I found out thanks to this stackoverflow, how you can handle this type of unhandled exception like so :
public App()
{
// Catch all handled exceptions in managed code, before the runtime searches the Call Stack
AppDomain.CurrentDomain.FirstChanceException += FirstChanceException;
InitializeComponent();
TestAsync();
}
private void FirstChanceException(object sender, FirstChanceExceptionEventArgs e)
{
Console.WriteLine(e.ToString());
}
Which gave me the following exception :
System.NotImplementedException: 'Either set MainPage or override CreateWindow.'
In my original code I was setting the main page after the await of my async call, which is after the constructor finished it's execution hence the error (no idea it was hide by a win32 unhandled exception though).
Related
I am trying to display messages to a list box in a WinForms application but it is not working. I am using the most recent Azure namespace, hence using asynchronous methods.
Below is Program.cs:
namespace App
{
public class Program
{
static ServiceBusClient client;
static ServiceBusProcessor processor;
public static List<string> data = new List<string>();
[STAThread]
public static async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
data.Add(body);
// complete the message. messages is deleted from the subscription.
await args.CompleteMessageAsync(args.Message);
}
public static void Main(string[] args)
{
Application.Run(new Form1());
}
public static async Task MainAsync()
{
client = new ServiceBusClient(_serviceBusConn);
// create a processor that we can use to process the messages
processor = client.CreateProcessor(_serviceBustopic, _ServiceBusSubscription, new ServiceBusProcessorOptions());
try
{
// add handler to process messages
processor.ProcessMessageAsync += MessageHandler;
// add handler to process any errors
processor.ProcessErrorAsync += ErrorHandler;
// start processing
await processor.StartProcessingAsync();
}
finally
{
await processor.DisposeAsync();
await client.DisposeAsync();
}
}
}
}
//end of Program.cs
And the Form.cs:
namespace App
{
public partial class Form1 : Form
{
public static List<string> AppNames = new List<string>();
public Form1()
{
InitializeComponent();
}
public static async Task receiveMessage()
{
await Program.MainAsync();
AppNames = Program.data;
}
public async void button1_Click(object sender, EventArgs e)
{
await receiveMessage();
for (int i = 0; i < AppNames.Count; i++)
{
listBox1.Items.Add("item" + AppNames[i].ToString());
}
}
}
}
There is a console version of this program that is functional, but I cannot seem to get it to display the messages in this Winforms Application. Some debugging showed me that the program was getting into the Main async. method upon the button being clicked, but it was not going into the Message Handler despite messages being sent through the service bus.
The pattern that you're using for the Service Bus client and processor isn't going to work in your scenario.
In MainAsync, when you call StartProcessingAsync, the method will return once the processor has started. Execution is then going to reach the finally block, where the processor and client are disposed. At that point, the processor is not running and, therefore, is not receiving messages.
Each time button1_Click runs, you're creating a new set of clients, establishing a new connection to Azure, and then immediately throwing them away.
The processor is intended to be a long-lived type that runs continuously in the background, calling back into your code as messages are available. Likewise, the client is intended to be used as a singleton for the lifetime of your application. I'd suggest reading through the Service Bus "Hello World" sample, which would help to explain some of the types and recommended patterns for use.
I have a simple WPF application that doesn't appear if api is not available. How do I handle it so screen at least shows up saying no api is available?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
GetWeather();
}
private void GetWeather()
{
using (var client = new WebClient())
{
var content = client.DownloadString($"http://localhost:50054/WeatherForecast");
var data = JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(content);
message4Label.Text = data.ToList()[0].Summary;
}
}
}
You must move the call from the constructor of the form because if there's an exception it will close the form without any message. Also, protecting the function with a try/catch is always a good idea.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if(!GetWeather())
//Present a message to the user
}
private bool GetWeather()
{
try
{
using (var client = new WebClient())
{
var content = client.DownloadString($"http://localhost:50054/WeatherForecast");
var data = JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(content);
message4Label.Text = data.ToList()[0].Summary;
}
return true;
}
catch{ return false; }
}
}
If await can be used only by async methods, how can I call a task from MainPage()?
My code sample:
public MainPage()
{
InitializeComponent();
label.Text=await Task.Run(TaskTest); //this doesn't work
}
private async Task<string> TaskTest()
{
try
{
using (WebClient client = new WebClient())
{
return await client.DownloadStringTaskAsync("https://www.example.com/return.php");
//also tried w/ no success:
//return client.DownloadStringTaskAsync("https://www.example.com/return.php").Result;
}
}
catch (Exception)
{
throw;
}
}
Avoid async void fire-and-forget methods.
Event handlers however are the only exception to that rule.
Reference Async/Await - Best Practices in Asynchronous Programming
In this case, since you want to await the task then create and event and handler that would facilitate the desired behavior
public MainPage() {
InitializeComponent();
Downloading += OnDownloading; //subscribe to event
Downloading(this, EventArgs.Empty); //raise event to be handled
}
private event EventHandler Downloading = delegate { };
private async void OnDownloading(object sender, EventArgs args) {
//Downloading -= OnDownloading; //unsubscribe (optional)
label.Text = await TaskTest(); //this works
}
private async Task<string> TaskTest() {
try {
using (WebClient client = new WebClient()) {
return await client.DownloadStringTaskAsync("https://www.example.com/return.php");
}
} catch (Exception) {
throw;
}
}
You cannot make the Main() method asynchronous and thus, you can use the await keyword in the body of the Main() function.
A simple workaround that you can implement by editing your current code is making your function TaskTest() return void so you don't have to await it's call.
Example:
public MainPage()
{
InitializeComponent();
TaskTest();
}
private async void TaskTest()
{
try
{
using (WebClient client = new WebClient())
{
label.Text = await client.DownloadStringTaskAsync("https://www.example.com/return.php");
}
}
catch (Exception)
{
throw;
}
}
Edit
In case you have to wait for the return value of an asynchronous call without using await, you could go ahead and use a while to check whether the Task has completed or not.
Task<string> accessTokenTask = Task.Run<string>(() => MethodToGetToken());
// wait until operation is done.
while(!accessTokenTask.IsCompleted)
{
accessTokenTask.Wait():
}
// once the task completes, the runtime will step out of the while loop
// and you can access your Token in the Result
string token = accessTokenTask.Result;
Hope this answers your question.
You probably shouldn't call your Task from MainPage. I started with the Visual Studio blank page and tried to do the same thing. I found an answer suggested to use await Navigation.PushModalAsync(NewPage);, and then call the task there Task.Run(async () => { await method(); }).Wait();. It worked, but not the best way to do it.
This article on CodeProject is great to help beginners to add MVVM to the blank page project. You just need to bind the ViewModel to the MainPage, and then call your Task from the ViewModel instead.
public MainPage()
{
InitializeComponent();
this.BindingContext = new MainPageViewModel(this);
}
In my mobile application (xamarin forms), I'm getting data from internet so it needs internet connection. Since I have a dictionary which I initialize in App.xaml.cs and I use data from internet, I need to check for internet connection. I have seen this question where OP asks for something similar, but the answer doesn't work for me since I need to check for internet connection whenever app launches, not after MainPage is launched. For example, Clash of Clans. Whenever the app launches, the app checks for internet connection and if there's no connection, it displays a alert to user repetitively until there's a connection.
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Collections.Generic;
using HtmlAgilityPack;
using System.Text.RegularExpressions;
using System;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Multi
{
public partial class App : Application
{
static GroupStage groupstage = new GroupStage();
public static HtmlWeb web = new HtmlWeb();
public static HtmlDocument doc = LoadUrlAndTestConnection();
//The reason why I have put a method is because I wanted to try if I can use try-catch to display alert, however this didn't work.
public static HtmlDocument LoadUrlAndTestConnection()
{
bool con = true;
while (con)
{
try
{
doc = web.Load(someURL);
}
catch (Exception ex)
{
var sth = new ErrorPage();
sth.InternetErrorDisplay();
con = true;
continue;
}
con = false;
}
return docSK;
}
public static Dictionary<string, Country> _countries = new Dictionary<string, Country>
{
["Australia"] = new Country(1, "Australia", false, "AU", "ausFlag.png", 3, groupstage, GetScore("Australia", 3)),
public static string[] GetScore(string name, int GroupID)
{
//Gets the score data from internet
}
public App()
{
InitializeComponent();
TwitchClass.MainAsync().Wait();
MainPage = new OpeningPage();
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
}
protected override void OnResume()
{
}
}
}
//GetScore method requires internet connection as it gets the score data from internet.
and the InternetErrorDisplay method is,
public void InternetErrorDisplay() => DisplayAlert("Connection Error", "Could not detect internet connection. This application requires access to internet.", "Retry");
Is it possible to have this behaviour in xamarin forms app? How can I achieve it?
Yes, why should it not be possible?
Here is an example which uses async/await
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using System.Threading.Tasks;
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace LoadingSample
{
public partial class App : Application
{
public App()
{
InitializeComponent();
//MainPage = new MainPage();
}
protected override async void OnStart()
{
// shows Loading...
MainPage = new LoadPage();
await Task.Yield();
// Handle when your app starts
// Just a simulation with 10 tries to get the data
for (int i = 0; i < 10; i++)
{
await Task.Delay(500);
// await internet_service.InitializeAsync();
await MainPage.DisplayAlert(
"Connection Error",
"Unable to connect with the server. Check your internet connection and try again",
"Try again");
}
await Task.Delay(2000);
// after loading is complete show the real page
MainPage = new MainPage();
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
I am trying to use PopModalAsync to remove the modal page. However, the Navigation.ModalStack.Count is 0. If I use PopModalAsync, it will throw an exception:
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
I am using Xamarin.Forms. Here is some sample code:
App.cs (Potable)
public class App : Application
{
public App()
{
// The root page of your application
MainPage = new View.LoginPage();
}
}
LoginPage.xaml.cs (Potable)
public partial class LoginPage : ContentPage
{
public INavigation _Navigate;
public LoginPage()
{
InitializeComponent();
_Navigate = Navigation;
}
async void LoginBtnClicked(object sender, EventArgs args)
{
await _Navigate.PushModalAsync(new AuthenicationBrowser());
//await _Navigate.PopModalAsync(); it is work at here
Debug.WriteLine("Navigation.NavigationStack LoginBtnClicked ===> {0}", Navigation.NavigationStack.Count); //getting 0
Debug.WriteLine("Navigation.ModalStack LoginBtnClicked ===> {0}", Navigation.ModalStack.Count); // getting 1
}
public async void PopModal()
{
Debug.WriteLine(Navigation.NavigationStack.Count);
await Navigation.PopModalAsync();
}
}
AuthenicationBrowser.cs (Potable) * Edited: Put PopModalAsync *
public partial class AuthenicationBrowser : ContentPage
{
public AuthenicationBrowser()
{
InitializeComponent();
}
public async void PopModal()
{
Debug.WriteLine("Navigation.ModalStack AuthenicationBrowser .PopModal===> {0}", Navigation.ModalStack.Count); // getting 0
await Navigation.PopModalAsync();
}
}
BrowserView.cs (Potable)
public class BrowserView : WebView
{
public BrowserView()
{
}
}
AuthenicationBrowserRenderer.cs (Droid) * Edited: Calling PopModal *
[assembly: ExportRenderer(typeof(BrowserView), typeof(AuthenicationBrowserRenderer))]
namespace App.Droid
{
class AuthenicationBrowserRenderer : WebViewRenderer
{
... // Doing some Auth in OnElementChanged and using JavaScriptCallBack class after received json in Webview
}
public class JavaScriptCallBack: Java.Lang.Object, IValueCallback
{
public JavaScriptCallBack()
{
}
public async void OnReceiveValue(Java.Lang.Object result)
{
Java.Lang.String json = (Java.Lang.String)result;
string raw_json = json.ToString();
Debug.WriteLine("raw_json ====>>> {0}", raw_json);
var login_page = new LoginPage();
var auth_page = new AuthenicationBrowser();
Debug.WriteLine(login_page.Navigation.ModalStack.Count); // getting 0
Debug.WriteLine(auth_page.Navigation.ModalStack.Count); // getting 0
auth_page.PopModal(); // Trying to do PopModalAsync
}
}
}
Finally, I may get the answer that App.Current.MainPage.Navigation.PopModalAsync(); can do the trick. The reason is that the new LoginPage() is called as a new Content Page not existing page.
If I call it from the App.Current.MainPage (The existing LoginPage), it can get the existing modal from Modal Stack.
So the solution can be :
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
async void LoginBtnClicked(object sender, EventArgs args)
{
await Navigation.PushModalAsync(new AuthenicationBrowser());
}
public async void PopModal()
{
Debug.WriteLine("Navigation.ModalStack PopModal ===> {0}", App.Current.MainPage.Navigation.ModalStack.Count);
await App.Current.MainPage.Navigation.PopModalAsync();
}
}
As it seems you had the wrong Navigation object. In my case I also had this wrong.
I displayed a modal page, which had it's own navigation bar (or Navigation object). So when I wanted to dismiss this modal page I got the mentioned exception, because there were no other pages on the navigation stack. It was the wrong object ...
For me I was calling PushModalAsync when I should have been calling PushAsync there was no items to push for PushModelAsync