Search code examples
c#winformscrashhttpclient

C# WinForms crashes with no Exception


I have a C# WinForms application.

I'm using the Unity Container nuget package to inject my service classes. If I get the service class in the Program.cs and make a call to my web api to authenticate a username and password using odata, it works successfully. If I call Application.Run(myForm), where myForm is a Telerik RadForm, and asynchronously run the same call, the application closes with no exceptions thrown.

I am using Application.ThreadException and registering Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException) but this does not catch anything.

The line that is causing the application to crash is a call to System.Net.Http.HttpClient.PostAsync(). This call is made from within the service class located in a separate .Net Standard 2.0 Class Library. The main application is a .NET Framework 4.7.2 Windows Application and the UI controls are located in a .NET Framework 4.7.2 Class Library. Again, this call is successful if called outside of the Telerik RadForm.

From within the Telerik RadForm I have tried to execute the call using:

  • private async void btnLogin_Click(object sender, System.EventArgs e)
  • var t = new Thread(() => Runner.DoWork(new Credentials(u, p, CLIENT_ID)))
  • An asynchronous call to a static method without using await

I did get a different result when I called LoginAsync(...).Result on the service class. This caused the application to enter a thread lock state, but did not crash the app.

UPDATE:

There are two RadForms; a DashboardForm and a LoginForm. Program.cs launches the Dashboard which checks for an existing Bearer token. If the token does not exist, the Dashboard displays the Login using .ShowDialog() to prevent using the Dashboard until authenticated. The Login then uses the service class described above.

If instead of launching the Dashboard in Program.cs using Application.Run() I launch the Login, the service call to authenticate is successful.

I then tried launching the Login, from the Dashboard in a new thread, but this resulted in the same problem described above.

Why does the System.Net.Http.HttpClient.PostAsync() crash the application (with no exceptions) if it is called from a Form displayed by another Form?

Program

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        WinInjectionHelper.Register(UnityConfig.RegisterComponents);

        Application.ThreadException += Application_ThreadException;
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

        Application.Run(new DashboardForm());
    }

    private static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
    {
        MessageBox.Show("Big error...");
    }

Dashboard

public partial class DashboardForm : Telerik.WinControls.UI.RadForm, IDashboard
{
    private readonly IProductService _service;

    public DashboardForm()
    {
        this._service = WinInjectionHelper.Generate2<IProductService>();
        this.InitializeComponent();
    }

    private void DashboardForm_Load(object sender, EventArgs e)
    {
        if (this.NotAuthenticated())
        {
            var frm = new LoginForm();

            if (frm.ShowDialog() != DialogResult.OK)
            {
                this.Close();
            }
        }
        else
        {
            //TODO: display error to user
            this.Close();
        }
    }
}

Login

public partial class LoginForm : Telerik.WinControls.UI.RadForm
{
    private const string CLIENT_ID = "...";
    private readonly IAuthenticationService _service;

    public LoginForm()
    {
        this._service = WinInjectionHelper.Generate2<IAuthenticationService>();
        this.InitializeComponent();
    }

    private void btnCancel_Click(object sender, System.EventArgs e)
    {
        this.DialogResult = DialogResult.Cancel;
        this.Close();
    }

    private async void btnLogin_Click(object sender, System.EventArgs e)
    {
        var u = this.txtUsername.Text.Trim();
        var p = this.txtPassword.Text.Trim();

        if (string.IsNullOrWhiteSpace(u))
        {
            MessageBox.Show("Username is required!", "NOTICE", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            return;
        }

        if (string.IsNullOrWhiteSpace(p))
        {
            MessageBox.Show("Password is required!", "NOTICE", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            return;
        }

        try
        {
            var jwt = await this._service.LoginAsync(new Credentials(u, p, CLIENT_ID));

            var identity = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, u),
                new Claim(ClaimTypes.Email, u),
                new Claim(ClaimTypes2.AccessToken, jwt.AccessToken),
            }, "JWT");

            ((GenericPrincipal)Thread.CurrentPrincipal).AddIdentity(identity);

            this.DialogResult = DialogResult.OK;

            this.Close();
        }
        catch
        {
            MessageBox.Show("An error occurred while processing your request.", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

Solution

  • I resolved the issue by calling the Login form first and then displaying the Dashboard before closing the Login and changed program.cs so that closing the Login did not exit the application. This does require calling Application.Exit() when the application does need to close.

    Program

    internal static class Program
    {
        [STAThread]
        private static void Main()
        {
            ...
    
            new LoginForm().Show();
    
            Application.Run();
        }
    }