Incredibly simple real-time web for .NET
This feature allows users to specify what the userId is based on an IRequest via a new interface IUserIdProvider:
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
By default there will be an implementation that uses the user's IPrincipal.Identity.Name as the user name.
In hubs, you'll be able to send messages to these users via a new API:
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
Users can now throw HubException from any hub invocation. The constructor of the HubException can take a string message and an object extra error data. SignalR will auto-serialize the exception and send it to the client where it will be used to reject/fail the hub method invocation.
The show detailed hub exceptions setting has no bearing on HubException being sent back to the client or not; it always is.
Server
public class MyHub : Hub
{
public void Send(string message)
{
if(message.Contains("<script>"))
{
throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message });
}
Clients.All.send(message);
}
}
JS Client
myHub.server.send("<script>")
.fail(function (e) {
if (e.source === 'HubException') {
console.log(e.message + ' : ' + e.data.user);
}
});
.NET Client
try
{
await myHub.Invoke("Send", "<script>");
}
catch(HubException ex)
{
Conosle.WriteLine(ex.Message);
}
To be more compatible with OWIN standards we've renamed MapHubs and MapConnection to MapSignalR. This gives you a single entry point for configuring SignalR into the OWIN pipeline. We've also introduced RunSignalR for more advanced scenarios (there's an example in the cross domain document).
Before
using Microsoft.Owin;
using Owin;
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Map all hubs to "/signalr"
app.MapHubs();
// Map the Echo PersistentConnection to "/echo"
app.MapConnection<EchoConnection>("/echo");
}
}
}
After
using Microsoft.Owin;
using Owin;
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Map all hubs to "/signalr"
app.MapSignalR();
// Map the Echo PersistentConnection to "/echo"
app.MapSignalR<EchoConnection>("/echo");
}
}
}
In SignalR 1.x cross domain requests were controlled by a single EnableCrossDomain flag. This flag controlled both JSONP and CORS requests. For greater fleixibility, we've removed all CORS support from SignalR and have made new OWIN middleware available to support these scenarios. To add support for CORS requests, install Microsoft.Owin.Cors and call UseCors before your SignalR middleware:
Before
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;
using Owin;
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapHubs(new HubConfiguration { EnableCrossDomain = true });
}
}
}
After
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;
using Owin;
namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
// Setup the cors middleware to run before SignalR.
// By default this will allow all origins. You can
// configure the set of origins and/or http verbs by
// providing a cors options with a different policy.
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
// Run the SignalR pipeline. We're not using MapSignalR
// since this branch is already runs under the "/signalr"
// path.
map.RunSignalR(hubConfiguration);
});
}
}
}
The new CORS middleware also allows you to specify a CorsPolicy which lets you lock down which origins you want to allow requests from.
JSONP is now a separate flag on ConnectionConfiguration and HubConfiguration and can be enabled if there is a need to support older browsers (and some versions of IE).
You can now send to multiple groups or multiple connections easily and efficiently.
Persistent Connection
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
namespace MyApplication
{
public class ChatConnection : PersistentConnection
{
protected override Task OnReceived(IRequest request, string connectionId, string data)
{
IList<string> connectionIds = DB.GetConnections(request.User.Identity.Name);
return Connection.Send(connectionIds, data);
}
}
}
Hubs
using System;
using System.Collections.Generic;
using Microsoft.AspNet.SignalR;
namespace MyApplication
{
public class Chat : Hub
{
public void Send(string message)
{
IList<string> connectionIds = DB.GetConnections(Context.User.Identity.Name);
Clients.Clients(connectionIds).send(message);
}
}
}
We've introduced an interface IHubCallerConnectionContext on Hubs that makes it easier to mock client side invocations. Here's an example using xunit and Moq.
[Fact]
public void HubsAreMockableViaDynamic()
{
bool sendCalled = false;
var hub = new MyHub();
var mockClients = new Mock<IHubCallerConnectionContext>();
hub.Clients = mockClients.Object;
dynamic all = new ExpandoObject();
all.send = new Action<string>(message =>
{
sendCalled = true;
});
mockClients.Setup(m => m.All).Returns((ExpandoObject)all);
hub.Send("foo");
Assert.True(sendCalled);
}
[Fact]
public void HubsAreMockableViaType()
{
var hub = new MyHub();
var mockClients = new Mock<IHubCallerConnectionContext>();
var all = new Mock<IClientContract>();
hub.Clients = mockClients.Object;
all.Setup(m => m.send(It.IsAny<string>())).Verifiable();
mockClients.Setup(m => m.All).Returns(all.Object);
hub.Send("foo");
all.VerifyAll();
}
public interface IClientContract
{
void send(string message);
}
All JavaScript error handling callbacks flow JavaScript error objects instead of raw strings. This allows us to flow richer information to your error handlers. You can get the inner exception from the source property of the error.
connection.start().fail(function(e) {
console.log('The error is: ' + e.message);
});
var config = new HubConfiguration
{
EnableCrossDomain = true
}
RouteTable.Routes.MapHubs(config);
var config = new HubConfiguration
{
EnableDetailedErrors = true
}
RouteTable.Routes.MapHubs(config);