Get network error status from exception thrown by httpClient in Blazor?

Viewed 603

I'm building an internal tool using Blazor WASM and dotnet6, which makes calls to external APIs.

Minimalistic example in Xunit - but imagine this is running from a Blazor client

[Xunit.Theory]
[InlineData("http://www.brokenexample.com/", "blocked for mixed-content")]
[InlineData("https://www.brokenexample.com/", "ERR_NAME_NOT_RESOLVED")]
[InlineData("https://www.api.with.cors.com/", "CORS Error")]
public async Task CanGetErrorMessage(string url, string expectedError)
{
    try
    {
        var httpClient = new HttpClient();
        var httpResponseMessage = await httpClient.GetAsync(url);
    }
    catch (Exception e)
    {
        var innerError = e.Message; // somehow find the inner error
        Assert.Equal(innerError, expectedError);
    }
}

Screenshot of "blocked for mixed content" in chrome network tab: blocked for mixed-content

Screenshot of ERR_NAME_NOT_RESOLVED in chrome network tab: ERR_NAME_NOT_RESOLVED

Screenshot of CORS Error in chrome network tab: CORS Error

However, my e.Message is always

"TypeError: Failed to fetch"

And the inner exception is always

System.Runtime.InteropServices.JavaScript.JSException

With the same error message, and no further inner exception

So I'm guessing Blazor is Interopping with Javascript, which throws an exception at it - Maybe the Interop/Proxy client is catching it and just returning "TypeError: Failed to fetch"

Somehow my (chrome) browser is able to distinguish between different kind of connection errors, but the "real" exception doesn't seems to appear anywhere in my exception stack.

Sometimes there's an Http-Statuscode that is somewhat useful, but anything that fails pre-flight like CORS or connection refused seems all the same

The purpose of this tool is that a user can input their URL during runtime and test the connection, so I can't really predetermine what will happen, and if the connection will work.

It's an internal tool, so I'd like to be able to tell my end-user something like "This url didn't work because of CORS, you can fix that by downloading a chrome extension to disable cors, etc etc. And more in detailed explanation why a certain url doesn't work, and assist them into getting it to work

Any way to get the status information as shown in these screenshots?

1 Answers

There's No Exception as its Chrome

It'd be nice if there was an exception with this detail, unfortunately none out of-the-box catch the detail (I've tried).

catch (WebException exc)
{
    Console.WriteLine("Network Error: " + exc.Message + "\nStatus code: " + exc.Status);
}
catch (ProtocolViolationException exc)
{
    Console.WriteLine("Protocol Error: " + exc.Message);
}
catch (UriFormatException exc)
{
    Console.WriteLine("URI Format Error: " + exc.Message);
}
catch (NotSupportedException exc)
{
    Console.WriteLine("Unknown Protocol: " + exc.Message);
}
catch (IOException exc)
{
    Console.WriteLine("I/O Error: " + exc.Message);
}
catch (System.Security.SecurityException exc)
{
    Console.WriteLine("Security Exception: " + exc.Message);
}
catch (InvalidOperationException exc)
{
    Console.WriteLine("Invalid Operation: " + exc.Message);
}
catch (HttpRequestException exc)
{
    Console.WriteLine("Invalid Operation: " + exc.Message);
}
catch (Exception exc)
{
    Console.WriteLine("Invalid Operation: " + exc.Message);
}

Using Selenium4 to get Chrome Network & etc tabs

Fortunately with the latest release of Selenium 4 there is a way and that's using Chrome to catch it. Read more: https://www.selenium.dev/documentation/webdriver/bidirectional/chrome_devtools/ and/or this article that covers the technology with a previous version: https://rahulshettyacademy.com/blog/index.php/2021/11/04/selenium-4-key-feature-network-interception/

  1. You can download the driver here (that aligns with your version of Chrome): http://chromedriver.storage.googleapis.com/index.html

  2. Extract the ChromeDriver.exe and copy it to the Project\bin\Debug\net6.0 folder

  3. Add Nuget Package Selenium.WebDriver 4.1 (or above).

enter image description here

Here is the code which I modified from the samples in Selenium4's documentation: https://www.selenium.dev/documentation/webdriver/bidirectional/chrome_devtools/

using System.Net;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.DevTools;

// We must use a version-specific set of domains
using OpenQA.Selenium.DevTools.V94.Performance;

var url = "https://www.brokenexample.com/";

IWebDriver driver = new ChromeDriver();
IDevTools devTools = driver as IDevTools;
DevToolsSession session = devTools.GetDevToolsSession();
//await session.SendCommand<EnableCommandSettings>(new EnableCommandSettings());
var metricsResponse =
    await session.SendCommand<GetMetricsCommandSettings, GetMetricsCommandResponse>(
        new GetMetricsCommandSettings());

try
{
    driver.Navigate().GoToUrl(url);
    driver.Quit();
}
catch (Exception ex)
{
    var innerError = ex.Message;
}

Selenium4 breaking changes

A lot of examples you find online were written with the Beta version, like this one: https://dotjord.wordpress.com/2020/09/13/how-to-capture-network-activity-with-selenium-4-in-asp-net-core-3-1/

Beta (old code):

IDevTools devTools = driver as IDevTools;
DevToolsSession session = devTools.CreateDevToolsSession();
session.Network.ResponseReceived += ResponseReceivedHandler;
session.Network.Enable(new EnableCommandSettings());
driver.Navigate().GoToUrl(url);
public void ResponseReceivedHandler(object sender, ResponseReceivedEventArgs e)
{
    System.Diagnostics.Debug.WriteLine($"Status: { e.Response.Status } : {e.Response.StatusText} | File: { e.Response.MimeType } | Url: { e.Response.Url }");
}

Alpha (working code):

using DevToolsSessionDomains = OpenQA.Selenium.DevTools.V96.DevToolsSessionDomains;
var driver = new ChromeDriver();
var devTools = (IDevTools)driver;
IDevToolsSession session = devTools.GetDevToolsSession();
var domains = session.GetVersionSpecificDomains<DevToolsSessionDomains>();
domains.Network.ResponseReceived += ResponseReceivedHandler;
await domains.Network.Enable(new OpenQA.Selenium.DevTools.V96.Network.EnableCommandSettings());
driver.Navigate().GoToUrl(url);

void ResponseReceivedHandler(object sender, ResponseReceivedEventArgs e)
{
    System.Diagnostics.Debug.WriteLine($"Status: { e.Response.Status } : {e.Response.StatusText} | File: { e.Response.MimeType } | Url: { e.Response.Url }");
}
Related