SignalR - Change server timeout response - c#

i've created a SignalR application but when i set the KeepAliveInternal and ClientTimeOutInterval a value in the hub configuration, the application ignore it and always set to "30,000ms" for both. This is my code:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR().AddHubOptions<ActivityHub>(SetConfig);
// Local function to set hub configuration
void SetConfig(HubOptions<ActivityHub> options)
{
options.ClientTimeoutInterval = TimeSpan.FromMinutes(30);
options.KeepAliveInterval = TimeSpan.FromMinutes(15);
}
}
I've read the SignalR Net Core docs and there is no limit for these two properties. The timeout always is "30,000" even i set those to differente values.

when i set the KeepAliveInternal and ClientTimeOutInterval a value in the hub configuration, the application ignore it and always set to "30,000ms" for both.
For SignalR JavaScript client, the default serverTimeoutInMilliseconds value is 30,000 milliseconds (30 seconds). If you set KeepAliveInterval of HubOptions with a value > 30 seconds, but not specify an appropriate value for serverTimeoutInMilliseconds of HubConnection on client side, the connection will be terminated with an error, like below.
To fix it, you can try to set serverTimeoutInMilliseconds of your HubConnection, like below.
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub")
.configureLogging(signalR.LogLevel.Trace)
.build();
connection.serverTimeoutInMilliseconds = 120000;
Test Result
Note:
In my above test, I configure SignalR hubs with below code snippet, and we can find a ping message is sent automatically per 60s.
hubOptions.ClientTimeoutInterval = TimeSpan.FromMinutes(2);
hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(1);

Please refer to the official documentation for configuring server options
You may try to configure it as following:
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(hubOptions =>
{
hubOptions.ClientTimeoutInterval = TimeSpan.FromMinutes(30);
hubOptions.KeepAliveInterval = TimeSpan.FromMinutes(15);
});
}
Or for a single hub:
services.AddSignalR().AddHubOptions<MyHub>(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromMinutes(30);
options.KeepAliveInterval = TimeSpan.FromMinutes(15);
});

I had the same problem back then, and what I changed was simple. I changed TimeSpan.FromMinutes to TimeSpan.FromSeconds since in the documentation you can see that those intervals are in seconds.
So my configuration code now is like this:
/// <summary>
/// Adds SignalR.
/// </summary>
private void AddSignalR(IServiceCollection services)
{
services.AddSignalR(hubOptions =>
{
hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.ClientTimeoutInterval);
hubOptions.HandshakeTimeout = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.HandshakeTimeout);
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(this.azureConfiguration.SignalR.KeepAliveInterval);
hubOptions.EnableDetailedErrors = this.azureConfiguration.SignalR.EnableDetailedErrors;
hubOptions.MaximumReceiveMessageSize = this.azureConfiguration.SignalR.MaximumReceiveMessageSize;
hubOptions.StreamBufferCapacity = this.azureConfiguration.SignalR.StreamBufferCapacity;
}).AddAzureSignalR(azureOptions =>
{
azureOptions.ConnectionCount = this.azureConfiguration.SignalR.ServerConnectionCount;
});
}

Related

Why is IConfiguration null when integration testing my Azure Functions?

The current version of the Microsoft.Azure.Functions.Extensions package exposes an additional property that allows you easy access to the IConfiguration provided to the function. Previously this required manually building a service provider, which was obviously problematic.
Using that package my FunctionsStartup.cs looks like this:
public override void Configure(IFunctionsHostBuilder builder)
{
base.Configure(builder);
var config = builder.GetContext().Configuration; // new in v1.1.0 of Microsoft.Azure.Functions.Extensions
var mySetting = config["MySetting"];
int.Parse(mySetting, out var mySetting);
// ... use mySetting...
}
In order to test my HTTP-triggered functions I've used this article as a base, which details how to manually build and start a host to execute my function as if it was running in Azure, similar to how TestServer works in ASP.NET Core:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
var functionsInstance = ActivatorUtilities.CreateInstance<MyFunctions>(host.Services);
I can then execute the function methods defined on MyFunctions to test their responses:
var request = new DefaultHttpRequest(new DefaultHttpContext());
var response = (OkObjectResult)functionsInstance.HttpTriggerMethod(request);
... assert that response is valid
The problem is that when I run my tests, builder.GetContext().Configuration is returning null in FunctionsStartup.Configure, which of course causes those tests to fail. How can I work around this?
The article I linked to hasn't been updated to take into account the existence of builder.GetContext().Configuration, but you can make this work for testing purposes with a little tweaking. Instead of using:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
you need to explicitly copy the host's settings into a new WebJobsBuilderContext that you then pass to your function's startup:
var host = new HostBuilder()
.ConfigureWebJobs((context, builder) => new FunctionsStartup().Configure(new WebJobsBuilderContext
{
ApplicationRootPath = context.HostingEnvironment.ContentRootPath,
Configuration = context.Configuration,
EnvironmentName = context.HostingEnvironment.EnvironmentName,
}, builder))
.Build();
I'm not sure if this is the completely correct way to achieve this, but it has worked well for me.

Connecting to SignalR Server from Client

I have a web server acting as SignalR server today, where the connections from JS are coming in to correct Hub and are handled correctly.
Example of the Register and start JS side
hub = $.connection.webRTCHub;
$.connection.hub.qs = "type=pusher";
$.connection.hub.start().done(function () {
connectionId = $.connection.hub.id;
log("Connected with id ", $.connection.hub.id);
});
When trying to connect to this SignalR server with the C# SignalR Client Nuget-package, I get connected, I get a connection ID, but I do not think I get connected to correct hub because non of the logging is triggered, nor the correct responses are sent to rest of clients.
I am using the trace log for SignalR and it is showing connections, and showing that the ID is connecting. Below is the connection code from the C# client
connection = new HubConnection("http://localhost/signalr/hubs/webRTCHub");
await connection.Start();
MessageBox.Show(connection.ConnectionId);
I have also tried
connection = new HubConnection("http://localhost/signalr/webRTCHub");
and
connection = new HubConnection("http://localhost/");
Can someone point me into the right direction where to start?
I cant see it here, but you need to create a HubProxy for the Hub you want to connect to.
I assume your hub is "webRTCHub".
using(var connection = new HubConnection("http://localhost/"))
{
var hubProxy = _connection.CreateHubProxy("webRTCHub");
hubProxy.On("yourevent", () =>
{
_logger.Debug("Event recieved");
});
await _connection.Start();
}
Make sure you're registering your hub's route in app start, for example in case your using .NET core:
app.UseSignalR(routes =>
{
routes.MapHub<webRTCHubHub>("/signalr/hubs/webRTCHub");
});
While the class webRTCHub should look something like this:
public class webRTCHub : Hub
{
public async Task SendNotification(string userId, string message)
{
await Clients.User(userId).SendAsync("ReceiveNotification", "You have a new message: " + message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
For the js side:
"use strict";
var connection;
connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost/signalr/hubs/webRTCHub')
.build();
connection.on('ReceiveNotification', (message) => {
// show the message maybe
})
connection.start().catch(function (err) {
return console.error(err.toString())
});
connection.on('finished',(update)=>{
connection.stop();
});
To send back a message from the client to the server you should create a method as well in the class and call that from the script
Update: Packages and Services
for ASP.NET:
NuGet Packages:
Microsoft.AspNet.SignalR
Mapping Route in Application_Start
RouteTable.Routes.MapHubs("/signalr/hubs/webRTCHub", new webRTCHub());
for .NET Core:
Make sure to install the following package and add SignalR in ConfigureServices
Microsoft.AspNetCore.SignalR
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSignalR();
// ...
}
I guess you have not created any custom routes to handle signalr requests. You should initialize the HubConnection object without any url which will initialize the url of the connection object to "/signalr" as a default value.
connection = new HubConnection("");
or just
connection = new HubConnection();
Since you are using .NET FW and not .NET Core, you should configure the hub on the server like:
On your startup:
public void Configuration(IAppBuilder app)
{
//Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR(hubConfiguration);
});
}
The package you use:
Microsoft.AspNet.SignalR;
Microsoft.Owin;
Then on client side is the same for FW and Core, just point to your hub.

Heartbeat functionality from AppInsights does not appear in azure portal

I'm trying to get the heartbeat feature of AppInsights sdk to work but I'm having some trouble.
I have a simple app (just the default ASP.net core 2.2 project created by using dotnet new webapp) running on a k8 cluster inside Azure and is configured with the following settings:
public void ConfigureServices(IServiceCollection services)
{
ApplicationInsightsServiceOptions aiOptions
= new ApplicationInsightsServiceOptions();
// Disables adaptive sampling.
aiOptions.EnableAdaptiveSampling = false;
// Disables QuickPulse (Live Metrics stream).
aiOptions.EnableQuickPulseMetricStream = false;
aiOptions.InstrumentationKey = InstrumentationKey;
aiOptions.EnableHeartbeat=true;
services.AddApplicationInsightsTelemetry(aiOptions);
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddApplicationInsightsKubernetesEnricher();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
However, I can't see any properties in Application Insights related to the heartbeat functionality. I can see other stuff like the kubernetes pod name, etc.
Am I missing some configuration?
Thank you.
The Heartbeat feature is enabled by default as of base SDK 2.5.0 and the ability to configure the Heartbeat was added in 2.3.0-beta1.
I would suggest you to modify your startup file like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
ApplicationInsightsServiceOptions aiOpts =
new ApplicationInsightsServiceOptions();
aiOpts.EnableHeartbeat = true; // false to disable
services.AddApplicationInsightsTelemetry(aiOpts);
...
}
Also add using Microsoft.ApplicationInsights.AspNetCore.Extensions; in the top of your file.
Configure the Heartbeat feature in code by modifying the IHeartbeatPropertyManager directly. You can do this when you first obtain the property manager via the TelemetryModules.Instance singleton.
foreach (var md in TelemetryModules.Instance.Modules)
{
if (md is IHeartbeatPropertyManager heartbeatPropertyMan)
{
heartbeatPropertyMan.HeartbeatInterval = TimeSpan.FromMinutes(5.0);
heartbeatPropertyMan.ExcludedHeartbeatProperties.Add("osType");
...
Try this and see if it helps.
To query heartbeat events you can use the following KQL query:
customMetrics
| where name == "HeartbeatState"

Microsoft.Extensions.Caching.Redis select different database than db0

a question on understanding which redis database is used and how it can be configured.
i have a default ASP.NET Core Web Application and a default configured local redis-server (containing 15 databases)
Over Package Management Console i have installed:
Install-Package Microsoft.Extensions.Caching.Redis
Redis is configured in Startup.cs like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDistributedRedisCache(option =>
{
option.Configuration = "127.0.0.1";
option.InstanceName = "master";
});
}
The code to read and write values into the cache is taken from the docs:
var cacheKey = "TheTime";
var existingTime = _distributedCache.GetString(cacheKey);
if (!string.IsNullOrEmpty(existingTime))
{
return "Fetched from cache : " + existingTime;
}
else
{
existingTime = DateTime.UtcNow.ToString();
_distributedCache.SetString(cacheKey, existingTime);
return "Added to cache : " + existingTime;
}
But this code only uses the default database db0 no matter what i configure.
E.g. using this configuration:
services.AddDistributedRedisCache(option =>
{
option.Configuration = "127.0.0.1";
option.InstanceName = "db6";
});
leads to:
What do i have to configure to use e.g. db6?
Do i have to use Stackexchange.Redis for this?
Microsoft.Extensions.Caching.Redis is using Stackexchange.Redis to connect to Redis.
The Configuration string is documented on StackExchange.Redis. That said, you should be able to do:
services.AddDistributedRedisCache(option =>
{
option.Configuration = "127.0.0.1;defaultDatabase=4";
option.InstanceName = "master";
});

IdentityServer3 - rejected because invalid CORS path

We have an ASP.NET MVC application that is authenticating without issue against IdentityServer3, however the web API part of the application using ApiController's start to fail if the user waits before proceeding with AJAX functionality after about 3 minutes (before 3 mins everything seems fine).
The errors seen in Chrome are:
XMLHttpRequest cannot load
https://test-auth.myauthapp.com/auth/connect/authorize?client_id=ecan-farmda…gwLTk5ZjMtN2QxZjUyMjgxNGE4MDg2NjFhZTAtOTEzNi00MDE3LTkzNGQtNTc5ODAzZTE1Mzgw.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://test.myapp.com' is therefore not allowed
access.
On IE I get the following errors:
SCRIPT7002: XMLHttpRequest: Network Error 0x4c7, The operation was
canceled by the user.
Looking at IdentityServer3's logs I'm seeing entries like so:
2015-08-10 16:42 [Warning]
(Thinktecture.IdentityServer.Core.Configuration.Hosting.CorsPolicyProvider)
CORS request made for path: /connect/authorize from origin:
http://test.myapp.com but rejected because invalid CORS path
In the IdentityServer3 web application I'm giving clients AllowedCorsOrigins:
Thinktecture.IdentityServer.Core.Models.Client client = new Thinktecture.IdentityServer.Core.Models.Client()
{
Enabled = configClient.Enabled,
ClientId = configClient.Id,
ClientName = configClient.Name,
RedirectUris = new List<string>(),
PostLogoutRedirectUris = new List<string>(),
AllowedCorsOrigins = new List<string>(),
RequireConsent = false, // Don't show consents screen to user
RefreshTokenExpiration = Thinktecture.IdentityServer.Core.Models.TokenExpiration.Sliding
};
foreach (Configuration.RegisteredUri uri in configClient.RedirectUris)
{
client.RedirectUris.Add(uri.Uri);
}
foreach (Configuration.RegisteredUri uri in configClient.PostLogoutRedirectUris)
{
client.PostLogoutRedirectUris.Add(uri.Uri);
}
// Quick hack to try and get CORS working
client.AllowedCorsOrigins.Add("http://test.myapp.com");
client.AllowedCorsOrigins.Add("http://test.myapp.com/"); // Don't think trailing / needed, but added just in case
clients.Add(client);
And when registering the service I add a InMemoryCorsPolicyService:
app.Map("/auth", idsrvApp =>
{
var factory = new IdentityServerServiceFactory();
factory.Register(new Registration<AuthContext>(resolver => AuthObjects.AuthContext));
factory.Register(new Registration<AuthUserStore>());
factory.Register(new Registration<AuthRoleStore>());
factory.Register(new Registration<AuthUserManager>());
factory.Register(new Registration<AuthRoleManager>());
// Custom user service used to inject custom registration workflow
factory.UserService = new Registration<IUserService>(resolver => AuthObjects.AuthUserService);
var scopeStore = new InMemoryScopeStore(Scopes.Get());
factory.ScopeStore = new Registration<IScopeStore>(scopeStore);
var clientStore = new InMemoryClientStore(Clients.Get());
factory.ClientStore = new Registration<IClientStore>(clientStore);
var cors = new InMemoryCorsPolicyService(Clients.Get());
factory.CorsPolicyService = new Registration<ICorsPolicyService>(cors);
...
var options = new IdentityServerOptions
{
SiteName = "Authentication",
SigningCertificate = LoadCertificate(),
Factory = factory,
AuthenticationOptions = authOptions
};
...
});
I do note that the IdentityServer3 log entries say "CORS request made for path: /connect/authorize" rather than "CORS request made for path: /auth/connect/authorize". But looking through the IdentityServer3 source code suggests this probably isn't the issue.
Perhaps the InMemoryCorsPolicyService isn't being picked up?
Any ideas of why things aren't working for the AJAX called ApiController?
Thinktecture.IdevtityServer3 v1.6.2 has been installed using NuGet.
Update
I'm having a conversation with the IdentityServer3 developer, but am still having an issue reaching a resolution. In case it helps:
https://github.com/IdentityServer/IdentityServer3/issues/1697
Did you try adding https url also?- client.AllowedCorsOrigins.Add("https://test.myapp.com");
The documentation of IdentityServer says you should configure it on the client:
AllowedCorsOrigins = ... // Defaults to the discovery, user info, token, and revocation endpoints.
https://docs.duendesoftware.com/identityserver/v6/reference/options/#cors
CORS is a nightmare!
It's a browser thing which is why you're witnessing different behaviour in IE than in Chrome.
There are (at least) two ways that CORS is configured on the server. When a client makes a request with the Origin header you have to tell the server whether or not to accept it -- if accepted then the server adds the Access-Control-Allow-Origin header to the response for the browser.
In MVC / webAPI you have to add CORS services, set a CORS policy, and then .UseCors something like this:
builder.Services.AddCors((options =>
{
if (settings.AllowedCorsOrigins.Length > 0)
{
options.AddDefaultPolicy(builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().WithOrigins(settings.AllowedCorsOrigins);
});
}
if (isDevelopment)
{
options.AddPolicy("localhost", builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().SetIsOriginAllowed((string origin) => { return origin.Contains("localhost"); }); });
}
});
and
app.UseCors();
if (app.Environment.IsDevelopment())
{
app.UseCors("localhost");
}
Typically, you want the list of allowed hosts as an array of strings in your appsettings.json. And watch out for the boobytrap with SetIsOriginAllowedToAllowWildcardSubdomains.
As well as this, IdentityServer has its own additional CORS settings which are applied in addition to the standard MVC/webAPI settings. These are in the ClientCorsOrigin table and this doesn't support wildcard subdomains. You can sidestep this whole boobytrap by implementing your own ICorsPolicyService to use the same settings from your appsettings.json something like this
public class CorsPolicyService : ICorsPolicyService
{
private readonly CorsOptions _options;
public CorsPolicyService(IOptions<CorsOptions> options)
{
_options = options.Value;
}
private bool CheckHost(string host)
{
foreach (string p in _options.AllowedCorsOrigins)
{
if (Regex.IsMatch(host, Regex.Escape(p).Replace("\\*", "[a-zA-Z0-9]+"))) // Hyphen?
{
return true;
}
}
return false;
}
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(CheckHost(origin));
}
}

Categories