SignalR core console client not receiving notifications - signalr

I have looked around at some of the Similar questions and didn't figure this problem out. I have a simple Hub in my .NET core Web API project. Here is the Hub:
public class NotificationHub : Hub<INotificationClient>
{
public async Task SendMessage(string user, string msg)
{
await Clients.All.ReceiveMessage(user, msg);
}
public Task SendMessageToCaller(string msg)
{
return Clients.Caller.ReceiveMessage(msg);
}
public Task SendMessageToPartner(string user, string msg)
{
return Clients.Client(user).ReceiveMessageToPartner(msg);
}
}
Here is the Interface:
public interface INotificationClient
{
Task ReceiveMessage(string user, string msg);
Task ReceiveMessage(string msg);
Task ReceiveMessageToPartner( string msg);
}
Here is the code from the controller:
[Route("[controller]")]
[ApiController]
public class NotificationsController : ControllerBase
{
private IHubContext<NotificationHub> _hub;
public NotificationsController(IHubContext<NotificationHub> hub)
{
_hub = hub;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var msg = new NotificationData { ClientId = "12345", Notification = "Somone just connected" };
await _hub.Clients.All.SendAsync("Notification", msg);
return Ok(new { Message = "Request complete" });
}
}
Lastly here is the console client code:
Console.WriteLine("Press a key to start listening");
Console.ReadKey();
Console.WriteLine("Client Listening!");
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61514/notifications")
.Build();
connection.On<NotificationData>("Notification", (notificationData) =>
Console.WriteLine($"Somebody connected: {notificationData.ClientId}"));
connection.StartAsync().GetAwaiter().GetResult();
Console.WriteLine("Listening. Press a key to quit");
Console.ReadKey();
Here is the startup of the web app with the mappings:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/Notifications");
});
}
I keep getting this error: System.IO.IOException: 'The server disconnected before the handshake could be started.' I must be missing something along the way here.
Update:
Turned on the logs and got this error:
dbug: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[8]
Establishing connection with server at 'http://localhost:61514/notifications'.
dbug: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[9]
Established connection 'BdROAEEQnGUeDYAW5EspRA' with the server.
dbug: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[7]
Starting transport 'ServerSentEvents' with Url: http://localhost:61514/notifications.
info: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[1]
Starting transport. Transfer mode: Text.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[3]
Starting receive loop.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[9]
Received 30 bytes. Parsing SSE frame.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[4]
Receive loop stopped.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[100]
Starting the send loop.
dbug: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[18]
Transport 'ServerSentEvents' started.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[102]
Send loop canceled.
dbug: Microsoft.AspNetCore.Http.Connections.Client.Internal.ServerSentEventsTransport[101]
Send loop stopped.
Unhandled exception. info: Microsoft.AspNetCore.Http.Connections.Client.HttpConnection[3]
HttpConnection Started.
info: Microsoft.AspNetCore.SignalR.Client.HubConnection[24]
Using HubProtocol 'json v1'.
System.IO.IOException: The server disconnected before the handshake could be started.
at Microsoft.AspNetCore.SignalR.Client.HubConnection.HandshakeAsync(ConnectionState startingConnectionState, CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncCore(CancellationToken cancellationToken)
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsyncInner(CancellationToken cancellationToken)
at System.Threading.Tasks.ForceAsyncAwaiter.GetResult()
at Microsoft.AspNetCore.SignalR.Client.HubConnection.StartAsync(CancellationToken cancellationToken)
at NotificationClient.Program.Main(String[] args)

So after chonking on this all day, I found out what the problem was, so I thought I'd post the solution here just in case someone else is having this problem.
The Hub Endpoint was pointing to the controller. So when the client was linking it was hitting the controller twice causing a the server to close the connection. So I changed this line:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<NotificationHub>("/Notify");
});
from "/Notifications" which was hitting the controller, to "Notify" as in the code above, re-pointed the client from Notifications to Notify
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61514/Notify")
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Debug);
})
.Build();
and messages started to flow in.

Related

Rebus Saga: Message is not arrived in correct sequence when subscriber(s) sent to publisher

Recently, we have upgraded rebus version from 1 to 5 and then after we are having problem with Saga handler. Now we are not getting response(s) in correct sequence from different subscriber(s).
We are having different sources to authenticate a request and for that we have coordinator to handle all responses from different authentication sources, but now problem is: All "SearchStarted" messages are not arrived first in coordinator from all authentication sources, due to that we can not check how many authentication sources started authenticating.
Tried to send message in different ways, like
1. Used SEND method instead of REPLY.
2. Tried with our without await keyword before sending response.
3. Tried .Wait() method with Send/Reply method(s).
AuthenticationCoordinator:
public class AuthenticationSaga : Saga<AuthenticationSagaData>, IAmInitiatedBy<AuthenticationRequest>, IHandleMessages<SearchStarted>, IHandleMessages<SearchCompleted>, IHandleMessages<AuthenticationResponse>
{
private readonly IBus _bus;
public IBus Bus
{
get { return _bus; }
}
public AuthenticationSaga(IBus bus)
{
_bus = bus;
}
public async Task Handle(AuthenticationRequest message)
{
if (!IsNew) return;
Data.Id = new Guid(MessageContext.Current.Headers[Rebus.Messages.Headers.CorrelationId]);
Data.ReturnAddress = MessageContext.Current.Headers[Rebus.Messages.Headers.ReturnAddress];
message.UniqueId = Data.Id.ToString();
Data.RequestMessage = message;
Bus.Publish(message);
}
public async Task Handle(SearchStarted message)
{
}
public async Task Handle(SearchCompleted message)
{
}
public async Task Handle(AuthenticationResponse message)
{
}
protected override void CorrelateMessages(ICorrelationConfig<AuthenticationSagaData> config)
{
config.Correlate<AuthenticationRequest>(m => m.UniqueId, d => d.Id);
config.Correlate<SearchStarted>(m => m.UniqueId, d => d.Id);
config.Correlate<AuthenticationResponse>(m => m.UniqueId, d => d.Id);
config.Correlate<SearchCompleted>(m => m.UniqueId, d => d.Id);
}
}
AuthenticationLdap:
public class AuthenticationLdapHandler : IHandleMessages
{
private readonly IBus _bus;
public IBus bus
{
get { return _bus; }
}
public AuthenticationLdapHandler(IBus bus)
{
_bus = bus;
}
public async Task Handle(AuthenticationRequest message)
{
await bus.Reply(new SearchStarted { MessageId = MessageContext.Current.Headers[Rebus.Messages.Headers.CorrelationId], UniqueId = message.UniqueId });
var response = AuthenticateLdap(message); await bus.Reply(response);
await bus.Reply(new SearchCompleted { MessageId = MessageContext.Current.Headers[Rebus.Messages.Headers.CorrelationId], UniqueId = message.UniqueId });
}
}
AuthenticationNative:
public class AuthenticationNativeHandler : IHandleMessages
{
private readonly IBus _bus;
public IBus bus
{
get { return _bus; }
}
public AuthenticationNativeHandler(IBus bus)
{
_bus = bus;
}
public async Task Handle(AuthenticationRequest message)
{
await bus.Reply(new SearchStarted { MessageId = MessageContext.Current.Headers[Rebus.Messages.Headers.CorrelationId], UniqueId = message.UniqueId });
var response = AuthenticateNative(message); await bus.Reply(response);
await bus.Reply(new SearchCompleted { MessageId = MessageContext.Current.Headers[Rebus.Messages.Headers.CorrelationId], UniqueId = message.UniqueId });
}
}
We are expecting response(s) in AuthenticationCoordinator as below sequence:
SearchStarted message from Ldap
SearchStarted message from Native
AuthenticationResponse message from Ldap
SearchCompleted message from Ldap
AuthenticationResponse message from Native
SearchCompleted message from Native
But now we are getting response(s) in below sequence:
SearchStarted message from Ldap
AuthenticationResponse message from Ldap
SearchCompleted message from Ldap
SearchStarted message from Native
AuthenticationResponse message from Native
SearchCompleted message from Native
Can we set priority to message? how we can achieve above expected responses in rebus 5.
What you're seeing is most likely a consequence of the fact that Rebus ensures that all outgoing messages are sent AFTER your handler has finished executing.
It does this by enlisting all bus operations in its transaction context, which gets committed only after your handler code is done.
This means that code like
public async Task Handle(string message)
{
await bus.Reply("this is message 1");
await Task.Delay(TimeSpan.FromSeconds(1));
await bus.Reply("this is message 2");
await Task.Delay(TimeSpan.FromSeconds(1));
await bus.Reply("this is message 3");
}
will result in messages 1, 2, and 3 being sent at the same time, when the Rebus transaction context is committed, which means that the recipient will receive them in random order.
If you messages to be sent immediately from your handler, you can "dismantle" the transaction context like this:
var transactionContext = AmbientTransactionContext.Current;
AmbientTransactionContext.SetCurrent(null);
try
{
// current transaction will never know....
await bus.Send(whee);
}
finally
{
AmbientTransactionContext.SetCurrent(transactionContext);
}
which I suggest you wrap in an implementation of IDisposable which enables usage like this:
using(new RebusTransactionContextDismantler())
{
// current transaction will never know....
await bus.Publish(whee);
}

Blazor Request blocked by CORS policy

I am trying to send a request from a Blazor(client-side) client to a server and i keep getting this error:
Access to fetch at '[route]' (redirected from '[other route]') from
origin '[origin route]' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
On the server i have already added the CORS extension in the pipeline to no avail:
Server Startup
public void ConfigureServices(IServiceCollection services) {
services.AddCors();
services.AddResponseCompression(options => {
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
MediaTypeNames.Application.Octet,
WasmMediaTypeNames.Application.Wasm,
});
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
app.UseCors(x => x.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin().AllowCredentials());
app.UseResponseCompression();
app.UseMvc();
app.UseBlazor<Client.Startup>();
}
Blazor Client request
public async Task<Catalog> GetCatalogAsync() {
try {
HttpRequestMessage message = new HttpRequestMessage {
RequestUri = new Uri(BASE_PATH + Routes.GET_CATALOG), //BASE_PATH= 172.XX.XX.XX:8600
Method = HttpMethod.Get
};
var resp = await this.client.SendAsync(message); // client is HttpClient
var resultString = await resp.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<Catalog>(resultString);
return data;
} catch (Exception ex) {
throw;
}
}
Controller
[HttpGet]
[Route(Routes.GET_CATALOG)]
public async Task<Catalog> GetCatalogAsync() {
try {
var registry = await this.adminService.GetCatalogAsync();
return registry;
} catch (Exception ex) {
throw;
}
}
POCO
[Serializeable]
public struct Catalog{
}
What else can i do to be able to reach my server? Is it due to Blazor ?
As you can see i have already added the UseCors(...).
P.S
I have published my Blazor Server project together with the Client.They are in the same directory.This folder i placed it on a computer,and i am trying from my computer to open blazor : 172.168.18.22:8600/
Update
I have also tried adding headers to my HttpRequestMessage to no avail:
HttpRequestMessage message = new HttpRequestMessage {
RequestUri = new Uri(BASE_PATH + Routes.GET_CATALOG),
Method = HttpMethod.Get,
};
message.Headers.Add("Access-Control-Allow-Origin","*");
message.Headers.Add("Access-Control-Allow-Credentials", "true");
message.Headers.Add("Access-Control-Allow-Headers", "Access-Control-Allow-Origin,Content-Type");
#Bercovici Adrian, why do you add CORS support to your App ? Do you make cross origin requests ? If you don't, don't try to solve the issue by adding unnecessary configuration that may lead to more subtle bugs.
As usual, without seeing a repo of this app, can't help you any further.
Update:
What is this UseBlazor ?
You should upgrade your app to the latest version...
New Update:
Sorry, but I'm using the current preview version of Blazor
Startup class
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBlazorDebugging();
}
**// Instead of UseBlazor**
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseStaticFiles();
app.UseRouting();
**// This configure your end points**
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
endpoints.MapFallbackToClientSideBlazor<Client.Startup>("index.html");
});
}
}
Note that I've removed the configuration of CORS as your client and server share the same domain. Please use the docs how to configure CORS appropriately.
Try this and see if it is working for you (I guess your issue is related to the configuration of the endpoints. Somehow, it seems to me that because you did not configure the endpoints, your request is redirected, and thus you get the message displayed by you above.)
Next to do is to check if your http request was appropriately cooked. But first checks the end points.
Somehow the problem was due to a very old client version that was cached on the browser.Never again will i forget to clear the browser cache after this problem.
Thank you all for your help and support !
Check that you do not send HTTP requests when running from HTTPS. For example if you send requests to http://172.168.18.22:8600 when your application was opened in https://172.168.18.22:8600 you may have an issue.
you need to specify your policy name in the middleware.
builder.Services.AddCors(policy =>{
policy.AddPolicy("Policy_Name", builder =>
builder.WithOrigins("https://*:5001/")
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyOrigin()
);});
// Configure the HTTP request pipeline.
app.UseCors("Policy_Name");

Passing JWT Token as QueryString to SignalR Hub

Trying to follow the suggestions in the link below to pass a JWT token to my SignalR hub but so far it's not working. In particular, see David Fowler's suggestion on July 22, 2017. https://github.com/aspnet/SignalR/issues/130
My frontend is React so I'm simply adding the token to the querystring as follows where _token has my JWT token value:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/myhub?AUTHORIZATION=" + _token)
.configureLogging(signalR.LogLevel.Information)
.build();
In the ConfigureServices() method of my Startup.cs, I have the following configuration for Jwt tokens:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if(context.HttpContext.WebSockets.IsWebSocketRequest)
context.Token = context.Request.Query["AUTHORIZATION"];
return Task.CompletedTask;
}
};
});
And this is what my Hub looks like:
[Authorize]
public class MyHub : Hub
{
private IBackendService _backendService;
public MyHub(IBackendService backendService)
{
_backendService = backendService;
}
public async Task SendMessage(string message)
{
// Regular SignalR stuff
// SignalR will now send the message to all connected users...
}
}
Basically, I'm getting the 401 Unauthorized error.
I put a break point where I check to see if the request is a web sockets request but I'm not hitting it. Looks like something in the pipeline is determining that the user is not authenticated.
What am I doing wrong in my code?
You can solve this by using custom middleware to handle grabbing the authentication token from the query string.
public class SignalRQueryStringAuthMiddleware
{
private readonly RequestDelegate _next;
public SignalRQueryStringAuthMiddleware(RequestDelegate next)
{
_next = next;
}
// Convert incomming qs auth token to a Authorization header so the rest of the chain
// can authorize the request correctly
public async Task Invoke(HttpContext context)
{
if (context.Request.Headers["Connection"] == "Upgrade" &&
context.Request.Query.TryGetValue("authToken", out var token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token.First());
}
await _next.Invoke(context);
}
}
public static class SignalRQueryStringAuthExtensions
{
public static IApplicationBuilder UseSignalRQueryStringAuth(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SignalRQueryStringAuthMiddleware>();
}
}
This will try to get the query string value "authToken" and it will set the heads so you can leverage your authentication middleware. You need to call this before the authentication middleware in the pipeline like so:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseSignalRQueryStringAuth();
app.UseAuthentication();
//...
}
EDIT
on a side note you should only append the token if the user is logged in:
if (accessToken) {
hubUrl += '?authToken' +'=' + accessToken;
}
this._hubConnection = new HubConnectionBuilder()
.withUrl(hubUrl)
.build();
I have also implemented this in my project. The shortest way of doing this is to add a middleware to your Configure method.
app.Use(async (context, next) =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Request.Headers["authorization"] = "Bearer " + accessToken;
}
await next.Invoke().ConfigureAwait(false);
});
It does the same thing as mentioned in the other answer. It adds the token to the header by reading it from the query string. Of course, you can separate out the implementation of custom middleware in a separate file.

Can't call javascript client method in signalr

The following code works fine in IIS Express, but failed in IIS10.
The weird thing is serverside method can successfully be invoked, however clientside method can't.
JavaScript
var hub = $.connection.liveRoomHub;
hub.client.addMessageToPage = function(data){
debugger;//here, this method never gets invoked
console.log(JSON.stringify(data));
};
$.connection.hub.start()
.done(function() {
hub.server.join('room1')
.done(function(){
debugger; //code can run into here
hub.server.sendMessage('user','test','room1');
})
});
C#
public class LiveRoomHub : Microsoft.AspNet.SignalR.Hub
{
public ILogger Logger { get; set; }
public async Task SendMessage(string name, string message, string roomName)
{
await Clients.Group(roomName)
.addMessageToPage(new
{
Name = name,
Message = message
});
Logger.Info($"{name}send msg:{message}in room:{roomName},");//logged
}
public async Task Join(string roomName)
{
await Groups.Add(Context.ConnectionId, roomName);
Logger.Info($"{Context.ConnectionId} enter room: {roomName}");//logged
}
}
All right, problem solved.
I'm using aspnetboilerplate, and abp.signalr.js automatically calls the hub connection before my JavaScript code is loaded.
Obviously, at that time, my hub.client.addMessageToPage isn't registered yet.
That's the common Connection started before subscriptions are added error.

"Could not establish trust relationship for the SSL/TLS secure channel." Exception on signalr2.2, https, .net client, custom certificate

I got some exception in testing signalr .net client with custom self-signed certificate.
There was no exception on http.
Is there any problem in setting up the self-signed certificate in my code?
Note that, there is no problem in my certificate file because it runs my https mvc sites well.
server side code : asp.net, azure local fabric
[assembly: OwinStartup(typeof(BookohWebRole.Startup))]
namespace BookohWebRole
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
//GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());
app.MapSignalR(hubConfiguration);
}
}
}
public class ChatHub : Hub
{
public override Task OnConnected()
{
Trace.TraceInformation("OnConnected");
var authToken = Context.QueryString.Get("AuthToken");
Trace.TraceInformation("authToken : " + authToken);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
Trace.TraceInformation("OnDisconnected");
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
Trace.TraceInformation("OnReconnected");
var authToken = Context.QueryString.Get("AuthToken");
Trace.TraceInformation("authToken : " + authToken);
return base.OnReconnected();
}
public void Send(string name, string message)
{
Trace.TraceInformation("Context.ConnectionId : " + Context.ConnectionId);
Clients.All.onSend(name, message);
}
}
client side code : .net client, unit test method
[TestMethod]
public void chat()
{
var ev = new AutoResetEvent(false);
Task.Run(async () =>
{
try
{
ServicePointManager.DefaultConnectionLimit = 10;
var queryString = new Dictionary<string, string>();
queryString.Add("AuthToken", Guid.NewGuid().ToString());
//https://localhost:44302/
//http://localhost:22792/
var hubConnection = new HubConnection("https://localhost:44302/");
hubConnection.Credentials = CredentialCache.DefaultCredentials;
hubConnection.AddClientCertificate(X509Certificate.CreateFromCertFile("bookoh.cer"));
hubConnection.TraceLevel = TraceLevels.All;
hubConnection.TraceWriter = Console.Out;
IHubProxy chatHubProxy = hubConnection.CreateHubProxy("ChatHub");
await hubConnection.Start();
chatHubProxy.On<string, string>("onSend", (name, message) =>
{
Trace.TraceInformation("onSend name : " + name);
Trace.TraceInformation("onSend message : " + message);
ev.Set();
});
Trace.TraceInformation("chatHubProxy.Invoke");
await chatHubProxy.Invoke("Send", "hhd2002", "hello");
}
catch (Exception ex)
{
Trace.TraceInformation("ex : " + ex);
}
});
ev.WaitOne();
}
full exception message on client program
vstest.executionengine.x86.exe Information: 0 : ex : System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult)
at System.Net.PooledStream.EndWrite(IAsyncResult asyncResult)
at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
It looks like your actual exception message is
The remote certificate is invalid according to the validation procedure.
This is most likely due to it being self-signed and not signed by a trusted certificate authority. It looks like this has already been answered here:
C# Ignore certificate errors?
I had this issue and it was due to an expired/missing Digi Cert Global Root G2 certificate.
I believe if you raise it with Microsoft they provide you a new certificate, however I just applied it from another machine.
The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel

Resources