I'm trying to use SignalR with core 2.0. If I set transport to LongPolling it works, but I get warnings about timeouts. I'm trying to use it with WebSockets, but I get
WebSocket is not in the OPEN state
I checked lots of similar questions, in most of them it's due to system restrictions, but I'm using Windows 10 and Chrome v.67.
I think the problem is in Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<NotifyHub>("/notify");
});
}
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("http://localhost:52170/");
}));
services.AddSignalR();
}
Front(TypeScript):
ngOnInit() {
let builder = new HubConnectionBuilder();
this.hubConnection = builder.withUrl("http://localhost:52170/notify", HttpTransportType.WebSockets).build();//'http://localhost:1874/notify').build();
this.hubConnection.serverTimeoutInMilliseconds = 5000;
this.hubConnection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.log(err)); //'Error while establishing connection :('));
console.log(this.hubConnection);
this.hubConnection.onclose = e => {
this.hubConnection.start();
}
}
I get these two messages from browser:
Information: WebSocket connected to
ws://localhost:52170/notify?id=SJ4cb49oWZtL6lIt4cqhKg WebSocket is not
in the OPEN state
#Jamil, best on your code i have created on project and it is working. Can you provide more details or compare with below code. Let me also know if there is any different.
-- In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//DKS: Makes the SignalR services available to the dependency injection system.
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins("http://localhost:53249")
.AllowCredentials();
}));
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
--- home.component
import { Component, OnInit } from '#angular/core';
import { HubConnection } from '#aspnet/signalr';
import * as signalR from '#aspnet/signalr';
#Component({
selector: 'app-home-component',
templateUrl: './home.component.html'
})
export class HomeComponent implements OnInit {
private _hubConnection: HubConnection | undefined;
public async: any;
message = '';
messages: string[] = [];
constructor() {
}
public sendMessage(): void {
const data = `Sent by you: ${this.message}`;
if (this._hubConnection) {
this._hubConnection.invoke('SendAngular', data);
}
this.messages.push(data);
}
ngOnInit() {
let builder = new signalR.HubConnectionBuilder();
this._hubConnection = builder.withUrl("http://localhost:53249/chatHub", signalR.HttpTransportType.WebSockets).build();//'http://localhost:1874/notify').build();
this._hubConnection.serverTimeoutInMilliseconds = 9999999999999;
this._hubConnection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.log(err)); //'Error while establishing connection :('));
console.log(this._hubConnection);
this._hubConnection.on('Send', (data: any) => {
const received = `Received by Friend: ${data}`;
this.messages.push(received);
});
//this._hubConnection.onclose = e => {
// this._hubConnection.start();
//}
}
}
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SignalRExample.Hubs
{
public class ChatHub : Hub
{
public Task SendAngular(string data)
{
return Clients.All.SendAsync("Send", data);
}
}
}
comment this line "this.hubConnection.serverTimeoutInMilliseconds = 5000;" and check again.
serverTimeoutInMilliseconds is too small.
Related
I have a standard asp dotnet core rest api application.
I want to read the data from the body before it arrives in the controllers to create a generic validation on that data.
I accept that I have to do this in Startup.cs, but I haven't found something similar for what I need.
STARTUP.CS
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
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();
}
else
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Response.ContentType = "text/html";
var ex = context.Features.Get<IExceptionHandlerFeature>();
if (ex != null)
{
//var err = $"<h1>Erro: {ex.Error.Message + "<br><br>" + ex.Error.InnerException + "<br><br>" + ex.Error.StackTrace}</h1>";
var err = $"<p>Erro: {ex.Error.Message} </p>";
await context.Response.WriteAsync(err, System.Text.Encoding.GetEncoding("iso-8859-1")).ConfigureAwait(false);
//await context.Response.WriteAsync(err).ConfigureAwait(false);
}
});
});
app.UseHsts();
}
app.UseRouting();
app.UseCors(x => x
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed(origin => true)
.AllowCredentials());
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
I want to read the data from the body before it arrives in the controllers to create a generic validation on that data it sounds like you want to have an action filter. After creating the filter, pls inject the dependency into Startup.cs file.
services.AddControllers(config =>
{
config.Filters.Add<MySampleActionFilter>();
});
For example, I have an .net 6 api application, I created a filter like below:
using Microsoft.AspNetCore.Mvc.Filters;
namespace WebApiNet6
{
public class MyActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Do something before the action executes.
var req = context.HttpContext.Request;
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Do something after the action executes.
}
}
}
Then I changed my Program.cs from builder.Services.AddControllers() to
builder.Services.AddControllers(options =>
{
options.Filters.Add<MyActionFilter>();
});
Then add a break point in the filter, when I call the api, it will go into the filter before go into the controller.
I'm developing an application which has 2 controllers - an AdminController and a JsonController (based off ControllerBase). The application contains 2 or more self-hosts for the controllers, but only 1 self-host will contain the AdminController - the other hosts will each contain the JsonController only.
I'm trying to use the ControllerFeatureProvider in just one self-host for the moment to disable the AdminController (and leave the JsonController active), however, if I implement this it disables both controllers, i.e. i cannot query either of them. I've confirmed that the IsController is returning true/false for each of the controllers in question. The ControllerFeatureProvider is shown below
public class CustomControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
if (base.IsController(typeInfo))
{
if (typeInfo.Name == nameof(AdminController))
{
return false;
}
return true;
}
return false;
}
}
And the Startup contents are:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(
"CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
// Found via Fiddler that 'Optimal' is very slow
options.Level = CompressionLevel.Fastest;
});
services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Add(new CustomControllerFeatureProvider());
})
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
})
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.DefaultValueHandling = DefaultValueHandling.Ignore;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseResponseCompression();
app.UseCors("CorsPolicy");
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "JSON API", pattern: "json", defaults: new { controller = "json" });
});
}
I've tried all sorts: removing any other Feature Providers, specifically mapping the route to the JsonController. Am i missing something here?
Not quite sure what happened, but seems to be working now. As King King mentioned in the comments, you need to clear any existing FeatureProviders (which i did try but to no avail).
The code is now simply:
services.AddControllers()
.ConfigureApplicationPartManager(manager =>
{
manager.FeatureProviders.Clear();
manager.FeatureProviders.Add(new CustomControllerFeatureProvider());
})
And because my IsController method was pretty amateur, this is now:
protected override bool IsController(TypeInfo typeInfo)
{
if (!base.IsController(typeInfo) || typeInfo.Name == nameof(AdminController))
return false;
return true;
}
I created asp.net core with React template in VS 2019, i need to authorize a controller method so I first registered my app on Azure AD and than i used this Startup.cs configurations:
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllersWithViews().AddMicrosoftIdentityUI();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
In the controller I used AuthorizeForScopes and ITokenAcquisition as follows
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ITokenAcquisition tokenAcquisition;
public WeatherForecastController(ITokenAcquisition tokenAcquisition)
{
this.tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(Scopes = new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" })]
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" });
......
......
}
}
but when i try to fetch the data i have a CORS error
Can you help me
Regarding the issue, please refer to the following steps
Register client application and server application
Use React project template with ASP.NET Core
Client application
a. Install msal
npm install msal
b. Define MsalAuthProvider.
import React, { Component } from 'react';
import { UserAgentApplication } from 'msal';
const msalConfig = {
authority: 'https://login.microsoftonline.com/common',
clientId: '232a1406-b27b-4667-b8c2-3a865c42b79c',
redirectUri: document.getElementById('root').baseURI
};
export const msalAuth = new UserAgentApplication({
auth: msalConfig
});
export function withAuth(HocComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
user: {},
renewIframe: false,
hasError: false,
errorMessage: null
};
}
async componentWillMount() {
msalAuth.handleRedirectCallback(() => {
let userAccount = msalAuth.getAccount();
this.setState({
isAuthenticated: true,
user: userAccount
});
}, (authErr, accountState) => { // on fail
console.log(authErr);
this.setState({
hasError: true,
errorMessage: authErr.errorMessage
});
});
if (msalAuth.isCallback(window.location.hash)) {
this.setState({
auth: {
renewIframe: true
}
});
return;
}
let userAccount = msalAuth.getAccount();
if (!userAccount) {
msalAuth.loginRedirect({});
return;
} else {
this.setState({
isAuthenticated: true,
user: userAccount
});
}
}
onSignIn() {
msalAuth.loginRedirect({});
}
onSignOut() {
msalAuth.logout();
}
render() {
if (this.state.renewIframe) {
return <div>hidden renew iframe - not visible</div>;
}
if (this.state.isAuthenticated) {
return <HocComponent auth={this.state} onSignIn={() => this.onSignIn()} onSignOut={() => this.onSignOut()} {...this.props} />;
}
if (this.state.hasError) {
return <div>{this.state.errorMessage}</div>;
}
return <div>Login in progress...</div>;
}
};
}
c. Update App.js
import { withAuth } from './msal/MsalAuthProvider';
import './custom.css'
class RootApp extends Component {
static displayName ="Azure AD application";
render () {
return (
<Layout>
...
</Layout>
);
}
}
//enable auth when we access the page
const App = withAuth(RootApp)
export default App;
call the API
import { msalAuth } from '../msal/MsalAuthProvider'
async componentDidMount() {
// get token and call the api
try {
const accessTokenRequest = {
scopes: ["api://872ebcec-c24a-4399-835a-201cdaf7d68b/access_as_user"]
}
var authRes
var accessToken = null;
try {
authRes = await msalAuth.acquireTokenSilent(accessTokenRequest);
accessToken=authRes.accessToken
}
catch (error) {
console.log("AquireTokenSilent failure");
authRes = await msalAuth.acquireTokenPopup(accessTokenRequest);
accessToken = authRes.accessToken
}
console.log(accessToken)
this.populateWeatherData(accessToken);
}
catch (err) {
var error = {};
if (typeof (err) === 'string') {
var errParts = err.split('|');
error = errParts.length > 1 ?
{ message: errParts[1], debug: errParts[0] } :
{ message: err };
} else {
error = {
message: err.message,
debug: JSON.stringify(err)
};
}
this.setState({
user: {},
isLoading: false,
error: error
});
}
}
async populateWeatherData(token) {
const response = await fetch('weatherforecast', {
method: 'get',
headers: new Headers({
'Authorization': 'Bearer ' + token
})
});
const data = await response.json();
this.setState({ forecasts: data, loading: false });
}
Server code
a. Startup.cs
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.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// 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();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
b. Controller
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ITokenAcquisition tokenAcquisition;
public WeatherForecastController(ITokenAcquisition tokenAcquisition)
{
this.tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(Scopes = new[] { "<your scope>" })]
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "<your scope>" });
......
......
}
}
For more details, please refer to here snd here.
We're working on an asp.net core project that depends on SignalR. Lastly we updated our project from ASP.NET Core 2.2 to 3.0 and SignalR stopped working. In the documentation we configured everything (I think correctly) but is still doesn't work. What did we miss?
ASP.NET Core 3.0 API
Startup:
public class Startup
{
private readonly string corsPolicy = "CorsPolicy";
private static string[] AllowdOrigins() => return new string[] {"localhost:4200","example.com"};
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
ConfigureAuthentication(services);
///MICROSOFT SQL DATABASE
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")
));
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("XX.XX.XX.XX"));
});
services.AddSignalR();
services.AddControllers();
//services dependencies
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(corsPolicy);
//app.UseForwardedHeaders(new ForwardedHeadersOptions
//{
// ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
//});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatHub>("/chat");
});
DummyData.Initialize(app);
}
private void ConfigureAuthentication(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(corsPolicy,
builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials()
.WithOrigins(AllowdOrigins());
});
});
services.AddHttpContextAccessor();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
}
Chathub:
[EnableCors("CorsPolicy")]
public class ChatHub : Hub
{
private static Dictionary<string, int> onlineClientCounts = new Dictionary<string, int>();
private static readonly string FrontPrefix = "_FRONT";
public ChatHub()
{
}
[HubMethodName("ConnectFrontend")]
public async Task ConnectFrontend(int sessionId)
{
//////
}
[HubMethodName("ConnectDevice")]
public async Task ConnectDevice(int sessionId)
{
//// This method should be called but it isn't.
}
public void DisConnect(int sessionId, int userId)
{
//////////
}
[HubMethodName("SendJsonToFrontends")]
public async Task SendJsonToFrontends(int sessionId, string jsonString)
{
///
}
[HubMethodName("SendJsonToAll")]
public async Task SendJsonToAll(int sessionId, string jsonString)
{
////
}
}
Angular project
SignalRService:
export class SignalRService {
private connection: signalR.HubConnection;
public newMessage = new Subject<SocketMessage>();
constructor() {
}
public connectFront() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:2525/chat")//(environment.baseSignalR)
.configureLogging(signalR.LogLevel.Trace)
.build();
this.connection.on("ReceiveJson", data => { this.handleJsonMessage(data) });
// handles the first connection message
this.connection.start().then(() => this.sendConnectionMessage());
// handles the incoming messages
this.connection.on("ReceiveJson", data => this.handleJsonMessage(data));
this.connection.on("ReceiveJsonFrontend", data => this.handleJsonMessage(data));
}
private sendConnectionMessage() {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("ConnectFrontend", sessionId).catch((error) => { debugger; console.log(error); });
}
}
public sendWebsocketMessageToAll(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToAll", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
public sendWebsocketMessageToFrontend(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToFrontends", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
private handleJsonMessage(data: string) {
this.newMessage.next(this.getSocketMessage(data));
}
public getSocketMessage(data: string): SocketMessage {
var msg: SocketMessage = JSON.parse(data);
return msg;
}
public disconnect() {
this.connection.stop();
}
}
angular output:
Api output:
Just create an web app API with angular template you can view my code for your ref
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSignalR().AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.WriteIndented = false;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/chatHub");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
In FE side. Note: use new package #microsoft/signalr
import * as signalR from "#microsoft/signalr";
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
title = 'app';
ngOnInit() {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.build();
connection.on("receiveMessage", (username: string, message: string) => {
console.log(username);
console.log(message);
});
connection.start().then(() => {
connection.send("sendMessage", "aaa", "aaaa")
.then(() => console.log("done"));
}).catch(err => document.write(err));
}
}
Well I finaly figured it out with the help of Tony,
Apperently It went wrong in the methods of the SignalR chat hub. The methods allowed only integers as parameters but it needed to be strings. I don't know it it has to do with some other settings but my guess now is that signalr cant convert the request parameters to something other then strings.
When I changed it to strings it worked.
It seems, that you have configured JWT based authentication on the server side and do not provide a token for SignalR connection. Try to provide a token using the accessTokenFactory:
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.urlAddress}/chathub`, {
accessTokenFactory: () => this.token
})
.build()
I, am using the SignalR features on angular 6 and asp.net core. But keep getting this error Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.
Did some research and found that it is CORS issue from the server side.So modified the server code.
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithOrigins("http://localhost:4200");
}));
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<SignalR>("/rule");
});
}
angular app
ngOnInit() {
this.callSignalR()
}
private callSignalR(): void {
// set up the signal R
let connection = new signalR.HubConnectionBuilder().withUrl(environment.baseUrl+"/rule").build();
//start the connection
connection.start().then(() => connection.invoke("updateRules", this.autheService.getCurrentuser.userId));
//Get the response from the server
connection.on("updateRules", data => {console.log(data);});
}
references
Access-Control-Allow-Origin - Angular 5
'Access-Control-Allow-Credentials' header in the response is '' which must be 'true'
https://github.com/aspnet/SignalR/issues/2095
https://github.com/SignalR/SignalR/issues/1694
https://github.com/aspnet/SignalR/issues/2110
You must allow credentials for your cors-policy because signalr is passing cookies as well.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithOrigins("http://localhost:4200");
}));
services.AddSignalR();
}
in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithOrigins("http://localhost:4200");
}));
services.AddSignalR();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<BroadcastHub>("/notify");
});
}
From your angular app
ngOnInit(): void {
const connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Information)
.withUrl(environment.baseUrl + 'notify')
.build();
connection.start().then( () => {
console.log('SignalR Connected!');
}).catch( (err) => {
return console.error(err.toString());
});
// BoradcastMessage is the name of the function in your SignalR
// interface.
connection.on('BroadcastMessage', () => {
console.log('Message from server');
});
}