0

I am trying to translate a PowerShell API call to C#. For some compatibility reasons I have to use .net framework 4.7.1.

When I call the API with PowerShell code I get a clear error message "No client certificate". But when I try to do the same thing in .net 4.7.1 I only get an html error page that is not even showing the error message. How do I get the error message?

Original Powershell code:

$outfile="C:\temp\OutFile.xml"
    
#Force Tls 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    
#Clientcert
$Certificate = Get-ChildItem Cert:\LocalMachine\My\xxxxx
#$Certificate
    
$LicenseKey = "xxxxx"
Invoke-RestMethod -OutFile $outfile -Certificate $Certificate -Uri https://My-API-URL
    
LicenseKey=$LicenceKey

Powershell output:

Invoke-RestMethod : {"Status":401,"Message":"No client certificate."}
    At C:\temp\Testing_API_call.ps1:14 char:1
    + Invoke-RestMethod -OutFile $outfile -Certificate $Certificate -Uri $U ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

My C# code:

public string GetDataFromMyApi(string requestUri, X509Certificate2 certificate)
{
    string result = null;
    HttpResponseMessage response;

    HttpClientHandler handler = new HttpClientHandler();
    handler.ClientCertificates.Add(certificate);

    using (HttpClient client = new HttpClient(handler))
    {
        try
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            response = client.GetAsync(requestUri).Result;
        }
        catch (Exception ex)
        {
           throw new Exception($"Exception when getting data from API. requestUri: {requestUri} Exception: {ex.Message}");
        }
    }
        
    string responseContent;
    if (response.IsSuccessStatusCode == false)
    {
        if (response.StatusCode == HttpStatusCode.BadRequest)
        {
            Console.WriteLine(response.ToString());
        }                    
        responseContent = response.Content.ReadAsStringAsync().Result;
        throw new Exception($"API call did not return success code. ReasonPhase: {response.ReasonPhrase}. StatusCode: {response.StatusCode}. Response content: {responseContent}.");
    }
    else //response.IsSuccessStatusCode == true
    {
        try
        {
            result = response.Content.ReadAsStringAsync().Result;
        }
        catch (Exception ex)
        {
            throw new Exception($"API response could not be serialized. requestUri: {requestUri} Exception: {ex.Message}");
        }
    }

    return result;
}

The response.ToString() looks like this:

StatusCode: 400
ReasonPhrase: 'Bad Request'
Version: 1.1
Content: System.Net.Http.StreamContent
Headers:
{
    X-XSS-Protection: 1; mode=block
    X-Frame-Options: SAMEORIGIN
    Feature-Policy: accelerometer 'none';camera 'none';geolocation 'none';gyroscope 'none';magnetometer 'none';microphone 'none';payment 'none';usb 'none'
    Referrer-Policy: same-origin
    Cache-Control: private
    Date: Wed, 15 Feb 2023 14:03:35 GMT
    P3P: CP=NID DSP NOI COR, policyref=/w3c/p3p.xml
    Server: Microsoft-IIS/10.0
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Content-Length: 3525
    Content-Type: text/html; charset=utf-8
}

The response content looks (a little simplified) like this:

<!DOCTYPE html>
<html>
<head>
    <title>Runtime Error</title>
</head>
<body bgcolor="white">
    <span>
        <H1>Server Error in 'my.api' Application.<hr width=100% size=1 color=silver></H1>
        <h2> <i>Runtime Error</i> </h2>
    </span>
    <b> Description: </b>An application error occurred on the server. The current custom error settings for this application prevent the details of the application error from being viewed remotely (for security reasons). It could, however, be viewed by browsers running on the local server machine.
     <br>
     <br>
     <b>Details:</b> To enable the details of this specific error message to be viewable on remote machines, please create a &lt;customErrors&gt; tag within a &quot;web.config&quot; configuration file located in the root directory of the current web application. This &lt;customErrors&gt; tag should then have its &quot;mode&quot; attribute set to &quot;Off&quot;.<br><br>
     <b>Notes:</b> The current error page you are seeing can be replaced by a custom error page by modifying the &quot;defaultRedirect&quot; attribute of the application&#39;s &lt;customErrors&gt; configuration tag to point to a custom error page URL.<br><br>
     <br>
</body>
</html>
Dai
  • 141,631
  • 28
  • 261
  • 374
Dudute
  • 357
  • 1
  • 3
  • 11
  • 2
    You can't (meaningfully) use `GetAsync` without using `await` in an `async` method. Using `try/catch` _without_ using `await` within the `try` block is pointless. – Dai Feb 15 '23 at 14:37
  • 1
    Also, `HttpClient.GetAsync` does not throw when a `>= 400` response is returned (that's something the old `HttpWebRequest.GetResponse` method did, which was a bad design in the first place). If you _really_ want `HttpClient` to throw on `>= 400` responses then use `HttpResponseMessage.EnsureSuccessStatusCode()`. – Dai Feb 15 '23 at 14:38
  • 2
    `result = response.Content.ReadAsStringAsync().Result;` <-- [**Don't do this**](https://stackoverflow.com/a/47290354/159145): your code will likely deadlock at runtime, especially if your code is reused within a Win32 UI application (be it WinForms, WPF, "XAML" etc), you should be using `await` instead. – Dai Feb 15 '23 at 14:41
  • 1
    There isn't any error message from the connection. You are using HTTPS. HTTPS attempt to establish a secure connection using TLS which is done before the HTTP Request is sent. So you will never receive a HTTP Response with an error message. The TLS connection is performed with 4.7.1 with Net Library and may not be reported except by a connection timeout. – jdweng Feb 15 '23 at 15:23
  • So what do I need to do to get the same error message as the powershell command Invoke-RestMethod does get? A higher version of .Net? A different protocol? Powershell is using .Net too, isn't it? – Dudute Feb 16 '23 at 06:09

0 Answers0