Objective: Use SignalR for notification between Web API, and TypeScript/JavaScript based Web App, where Web API and the Web App is hosted in different domain.
Enabling SignalR and CORS on Web API: Create a standard Web API project, and install the following NuGet packages:
After that you can get rid of the Global.asax
and add a OWIN Startup class instead.
using System.Web.Http;
using System.Web.Http.Cors;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(WebAPI.Startup), "Configuration")]
namespace WebAPI
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var httpConfig = new HttpConfiguration();
//change this configuration as you want.
var cors = new EnableCorsAttribute("http://localhost:9000", "*", "*");
httpConfig.EnableCors(cors);
SignalRConfig.Register(app, cors);
WebApiConfig.Register(httpConfig);
app.UseWebApi(httpConfig);
}
}
}
Create the SignalRConfig
class as follows:
using System.Linq;
using System.Threading.Tasks;
using System.Web.Cors;
using System.Web.Http.Cors;
using Microsoft.Owin.Cors;
using Owin;
namespace WebAPI
{
public static class SignalRConfig
{
public static void Register(IAppBuilder app, EnableCorsAttribute cors)
{
app.Map("/signalr", map =>
{
var corsOption = new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context =>
{
var policy = new CorsPolicy { AllowAnyHeader = true, AllowAnyMethod = true, SupportsCredentials = true };
// Only allow CORS requests from the trusted domains.
cors.Origins.ToList().ForEach(o => policy.Origins.Add(o));
return Task.FromResult(policy);
}
}
};
map.UseCors(corsOption).RunSignalR();
});
}
}
}
Till now we have just enabled SignalR with CORS on server side. Now lets see how you can publish events from server side. For this we need a Hub
:
public class NotificationHub:Hub
{
//this can be in Web API or in any other class library that is referred from Web API.
}
Now finally some code to actually broadcast the change:
public class SuperHeroController : ApiController
{
[HttpGet]
public string RevealAlterEgo(string id)
{
var alterEgo = $"The alter ego of {id} is not known.";
var superHero = _superHeroes.SingleOrDefault(sh => sh.Name.Equals(id));
if (superHero != null)
{
alterEgo = superHero.AlterEgo;
/*This is how you broadcast the change.
*For simplicity, in this example, the broadcast is done from a Controller,
*but, this can be done from any other associated class library having access to NotificationHub.
*/
var notificationHubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
if (notificationHubContext != null)
{
var changeData = new { changeType = "Critical", whatHappened = $"Alter ego of {id} is revealed." };
//somethingChanged is an arbitrary method name.
//however, same method name is also needs to be used in client.
notificationHubContext.Clients.All.somethingChanged(changeData);
}
}
return alterEgo;
}
}
Thus, so far, we made the server side ready. For client side, we need jQuery
, and signalr
package. You may install both with jspm
. Install the typings for both, if needed.
We will not be using the default generated JavaScript proxy. We will rather create a very simple class to handle the SignalR communication.
/**
* This is created based on this gist: https://gist.github.com/donald-slagle/bf0673b3c188f3a2559c.
* As we are crreating our own SignalR proxy,
* we don't need to get the auto generated proxy using `signalr/hubs` link.
*/
export class SignalRClient {
public connection = undefined;
private running: boolean = false;
public getOrCreateHub(hubName: string) {
hubName = hubName.toLowerCase();
if (!this.connection) {
this.connection = jQuery.hubConnection("https://localhost:44378");
}
if (!this.connection.proxies[hubName]) {
this.connection.createHubProxy(hubName);
}
return this.connection.proxies[hubName];
}
public registerCallback(hubName: string, methodName: string, callback: (...msg: any[]) => void,
startIfNotStarted: boolean = true) {
var hubProxy = this.getOrCreateHub(hubName);
hubProxy.on(methodName, callback);
//Note: Unlike C# clients, for JavaScript clients,
// at least one callback needs to be registered,
// prior to start the connection.
if (!this.running && startIfNotStarted)
this.start();
}
start() {
const self = this;
if (!self.running) {
self.connection.start()
.done(function () {
console.log('Now connected, connection Id=' + self.connection.id);
self.running = true;
})
.fail(function () {
console.log('Could not connect');
});
}
}
}
Lastly use this class to listen to change broadcasts, as follows:
/**
* Though the example contains Aurelia codes,
* the main part of SignalR communication is without any Aurelia dependency.
*/
import {autoinject, bindable} from "aurelia-framework";
import {SignalRClient} from "./SignalRClient";
@autoinject
export class SomeClass{
//Instantiate SignalRClient.
constructor(private signalRClient: SignalRClient) {
}
attached() {
//To register callback you can use lambda expression...
this.signalRClient.registerCallback("notificationHub", "somethingChanged", (data) => {
console.log("Notified in VM via signalr.", data);
});
//... or function name.
this.signalRClient.registerCallback("notificationHub", "somethingChanged", this.somethingChanged);
}
somethingChanged(data) {
console.log("Notified in VM, somethingChanged, via signalr.", data);
}
}