將 SignalR 與 Web API 和 JavaScript Web App 一起使用,並支援 CORS
目標: 使用 SignalR 在 Web API 和基於 TypeScript / JavaScript 的 Web App 之間進行通知,其中 Web API 和 Web App 託管在不同的域中。
在 Web API 上啟用 SignalR 和 CORS: 建立標準 Web API 專案,並安裝以下 NuGet 包:
- Microsoft.Owin.Cors
- Microsoft.AspNet.WebApi.Cors
- Microsoft.AspNet.WebApi.Owin
- Microsoft.AspNet.SignalR.Core
之後,你可以擺脫 Global.asax
並新增一個 OWIN Startup 類。
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);
}
}
}
建立 SignalRConfig
類如下:
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();
});
}
}
}
到目前為止,我們剛剛在伺服器端啟用了帶有 CORS 的 SignalR。現在讓我們看看如何從伺服器端釋出事件。為此我們需要一個 Hub
:
public class NotificationHub:Hub
{
//this can be in Web API or in any other class library that is referred from Web API.
}
現在終於實際廣播變化的一些程式碼:
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;
}
}
因此,到目前為止,我們已經準備好了伺服器端。對於客戶端,我們需要 jQuery
和 signalr
包。你可以使用 jspm
安裝。如果需要,安裝兩者的型別。
我們不會使用預設生成的 JavaScript 代理。我們寧願建立一個非常簡單的類來處理 SignalR 通訊。
/**
* 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');
});
}
}
}
最後使用此類來收聽更改廣播,如下所示:
/**
* 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);
}
}