How to connect to a SignalR self-host wpf app remotely? - signalr

I am trying to connect to my self-hosted SignalR server remotely using a web-based client. I can successfully connect to the server locally, but I cannot connect to the server remotely.
When I type (self-hosted ip4 address):7777/signalr/hubs in the URL of my remote browser, I see the generated hubs file. Therefore, I believe that I'm making a connection. But I want to see the web page. If you couldn't tell already, I'm a beginner to SignalR
2 more questions:
What URL should I type into the other computer's web browser to connect to my server?
What code determines what that URL is?
Here's my code
The self-hosted SignalR server (a wpf application)
public partial class MainWindow : Window
{
IDisposable SignalR;
private string url = "http://*:7777";
public MainWindow()
{
InitializeComponent();
SignalR = WebApp.Start(url);
Console.WriteLine("Server running on {0}", url);
}
}
Startup class
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
Index Page (scripts)
#section scripts
{
<!--Reference the jQuery library. -->
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<!--Reference the SignalR library. -->
<script src="~/Scripts/jquery.signalR-2.4.3.min.js"></script>
<!--Reference the autogenerated SignalR hub script. -->
<script src="http://localhost:7777/signalr/hubs"></script>
<!--Add script to update the page and send messages.-->
<script src="~/MyScripts/Main.js"></script>
}
My js file
$(function () {
$.connection.hub.url = "http://localhost:7777/signalr";
var hub = $.connection.mainHub;
$.connection.hub.logging = true;
$.connection.hub.log("Trying to connect...");
// Start connection
$.connection.hub.start()
.done(function () {
$.connection.hub.log('Now connected, connection ID=' + $.connection.hub.id);
});
})
.fail(function () {
$.connection.hub.log("Error!");
});
});

I figured out how to connect remotely to my self-hosted server.
I changed localhost with my ip4 address (192.168.0.183) in the js and index script section
My new code
$.connection.hub.url = "http://192.168.0.183:7777/signalr";
and
<script src="http://192.168.0.183:7777/signalr/hubs"></script>
Finally, I had to edit my applicationhost.config file located at
/{project folder}/.vs/{solution name}/config/applicationhost.config
In the binding protocol section, I changed localhost after the port to my ip4 address
New code
<bindings>
<binding protocol="http" bindingInformation="*:58038:192.168.0.183" />
<binding protocol="https" bindingInformation="*:44389:192.168.0.183" />
</bindings>
This at least allows me to connect to the http url (doesn't work with https in my experience so far)

Related

Can you run a asp.net core 3.0 gRPC CLIENT in IIS? (possibly on Azure?)

I've read a lot of conflicting information about this and it seems people are not 100% clear on what is possible and what is not. I am certain that you cannot host a gRPC server app in IIS due to the HTTP/2 limitations. The documentation is pretty clear. However, I want to use IIS as a reverse proxy, with the internal side communicating using gRPC. So the client would be in IIS, not the server. I assumed that since the communication at this point (i.e. the back end) was not funneled through IIS, there would be no issue with this. However, I keep seeing mixed answers.
I have created a dumb webapp that is hosted in IIS Express and can successfully post to my service running on Kestrel with gRPC.
Client code sample below. The SubmitButton is just a form post on the razor page.
public async void OnPostSubmitButton()
{
// The port number(5001) must match the port of the gRPC server.
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "GreeterClient" });
Console.WriteLine("Greeting: " + reply.Message);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Server code is the boilerplate template for gRPC but looks like this:
namespace grpcGreeter
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
namespace grpcGreeter
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
This works. But, because I keep seeing mixed information saying it that it won't, I am not certain that once I go to deploy the client code (i.e. the reverse proxy), if I will run into problems. I would like to use a host like Azure...but don't know if it's possible or not.
Any clarity on the subject would be greatly appreciated.
As far as I know, we could use asp.net core mvc or razor page application as the client to call the grpc server.
But gRPC client requires the service to have a trusted certificate when you hosted the application on remote server IIS.
If you don't have the permission to install the certificate, you should uses HttpClientHandler.ServerCertificateCustomValidationCallback to allow calls without a trusted certificate.
Notice: this will make the call not security.
Additional configuration is required to call insecure gRPC services with the .NET Core client. The gRPC client must set the System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport switch to true and use http in the server address.
Code as below:
AppContext.SetSwitch(
"System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var httpClientHandler = new HttpClientHandler();
// Return `true` to allow certificates that are untrusted/invalid
httpClientHandler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:5001",
new GrpcChannelOptions { HttpClient = httpClient });
var client = new Greeter.GreeterClient(channel);
var response = await client.SayHelloAsync(new HelloRequest { Name = "World" });

SignalR - unable to call server method from external JavaScript client

I am learning SignalR. Using a tutorial I managed to create a very simple ASP.NET MVC based SignalR Server. This is the code of the hub:
[HubName("echo")]
public class EchoHub : Hub
{
public void Say(string message)
{
Trace.WriteLine(message);
}
}
This is my Startup file:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableJSONP = true
};
map.RunSignalR(hubConfiguration);
});
}
}
Next I created a HTML/JavaScript based client, that was in the same project as the server code. This client works correctly, when I debug the application, the Output window displays my message. This the code of the first client:
<script src="../Scripts/jquery-1.10.2.min.js"></script>
<script src="../Scripts/jquery.signalR-2.1.2.min.js"></script>
<script src="/signalr/hubs"></script>
<script>
$(function() {
var hubProxy = $.connection.echo;
$.connection.hub.logging = true;
$.connection.hub
.start()
.done(function () {
hubProxy.server.say('Hello SignalR');
});
})
</script>
The last thing I wanted to do is to create an client in separate project. I changed a little bit the code, mostly by adding the url's of the server. But this client don't work properly. This is the code of the client:
<script src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.2.0.min.js"></script>
<script src="http://localhost:51644/signalr/hubs"></script>
<script>
$(function() {
$.connection.hub.url = 'http://localhost:51644/signalr';
var hubProxy = $.connection.echo;
$.connection.hub.logging = true;
$.connection.hub
.start()
.done(function () {
hubProxy.server.say('Hello SignalR');
});
})
</script>
The JavaScript don't even enters the function in the done method after starting the connection. But in console on Chrome there are no errors or exceptions being thrown.
So the question is what am I doing wrong in the second client?
EDIT: I enabled and checked the SignalR client logs in Chrome console. The second client is stoping by step "SignalR: Negotiating with '/signalr/negotiate?clientProtocol=1.4&connectionData=%5B%5D'." No errors are returned, everything seems alright, but the client cannot go past this step.
EDIT2: According to the comment from JF Beaulieu the negotiate return 200 status code, but nothing else. After the negotiate there should be next steps, like invoking Say, but in the external client hangs on the negotiate. I paste here console outputs from the build in and external JavaScript client.
Build in client output:
External client output:
The second screenshot shows, that the client stops executing on the negotiate step.
I've also tried the solution of Mareq, but adding the jsonp attribute didn't help.
EDIT3: I add here a link to my Github repo with this project, the server is in the AspNetServer project and client in JavaScriptClient project, hope that helps:
https://github.com/RomanSuska/SignalRSandbox
I finally found the solution of my problem and it's stupid simple. The problem was with the SignalR JavaScript file version, on the server I used version 2.1.2 of SignalR and on the client I used 2.2.0. When I switched the version of the client to 2.1.2 everything started to work.
My case was due to my Hub having a constructor because I was trying to do Dependency Injection with Unity. After I removed the constructor, everything worked.
Add this argument to start:
$.connection.hub.start({ jsonp: true });
EDIT I hosted SignalR in console app and I used simple set configuration:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration { EnableJSONP = true });
}
}
and client code I used like this:
$(function () {
var hub = $.connection.serviceHub;
$.connection.hub.url = 'http://mareq.ddns.net:8087/signalr';
$.connection.hub.start({ jsonp: true });
...
});

Self hosting SignalR in linqpad

Scenario
I'm attempting to use linqpad to self host SignalR.
Script
// I have uploaded to instant share here: http://share.linqpad.net/578ol2.linq
// Import the following nuget packages:
// Microsoft.AspNet.SignalR.SelfHost
// Microsoft.Owin.Cors
#define NONEST
void Main()
{
var baseUrl = #"http://localhost:8080/";
using (WebApp.Start<Startup>(baseUrl))
{
Process.Start(baseUrl + "index.html");
Console.WriteLine("Server running at " + baseUrl);
Console.ReadLine();
}
}
public class Startup
{
public readonly string Html = #"
<!DOCTYPE html>
<html>
<head>
<title>SignalR Simple Chat</title>
</head>
<body>
<div class='container'>
<input type='text' id='message' />
<input type='button' id='sendmessage' value='Send' />
<input type='hidden' id='displayname' />
<ul id='discussion'></ul>
</div>
<script src='https://code.jquery.com/jquery-1.6.4.js'></script>
<script src='http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.2.0.js'></script>
<script src='http://localhost:8080/signalr/hubs'></script>
<script type='text/javascript'>
$(function () {
$.connection.hub.url = 'http://localhost:8080/signalr';
$.connection.hub.logging = true;
var chat = $.connection.myHub;
chat.client.addMessage = function (name, message) {
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
$('#discussion').append('<li><strong>' + encodedName + '</strong>: ' + encodedMsg + '</li>');
};
$('#displayname').val(prompt('Enter your name:', ''));
$('#message').focus();
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
chat.server.send($('#displayname').val(), $('#message').val());
$('#message').val('').focus();
});
});
});
</script>
</body>
</html>
".Trim();
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
app.Map("/index.html", config =>
{
config.Run(context =>
{
context.Response.ContentType = "text/html";
return context.Response.WriteAsync(Html);
});
});
}
}
public class MyHub : Hub
{
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);
}
}
Outcome
If you look at the browser console you see that SignalR eventually exhausts all transports, so can't create a connection between the client and the server
SignalR: Client subscribed to hub 'myhub'.
Negotiating with 'http://localhost:8080/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22myhub%22%7D%5D'.
serverSentEvents transport starting.
Attempting to connect to SSE endpoint 'http://localhost:8080/signalr/connect?transport=serverSentEvents&clientProt…joHW8a9m5Q%3D%3D&connectionData=%5B%7B%22name%22%3A%22myhub%22%7D%5D&tid=1'.
EventSource connected.
serverSentEvents transport timed out when trying to connect.
EventSource calling close().
serverSentEvents transport failed to connect. Attempting to fall back.
foreverFrame transport starting.
Forever Frame is not supported by SignalR on browsers with SSE support.
foreverFrame transport failed to connect. Attempting to fall back.
longPolling transport starting.
Opening long polling request to 'http://localhost:8080/signalr/connect?transport=longPolling&clientProtocol=…QeRv75joHW8a9m5Q%3D%3D&connectionData=%5B%7B%22name%22%3A%22myhub%22%7D%5D'.
longPolling transport timed out when trying to connect.
Aborted xhr request.
longPolling transport failed to connect. Attempting to fall back.
Fallback transports exhausted.
Stopping connection.
Fired ajax abort async = true.
However this same exact code works without issue within a Console Application
Question
Does anyone know why this code fails when hosting in linqpad but works when hosting in a console application?
I'm leaning towards some sort of connectivity issue rather than a LinqPad one as I'm able to run your code snipped without an issue. I am also running it under version 5. Do you happen to have a 3r party firewall?
Just to confirm, what's the expected behaviour? When I run it, my browser pops up asking me to enter a name. Then I'm able to send 'messages', which appear on the page below the text box.

SignalR: "The user identity cannot change during an active SignalR connection" error with windows auth website

I have an MVC 5 website running signalR 2.1.0 using Windows Authentication. Because I'm using windows auth login/logout is handled automatically by IIS. Occassionally I'm getting a 403 error saying "Unrecognized user identity. The user identity cannot change during an active SignalR connection." This doesn't happen all the time, and I can't seem to find a pattern to when it does and does not work. Has anyone else encountered this?
Here is the code on the view:
<script type="text/javascript">
$(document).ready(function() {
SignalRSetup();
});
function SignalRSetup() {
// Declare a proxy to reference the hub.
var hub = $.connection.tokenRequestHub;
// Create a function that the hub can call to broadcast messages.
hub.client.updateFromService = function(tokenRequestID, message, success) {
var msg = "Token Request ID {0} => {1}".format(tokenRequestID, message);
var notyType = (success) ? 'success' : 'error';
noty({ text: msg, type: notyType, timeout: 2000 });
if (success) {
refreshGrids();
}
};
$.connection.hub.start();//this is where it errors!
}
</script>
Any help would be greatly appreciated.
I think I fixed the issue by adding an [Authorize] attribute to my Hub class. It's only been a few hours, but my SignalR powered page is behaving much better.
What worked for me was to disable Anonymous Authentication in the IIS settings.
Another solution I found other than disable Anonymous Authentication is to add
GlobalHost.HubPipeline.RequireAuthentication();
to public void Configuration(IAppBuilder app) method in the Startup class.

SignalR Negotiate 404

I am using SignalR 2.0. Everything works fine when running locally on my VS 2012. But when I publish the site on IIS, it breaks. The site loads but one of the scripts returns 404 Not Found. The script is something like.
https://example.com/signalr/negotiate?xxx
This path doesn't exist indeed. The correct path should be:
https://example.com/private/signalr/negotiate?xxx
Note the part in bold.
Inside the WebSite (https://example.com/) I have another Application (https://example.com/private/). This one is using SignalR.
This seems like a bug in SignalR since the signalr/hubs path is accessible from my private site.
I had a similar problem.
Here is the documentation for configuring the /signalr URL.
However, my solution differed from the docs.
Instead of changing the standard app.MapSignalR(), I changed my client code to use /MyApp/signalr. Here is the code where "MyApp" is the virtual directory of my web application.
var connection = $.hubConnection('/MyApp/signalr', {useDefaultPath: false});
var changesHub = connection.createHubProxy('changesHub');
changesHub.on('userCountChanged', function (count) {
$('#user-count').text(count);
});
connection.start().done(function () {
console.log('Hub has started');
changesHub.invoke('subscribeToChanges', user.id);
});
I tried the other way around (change the MapSignalR to the /signalr path) but this did not work and the negotiation was still routed to /MyApp/signalr/negotiate.
I had the same problem, with an application running in the IIS Default Web Site.
All the Microsoft examples show the hub url with a starting \, and I had copied those examples. But this meant that the signalr routing was from the Default Web Site rather than the application. Removing the leading \ solved it.
So I used endpoints in Startup.cs like:
endpoints.MapHub<MyHub>("myHub");
and hub connections in Javascript like:
var connection = new signalR.HubConnectionBuilder().withUrl("myHub").build();
I had the same issue when web site with signalr is not running as root site. Below solution worked for me. instead of using /signalr, use ../signalr. it will work with any site name folder. no hardcoded name 'MyApp'
var connection = $.hubConnection('../signalr', {useDefaultPath: false});
Had the same issue. web sites running as virtual directories of the root site. For some reason prefixing with ../ as in ../signalr didn't work, but ./signalr did.
My sample code:
function initSR() {
// logs signalr messages
$.connection.hub.logging = true;
// Declare a proxy to reference the hub.
var chat = $.connection.myHub;
$.connection.hub.url = "./signalr";
$.connection.hub.start();
// Create a function that the hub can call to broadcast messages.
chat.client.broadcastMessage = function (message) {
// Process Message, take action upon receipt
alert(message);
};
}
I had the same problem, it is all about CORS, you should add Host URL in CORS config in Startup.cs like this:
services.AddCors(option =>
{
option.AddPolicy("AutomationCors", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("YOUR LOCALHOST URL",
"YOUR HOST URL")
.AllowCredentials();
});
});
I faced the same problem. The mistake i was doing that i was calling the wrong endpoint url like i was mapping the Signal Url in Configure service like /notification but calling [API-Host]/api/notification. Removing the api from url and calling [API-Host]/notification fixed for me.
Probably you added MapSignalR() in your Application (https://example.com/private/).
If you want it on the root, then do the configuration on your WebSite (https://example.com/)
#styfle point me in the right direction the problem can be resolve in a more flexible way injecting BASE_URL (at least in angular 4)
import { Injectable, Inject } from '#angular/core';
import { HubConnection } from '#microsoft/signalr';
import * as signalR from '#microsoft/signalr';
import { Subject, Observable } from 'rxjs';
#Injectable()
export class SignalRService {
private hubConnection: HubConnection;
private message: Subject<any>;
constructor(#Inject('BASE_URL') private baseUrl: string) {
}
public connect() {
this.message = new Subject<any>();
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.baseUrl+"notification-hub")
.withAutomaticReconnect()
.build();
// ...
}
}

Resources