Hello everyone and thanks for the help in advance. I am developing a .Net 5 SignalR notification that utilizes SQL Notification. The application is partially working, sending a notification to the client on the first time the page loads. After some further debugging. I determined the SendCallNotifications method is called correctly upon each database update, however, the client does not update correctly. I'm not sure if the problem lies in my javascript, a threading problem since it is set up as a task, or whether there is some type of failure in the pipeline. Here is my hub code:
public class CallNotificationHub : Hub
{
//
// https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-5.0
// https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-5.0
//
private SqlDependency dependency;
// public Task OnConnected()
public override async Task OnConnectedAsync()
{
//return base.OnConnectedAsync();
await base.OnConnectedAsync();
}
//[HubMethodName("sendCallNotifications")]
public async Task SendCallNotifications()
{
SendEmail sendEmail = new SendEmail("SendCallNotifications Reached", "New SignalR5 Notification", "");
try
{
string connectionString = "connectionstring";
SqlDependency.Start(connectionString);
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
SqlCommand command = new SqlCommand();
command.CommandText = "SELECT [ID], [TimeEntered], [CallFrom], [CallTo] FROM [dbo].[tbl_Log_InboundTwilioCalls]";
command.Connection = connection;
command.CommandType = CommandType.Text;
dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
DataTable dt = new DataTable();
dependency.AddCommandDependency(command);
var reader = command.ExecuteReader();
//await Clients.All.SendAsync("ReceiveMessage", "user", "message");
}
catch (Exception ex)
{
SendEmail sendErrEmail = new SendEmail("SendCallNotifications Error", ex.ToString(), "");
}
await Clients.All.SendAsync("RecieveNotification", "Changed");
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SendEmail sendEmail = new SendEmail("Dependency Change", e.Type.ToString(), "");
if (e.Type == SqlNotificationType.Change)
{
CallNotificationHub nHub = new CallNotificationHub();
nHub.SendCallNotifications();
}
}
And here is my javascript.
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/CallNotificationHub").build();
connection.on("RecieveNotification", (result) => {
var li = document.createElement("li");
li.textContent = result;
document.getElementById("messagesList").appendChild(li);
console.log(result);
alert("Changed");
});
connection.start().then(function () {
connection.invoke("SendCallNotifications").catch(err => console.error(err));
}).catch(function (err) {
return console.error(err.toString());
});
I don't see any errors in the console, so I am not sure where to look next. Any help would be appreciated.
I just ran into a new error showing up in the console:
Error: An unexpected error occurred invoking 'SendCallNotifications' on the server.
invocationDescriptor.invocationId http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:2259
processIncomingData http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:2371
onreceive http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1923
onmessage http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4728
connect http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4725
connect http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4687
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4649
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4630
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4624
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4620
connect http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:4669
startTransport http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3842
createTransport http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3792
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3411
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3392
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3386
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3382
createTransport http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3745
startInternal http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3665
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3411
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3392
fulfilled http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3383
promise callback*step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3385
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3386
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3382
startInternal http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3596
start http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3480
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3411
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3392
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3386
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3382
start http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:3468
startInternal http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:2036
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1883
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1864
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1858
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1854
startInternal http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:2024
startWithStateTransitions http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:2006
step http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1883
verb http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1864
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1858
__awaiter http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1854
startWithStateTransitions http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1993
start http://192.168.0.3:61443/js/signalr/dist/browser/signalr.js:1989
<anonymous> http://192.168.0.3:61443/js/CallNotification2.js:14
CallNotification2.js:15:69
Trying to figure out what this is telling me. Haven't seen this before.
Related
Calling InvokeAsync on a connection hangs.
I'm referencing the following document to configure a group connection in Azure SignalR.
NOTE:
I am only able to establish a connection when NOT relying on a group configuration.
Client:
var negotiateJson = await _client.GetStringAsync($"{host}{"negotiatefn"}");
var negotiate = JsonConvert.DeserializeObject<NegotiateInfo>(negotiateJson);
var connection = new HubConnectionBuilder()
.AddNewtonsoftJsonProtocol()
.WithUrl(negotiate.Url, options => options.AccessTokenProvider = async () => negotiate.AccessToken)
.Build();
connection.Closed -= Connection_Closed;
connection.Closed += Connection_Closed;
connection.On<JObject>(hubMethodName, OnCourierUpdate);
await connection.StartAsync();
await connection.InvokeAsync("JoinGroup", sessionId); // HANGS APP HERE !!!
Server: Azure Function
public static class LocationFn
{
[FunctionName(nameof(LocationFn))]
public static async Task<IActionResult> Run(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"post",
Route = nameof(LocationFn))]
HttpRequest req,
[SignalR(HubName = "LocationHub")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
log.LogInformation($"{nameof(LocationFn)} has been invoked.");
try
{
using (var streamReader = new StreamReader(req.Body))
{
var json = await streamReader.ReadToEndAsync();
var subjectLocation = JsonConvert.DeserializeObject<SubjectLocation>(json);
await signalRMessages.AddAsync(
new SignalRMessage
{
Target = "LocationUpdate",
GroupName = subjectLocation.SessionId,
Arguments = new[] { subjectLocation }
});
var message = Log(log, subjectLocation);
return new OkObjectResult(message);
}
}
catch (Exception ex)
{
return new BadRequestObjectResult("There was an error: " + ex.Message);
}
}
}
public static class JoinGroupFn
{
[FunctionName(nameof(JoinGroupFn))]
public static async Task<IActionResult> Run(
[HttpTrigger(
AuthorizationLevel.Anonymous,
"post",
Route = nameof(JoinGroupFn))]
HttpRequest req,
[SignalR(HubName = "LocationHub")]
IAsyncCollector<SignalRMessage> signalRMessages,
ILogger log)
{
log.LogInformation($"{nameof(JoinGroupFn)} has been invoked.");
try
{
var groupId = await req.ReadAsStringAsync();
await signalRMessages.AddAsync(
new SignalRMessage
{
Target = "JoinGroup",
GroupName = groupId,
Arguments = new[] { groupId }
});
log.LogInformation($"{nameof(JoinGroupFn)} {groupId}");
return new OkObjectResult(groupId);
}
catch (Exception ex)
{
return new BadRequestObjectResult("There was an error: " + ex.Message);
}
}
}
Server: Hub
type LocationHub() as x =
inherit Hub()
let this = (x :> Hub)
member x.LocationUpdate(v:SubjectLocation) =
async { do! this.Clients.Group(v.SessionId).SendAsync("LocationUpdate", v) |> Async.AwaitTask
} |> Async.StartAsTask
member x.JoinGroup(groupId:string) =
async { do! this.Groups.AddToGroupAsync(this.Context.ConnectionId, groupId) |> Async.AwaitTask
} |> Async.StartAsTask
Im from Azure SignalR team, your service is running in Serverless mode, it do not allow any server connection, how can your F# hub run?
And in client side, connection.InvokeAsync require server ack, since the server connection can not connected, it cannot get any ack from service.
If you need hub server, please switch the service to Default mode, then run hub with Azure SignalR SDK, and verify the server is connected (by log or connection count in service metrics).
If you want serverless, please remove the hub, you can add a Serverless hub in Functions side, see: https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-concept-serverless-development-config#class-based-model.
And do not forget to set the upstream, see: https://learn.microsoft.com/en-us/azure/azure-signalr/concept-upstream#rule-settings.
I have this method:
public async static Task ExecuteSpAndForget(string sp, Location location, params SqlParameter[] parameters)
{
using (SqlConnection conn = await GetConnectionAsync(location))
{
SqlCommand cmd = new SqlCommand(sp, conn);
cmd.Parameters.AddRange(parameters);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = Int32.Parse(ConfigurationManager.AppSettings["CommandTimeout"]);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
public async static Task<SqlConnection> GetConnectionAsync(Location location)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["ConnectionString" + location]);
await conn.OpenAsync().ConfigureAwait(false);
return conn;
}
And in my log filter I applied to WebApiConfig.cs:
config.Filters.Add(new LogFilter());
And in LogFilter(), I call the database to write log
public static int WriteLog(HttpActionContext actionContext, Exception exception = null)
{
\\logic
var logTask = DatabaseHelper.ExecuteSpAndForget("writelogSP", Location.Log, sqlParams.ToArray());
\\return logic
}
When I am debugging locally, this never writes to the database, when I deployed it to IIS, it sometimes write to the database, sometimes not. I tested it locally in a console app, if I wait several seconds after calling before exit, data can be inserted into the database, otherwise no insert happens if no wait.
Why? How can I use it?
What you are referring to is a background task.
There are different ways of going about it. The simplest one is Task.Run with no await
public static int WriteLog(HttpActionContext actionContext, Exception exception = null)
{
\\logic
// fire and forget, no await
var logTask = Task.Run(async () =>
{
await DatabaseHelper.ExecuteSpAndForget("writelogSP", Location.Log, sqlParams.ToArray());
});
\\return logic;
}
You can check out this link https://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html for a better solution.
As follow this answer How to send Parameter/Query in HubConnection SignalR Core
I'm setting the client :
const connectionHub = new HubConnectionBuilder()
.withUrl(Constants.URL_WEB_SOCKET + '?token=123')
.build();
but how to get the token value server side?
public override async Task OnConnectedAsync()
{
_connectionId = Context.ConnectionId;
var token = Context.Items["token"]; // this is null
var token2 = Context.QueryString["token"]; // 'HubCallerContext' does not contain a definition for 'QueryString'
await base.OnConnectedAsync();
}
if you want to get token value in .net core, you can use the following code:
var httpContext = Context.GetHttpContext();
var tokenValue = httpContext.Request.Query["token"];
You can send a parameter in QueryString.
In your client, declare a string dictionary and connection
private Dictionary<string, string> _querystringdata = new Dictionary<string, string>();
private HubConnection _connection;
private const string HubUrl = "your hub url";
Then, assign the value you want to send
_querystringdata.Add("key", "Value");
_connection = new HubConnection(HubUrl, _querystringdata);
Start the connection
if (_connection.State == ConnectionState.Disconnected)
{
// Creating the signalrHub proxy
IHubProxy signalrHub = _connection.CreateHubProxy("SignalrHub");
Console.WriteLine("Initiating Connection");
// starting the signalr connection
_connection.Start().ContinueWith(task =>
{
if (task.IsFaulted)
{
Console.WriteLine("There was an error opening the connection:{0}", task.Exception.GetBaseException());
}
else
{
Console.WriteLine("Connected to server");
//Client methods which server can invoke
signalrHub.On<dynamic>("sendMessage", (data) =>
{
Console.WriteLine("Message:- {0}", data);
// do something
});
}
}).Wait();
}
then in your server signalR hub class
public override Task OnConnected()
{
try
{
// getting the value sent with query string
var token = Context.QueryString.Get("Key");
// do something like connection mapping etc
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return (base.OnConnected());
}
Context:
We are using Websockets in our ASP.NET Core 2 MVC app. This is our code in our Startup.cs class:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
[...]
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
if(context.Request.Path == "/ws")
{
WebSocket lWebSocket = await context.WebSockets.AcceptWebSocketAsync();
// a custom class that handles the websocket request → perhaps replace by an action controller.
var lHandleWebSocket = new HandleWebSocket();
await lHandleWebSocket.HandleConnection(context, lWebSocket);
}
}
}
}
The data transfer will be handled by the class 'HandleConnection':
public async Task HandleConnection(HttpContext context, WebSocket pWebSocket)
{
var lBuffer = new byte[1024 * 4];
WebSocketReceiveResult lWebSocketReceiveResult = null;
string lTmpString = string.Empty;
// get next values
lWebSocketReceiveResult = await pWebSocket.ReceiveAsync(new ArraySegment<byte>(lBuffer), CancellationToken.None);
// Doing something here...
// close connection
await pWebSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, "TestError", CancellationToken.None);
}
Here is a small snippet about the used JavaScript:
self.WebSocketChat = new WebSocket("ws://" + window.location.host + "/ws");
self.WebSocketChat.onclose = function (data) { console.log(data); };
self.WebSocketChat.onerror = function (data) { console.log(data); };
Description: The client connects with the server. When the client sends the first message to the Server, then the server closes the connection. In the client the 'onclose'-event fires as expected and prints the following information to the console:
Console: CloseEvent {isTrusted: true, wasClean: true, code: 1011, reason: "TestError", type: "close", …}
All is working as expected.
Problem:
Now we want to close the websocket connection when an exception occurs on server-side. We want to pass the message and callstack of the exception into the closing frame.
Catch Exceptions in the Startup.cs-File:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
[...]
app.Use(async (context, next) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
if(context.Request.Path == "/ws")
{
WebSocket lWebSocket = await context.WebSockets.AcceptWebSocketAsync();
// a custom class that handles the websocket request → perhaps replace by an action controller.
var lHandleWebSocket = new HandleWebSocket();
try
{
await lHandleWebSocket.HandleConnection(context, lWebSocket);
}
catch (Exception e)
{
// close connection
await lWebSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, e.ToString(), CancellationToken.None);
throw;
}
}
}
}
}
Throw an exception:
public async Task HandleConnection(HttpContext context, WebSocket pWebSocket)
{
var lBuffer = new byte[1024 * 4];
WebSocketReceiveResult lWebSocketReceiveResult = null;
string lTmpString = string.Empty;
// get next values
lWebSocketReceiveResult = await pWebSocket.ReceiveAsync(new ArraySegment<byte>(lBuffer), CancellationToken.None);
// throw an exception
throw new TestException("TestException");
}
When we send an message now to the server, the TestException will be thrown and catched as expected:
But on client-side the 'onclose'-Event does not fire.
self.WebSocketChat.onclose = function (data) { console.log(data); }; // nothing happens
Also the '.onerror' event does not fire.
Question:
How to close the Websocket connection when an exception occurs and pass the exception message to the client?
I have been stuck all day on a stupid problem with registering a user to my application.
Here is my code once the 'Register' button is clicked:
public ICommand RegisterCommand
{
get
{
return new Command(async() =>
{
var isSuccess = await _apiServices.RegisterAsync(Email, Password, ConfirmPassword);
if (isSuccess){
Message = "Registered Successfully";
}
else
{
Message = "Retry later";
}
});
}
}
Api services Register Async method:
public async Task<bool> RegisterAsync(string email, string password, string confirmPassword)
{
try
{
System.Diagnostics.Debug.WriteLine("Email: "+email);
var client = new HttpClient();
var model = new RegisterBindingModel
{
Email = email,
Password = password,
ConfirmPassword = confirmPassword
};
var json = JsonConvert.SerializeObject(model);
HttpContent content = new StringContent(json);
// content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("http://localhost:63724/api/Account/Register", content);
if (response.IsSuccessStatusCode)
{
return true;
}
return false;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine("Error: "+e);
throw;
}
}
}
The Error that I get is:
System.Net.Http.HttpRequestException: An error occurred while sending the request ---> System.Net.WebException: Error: ConnectFailure (Connection refused) ---> System.Net.Sockets.SocketException: Connection refused
at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000b6] in <6c708cf596db438ebfc6b7e012659eee>:0
at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0016d] in <6c708cf596db438ebfc6b7e012659eee>:0
--- End of inner exception stack trace ---
To me this is very frustrating as I can register a use using Postman with the exact same localhost address. I am following Houssem Dellai's Xamarin.Forms mvc web api tutorials which can be found here
I had an issue with httpclient during the development of my app. I believe there was an issue with the cross-platform implementation of the httpclient class. iOS didn't know how to handle it.
Instead I implemented a very simple httpclient library called flurl: http://tmenier.github.io/Flurl/
First, you will need to install flurl in all project directories (iOS, Android, and the PCL) then the implementation is very simple.
using Flurl;
using Flurl.Http;
public async Task<User> CreateUserAsync(RegisterUserModel userModel)
{
string url = "your/backend/here";
//resp is a user object received and automatically converted into a c# object through the use of .ReceiveJson<typeofobject>();
var resp = await (url).PostJsonAsync(userModel)
.ReceiveJson<User>();
if (resp.LoginSession != null)
{
//Raise my registered event to let other classes know to proceed
OnUserRegistered(resp);
}
return resp;
}
As you can see it makes httpclient implementation very simple. Hopefully this helps.