I read a lot here on stackoverflow before post this, and the only that help, but not really, was
SignalR call from controller
https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-2.2&tabs=visual-studio
I thought that that error they say was my error, but didnt work. So When the client send a message, this should trigger the hub, but didnt... At least did show up
My controller is like...
//Constructor
private readonly IHubContext<ChatHub> chatHub;
public UserController(IHubContext<ChatHub> hubContext)
{
this.chatHub = hubContext;
}
//Method
[HttpPost]
public async Task<ActionResult> Message(Message message)
{
await chatHub.Clients.All.SendAsync("ReceiveMessage", message.emisor, message.Text);
}
Chat.js
"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
//Disable send button until connection is established
document.getElementById("sendButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
connection.start().then(function () {
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
ChatHub class
public class ChatHub : Hub
{
public async Task SendMessage(string name, string message)
{
await Clients.All.SendAsync("ReceiveMessage", name, message);
}
}
So, this method on chathub, did not have any reference, If I debug, didnt call this method
You only call hub methods from the client. And you call client methods from the server.
IHubContext is on the server and all it does is send to clients. If you want to "call a hub method" from the server, then you need to refactor your code to have a common class that both the hub method calls and your controller calls.
Related
I am trying to set up a WebSocket connection using the .net SignalR and React app as a client to be able to send private messages.
Here is my code on the client side:
const setUpSignalRConnection = async () => {
const connection = new HubConnectionBuilder()
.withUrl("http://localhost:5000/messaginghub")
.build();
setConnection(connection);
connection.on("ReceiveMessage", (message: string) => {
console.log("Recieved Message", message);
setChatMessages((oldArray) => [...oldArray, message]);
});
try {
await connection.start();
} catch (err) {
console.log("Errors", err);
}
return connection;
};
const SendMessage = async () => {
if (connection) {
try {
console.log("sending message");
await connection.send("SendPrivateMessage", user.user.email, message);
} catch (e) {
console.log("Errors sending message", e);
}
} else {
alert("No connection to server yet.");
}
};
and my server side code
public async Task SendPrivateMessage(string userEmail, string message)
{
var RecivingMessageUser = _unitOfWork.UserRepository.GetByEmail(userEmail);
var currUserEmail = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var sender = _unitOfWork.UserRepository.GetByEmail(currUserEmail);
var newMessage = new MessagesDto
{
FromId = sender.UserId,
ToId = RecivingMessageUser.UserId,
MessageBody = message,
SentAt = DateTime.UtcNow,
};
await Clients.Group(userEmail).SendAsync("ReceiveMessage", message);
_unitOfWork.MessagingRepository.Insert(_mapper.Map<MessagesDto, Messages>(newMessage));
_unitOfWork.SaveChanges();
}
public override Task OnConnectedAsync()
{
var groupName = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
Groups.AddToGroupAsync(Context.ConnectionId, groupName);
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception ex)
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, Context.User.FindFirstValue(ClaimTypes.NameIdentifier));
return base.OnDisconnectedAsync(ex);
}
With console.logs I see that I am sending a message once and the message is stored in DB once but somehow on the other end, I am getting two received messages.
I am testing it on my local machine in two separate browsers.
What am I doing wrong?
Which method on your back-end is calling twice?
You are telling your message saved in to the DB once so it shouldn't be the SendPrivateMessage method which is calling towice.
EDITED: see at the bottom
I'm new to SignalR and trying to implement with it a simple scenario with Angular7 client using this library, and ASP.NET Core web API. All what I need is to use SignalR to notify the client about the progress of some lengthy operations in methods of the API controllers.
After a number of attempts, I got to a point where apparently the connection is established, but then when the long task starts running and sending messages, my client does not seem to receive anything, nor any traffic appears in web sockets (Chrome F12 - Network - WS).
I post here the details, which might also be useful to other newcomers (full source code at https://1drv.ms/u/s!AsHCfliT740PkZh4cHY3r7I8f-VQiQ). Probably I'm just making some obvious error, yet in the docs and googling around I could not find a code fragment essentially different from mine. Could anyone give a hint?
My start point for the server side was https://msdn.microsoft.com/en-us/magazine/mt846469.aspx, plus the docs at https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2. I tried to create a dummy experimental solution with that.
My code snippets in form of a recipe follow.
(A) Server Side
1.create a new ASP.NET core web API app. No authentication or Docker, just to keep it minimal.
2.add the NuGet package Microsoft.AspNetCore.SignalR.
3.at Startup.cs, ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
// CORS
services.AddCors(o => o.AddPolicy("CorsPolicy", builder =>
{
builder.AllowAnyMethod()
.AllowAnyHeader()
// https://github.com/aspnet/SignalR/issues/2110 for AllowCredentials
.AllowCredentials()
.WithOrigins("http://localhost:4200");
}));
// SignalR
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
and the corresponding Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// CORS
app.UseCors("CorsPolicy");
// SignalR: add to the API at route "/progress"
app.UseSignalR(routes =>
{
routes.MapHub<ProgressHub>("/progress");
});
app.UseHttpsRedirection();
app.UseMvc();
}
4.add a ProgressHub class, which just derives from Hub:
public class ProgressHub : Hub
{
}
5.add a TaskController with a method to start some lengthy operation:
[Route("api/task")]
[ApiController]
public class TaskController : ControllerBase
{
private readonly IHubContext<ProgressHub> _progressHubContext;
public TaskController(IHubContext<ProgressHub> progressHubContext)
{
_progressHubContext = progressHubContext;
}
[HttpGet("lengthy")]
public async Task<IActionResult> Lengthy([Bind(Prefix = "id")] string connectionId)
{
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskStarted");
for (int i = 0; i < 100; i++)
{
Thread.Sleep(500);
Debug.WriteLine($"progress={i}");
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskProgressChanged", i);
}
await _progressHubContext
.Clients
.Client(connectionId)
.SendAsync("taskEnded");
return Ok();
}
}
(B) Client Side
1.create a new Angular7 CLI app (without routing, just to keep it simple).
2.npm install #aspnet/signalr --save.
3.my app.component code:
import { Component, OnInit } from '#angular/core';
import { HubConnectionBuilder, HubConnection, LogLevel } from '#aspnet/signalr';
import { TaskService } from './services/task.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
private _connection: HubConnection;
public messages: string[];
constructor(private _taskService: TaskService) {
this.messages = [];
}
ngOnInit(): void {
// https://codingblast.com/asp-net-core-signalr-chat-angular/
this._connection = new HubConnectionBuilder()
.configureLogging(LogLevel.Debug)
.withUrl("http://localhost:44348/signalr/progress")
.build();
this._connection.on("taskStarted", data => {
console.log(data);
});
this._connection.on("taskProgressChanged", data => {
console.log(data);
this.messages.push(data);
});
this._connection.on("taskEnded", data => {
console.log(data);
});
this._connection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.error('Error while establishing connection: ' + err));
}
public startJob() {
this.messages = [];
this._taskService.startJob('zeus').subscribe(
() => {
console.log('Started');
},
error => {
console.error(error);
}
);
}
}
Its minimalist HTML template:
<h2>Test</h2>
<button type="button" (click)="startJob()">start</button>
<div>
<p *ngFor="let m of messages">{{m}}</p>
</div>
The task service in the above code is just a wrapper for a function which calls HttpClient's get<any>('https://localhost:44348/api/task/lengthy?id=' + id).
EDIT 1
After some more experimenting, I came with these changes:
use .withUrl('https://localhost:44348/progress') as suggested. It seems that now it no more triggers 404. Note the change: I replaced http with https.
do not make the API method async as it seems that the await are not required (i.e. set the return type to IActionResult and remove async and await).
With these changes, I can now see the expected log messages on the client side (Chrome F12). Looking at them, it seems that the connection gets bound to a generated ID k2Swgcy31gjumKtTWSlMLw:
Utils.js:214 [2019-02-28T20:11:48.978Z] Debug: Starting HubConnection.
Utils.js:214 [2019-02-28T20:11:48.987Z] Debug: Starting connection with transfer format 'Text'.
Utils.js:214 [2019-02-28T20:11:48.988Z] Debug: Sending negotiation request: https://localhost:44348/progress/negotiate.
core.js:16828 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
Utils.js:214 [2019-02-28T20:11:49.237Z] Debug: Selecting transport 'WebSockets'.
Utils.js:210 [2019-02-28T20:11:49.377Z] Information: WebSocket connected to wss://localhost:44348/progress?id=k2Swgcy31gjumKtTWSlMLw.
Utils.js:214 [2019-02-28T20:11:49.378Z] Debug: Sending handshake request.
Utils.js:210 [2019-02-28T20:11:49.380Z] Information: Using HubProtocol 'json'.
Utils.js:214 [2019-02-28T20:11:49.533Z] Debug: Server handshake complete.
app.component.ts:39 Connection started!
app.component.ts:47 Task service succeeded
So, it might be the case that I get no notification because my client ID does not match the ID assigned by SignalR (from the paper quoted above I had the impression that it was my duty to provide an ID, given that it is an argument of the API controller). Yet, I cannot see any available method or property in the connection prototype allowing me to retrieve this ID, so that I can pass it to the server when launching the lengthy job. Could this be the reason of my issue? If this is so, there should be a way of getting the ID (or setting it from the client side). What do you think?
It seems I've finally found it. The issue was probably caused by the wrong ID, so I started looking for a solution. A post (https://github.com/aspnet/SignalR/issues/2200) guided me to the usage of groups, which seems the recommended solution in these cases. So, I changed my hub so that it automatically assign the current connection ID to a "progress" group:
public sealed class ProgressHub : Hub
{
public const string GROUP_NAME = "progress";
public override Task OnConnectedAsync()
{
// https://github.com/aspnet/SignalR/issues/2200
// https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/working-with-groups
return Groups.AddToGroupAsync(Context.ConnectionId, "progress");
}
}
Now, my API controller method is:
[HttpGet("lengthy")]
public async Task<IActionResult> Lengthy()
{
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskStarted");
for (int i = 0; i < 100; i++)
{
Thread.Sleep(200);
Debug.WriteLine($"progress={i + 1}");
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskProgressChanged", i + 1);
}
await _progressHubContext
.Clients
.Group(ProgressHub.GROUP_NAME)
.SendAsync("taskEnded");
return Ok();
}
And of course I updated the client code accordingly, so that it does no more have to send an ID when invoking the API method.
Full demo repository available at https://github.com/Myrmex/signalr-notify-progress.
You set the route for the hub as /progress, but then you're attempting to connect to /signalr/progress, which is going to be a 404. If you open the developer console, you should have an connection error there telling you as much.
Just wanted to add that OP was on the right track with the connection ID.
I send it along optionally in the form data.
I'm just reporting progress while uploading to AWS and I handle with SignalR like this:
Controller
[HttpPost("MyPostRoute")]
public async Task<ActionResult> UploadFiles([FromForm] List<IFormFile> files, [FromForm] string? connectionId)
{
await _logic.UploadFiles(files, connectionId);
return Ok();
}
Logic
public async Task<bool> UploadFiles(List<IFormFile> files, string? connectionId)
{
foreach (var file in files)
{
Guid fileGuid = Guid.NewGuid();
string extension = Path.GetExtension(file.FileName);
using (var transferUtility = new TransferUtility(_awsClient))
{
var request = new TransferUtilityUploadRequest()
{
BucketName = _configuration["AwsBucket"],
Key = fileGuid + extension,
InputStream = file.OpenReadStream()
};
if (connectionId != null)
{
AwsFileProgress progress = new()
{
ConnectionId = connectionId,
FileName = file.FileName
};
request.UploadProgressEvent += (sender, e) => Request_UploadProgressEvent(sender, e, progress);
}
await transferUtility.UploadAsync(request);
}
}
return true;
}
private async void Request_UploadProgressEvent(object? sender, UploadProgressArgs e, AwsFileProgress progress)
{
progress.ProgressPercent = e.PercentDone;
await _hub.Clients.Client(progress.ConnectionId).SendAsync("AWSProgress", progress);
}
I made a model for the progress stuff:
public class AwsFileProgress
{
public string ConnectionId { get; set; }
public string FileName { get; set; }
public int ProgressPercent { get; set; } = 0;
}
And finally, the front-end:
import { Injectable } from '#angular/core';
import * as signalR from "#microsoft/signalr";
import { environment } from "#environment";
import { AwsFileProgress } from "#models/signalr/awsFileProgress";
#Injectable({
providedIn: 'root',
})
export class AwsSignalRService {
private hubConnection!: signalR.HubConnection
public data!: AwsFileProgress;
public startConnection = () => {
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.signalRhubRoot}/awsprogresshub`)
.build();
this.hubConnection
.start()
.then(() => { console.log('AWS SignalR Service Connection started') })
.catch(err => console.log('Error while starting AWS SignalR Service WebSocket: ' + err))
}
/////////////////////////////////////////////////////////
//This is all you have to do to get the connection ID:
public getConnectionId() : string | null {
return this.hubConnection.connectionId;
}
/////////////////////////////////////////////////////////
public addAwsProgressListener = () => {
this.hubConnection.on('AWSProgress', (awsFileProgress : AwsFileProgress) => {
this.data = awsFileProgress;
console.log(awsFileProgress.progressPercent);
});
}
}
Then the actual component it's injected into:
import { AwsSignalRService } from "#shared/services/aws-signalr-service";
#Component({
selector: 'settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
constructor(
private awsSignalRService: AwsSignalRService
) {
awsSignalRService.startConnection();
awsSignalRService.addAwsProgressListener();
}
^ Whatever is in our progress listener is handled here.
Making the request (and reporting client-side upload progress) is handled like this.
Note that I just use a basic (change) handler on a file input (<input type="file" (change)="fileUploadTest($event)"/>)
fileUploadTest(e: any) {
const formData = new FormData();
var cId = this.awsSignalRService.getConnectionId();
for(let file of e.target.files as File[]) {
formData.append('files', file, file.name);
};
if(cId != null)
formData.append('connectionId',cId);
const url: string = `${environment.ApiRoot}/MyPostRoute`;
this.http.post<any>(url,formData, {
reportProgress: true,
observe: 'events'
}).subscribe({
next: (response) => console.log(response),
error: (error) => console.log(error)
});
}
And the results in console of my API uploading data to AWS:
I'm trying to use SignalR for Asp Net Core 2.1 in order to send a message from a controller method which call is triggered from a test button in Angular.
The behavior I'd expect is that when I click the button, my service invokes the controller method, which sends the test message. Then, I will simply log the message.
I want to manage this in a service in order to avoid code duplication in all of the components.
I've read some examples like this question about using SignalR in a service (I've used the second solution) and this article and the official docs but even with applying these concepts it don't seems to work. (So, or I'm absolutely applying them in a wrong way or there's still something missing but I can't find out what...)
The client connects to the Message Hub successfully and if I click the button, the method is getting hit but I don't get any message and instead I get this warning in the Chrome console:
Warning: No client method with the name 'SendAsync' found.
Sending messages works fine, the issue is just with receiving them...
The question is: what am I doing wrong? Is the error on the back-end side or in the Angular side?
I share with you all of my code (the button and the service to call the controller method are not relevant since the call to the service goes fine):
> Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSignalR();
}
//...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseSignalR(routes =>
{
//...
routes.MapHub<MessageHub>("/messagehub");
//...
});
}
> MessageHub.cs
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
public interface ITypedHubClient
{
Task SendAsync(string title, string name, string message);
}
> MessageController.cs
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public MessageController(IHubContext<MessageHub, ITypedHubClient> messageHubContext)
{
_messageHubContext = messageHubContext;
}
[HttpPost("Test")]
public async Task<IActionResult> Test()
{
try
{
await _messageHubContext.Clients.All.SendAsync("ReceiveMessage","test", "test");
return Ok(true);
}
catch (Exception e)
{
return BadRequest(e);
}
}
> communication.service.ts
#Injectable()
export class CommunicationService {
private _hubConnection: HubConnection | undefined;
public async: any;
message = '';
messages: string[] = [];
private newmessage = new Subject<string>();
message$ = this.newmessage.asObservable();
constructor() {
this._hubConnection = new signalR.HubConnectionBuilder()
.withUrl('/messagehub')
//.configureLogging(signalR.LogLevel.Information)
.configureLogging(signalR.LogLevel.Debug)
.build();
this._hubConnection.start().catch(err => console.error(err.toString()));
this._hubConnection.on('SendMessage', (user: any, message:any) => {
const received = `Received: ${message}`;
//this.messages.push(received);
this.newmessage.next(received);
console.log("got something new...", received);
});
}
clear() {
this.newmessage.next("");
}
public sendMessage(): void {
const data = `Sent: ${this.message}`;
if (this._hubConnection) {
this._hubConnection.invoke('SendMessage', 'AAA' ,data);
}
this.messages.push(data);
}
}
In signalr core 2.1 you can use strongly typed hubs to declare in an interface what actions can be called on the clients :
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendMessageToClient(title, user, message);
}
}
public interface ITypedHubClient
{
Task SendMessageToClient(string title, string name, string message);
}
in the controller :
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public async Task<IActionResult> Test()
{
await _messageHubContext.Clients.All.SendMessageToClient("test", "test", "test");
return Ok("ok");
}
in the client :
_hubConnection.on('SendMessageToClient', (title, user, message) => {
const received = `title: ${title}, name: ${user}, message: ${message}`;
console.log(received);
});
If you don't use strongly typed hub, then to call the same method in the client it becomes :
public class MessageHub : Hub
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendAsync("SendMessageToClient", title, user, message);
}
}
In that case you can use the SendAsync method on the client proxy, it's first parameter is the name of the method you want to call.
Update :
When we define a strongly typed hub with an interface, all interface methods must return a Task. With custom methods, signalr generates methods that call SendCoreAsync. That allow us to call these methods asynchronously.
If the return type of the interface methods is not a Task we get the error : All client proxy methods must return 'System.Threading.Tasks.Task'
After reviewing and trying many of the suggestions surrounding the error message:
"An asynchronous module or handler completed while an asynchronous
operation was still pending."
I found myself in the situation where even though the call to the MVC accountController actually EXECUTED the desired code (an email was sent to the right place with the right content) and a Try/Catch in the controller method would not 'catch' the error, the AngularJS factory that was initiating the call would receive a server error "page".
Factory:(AngularJS)
InitiateResetRequest: function (email) {
var deferredObject = $q.defer();
$http.post(
'/Account/InitiateResetPassword', { email: email }
)
.success(function (data) {
deferredObject.resolve(data);
})
.error(function (data) {
//This is a stop-gap solution that needs to be fixed..!
if (data.indexOf("An asynchronous module or handler completed while an asynchronous operation was still pending.") > 0) {
deferredObject.resolve(true);
} else {
deferredObject.resolve(false);
}
});
return deferredObject.promise;
}
MVC Controller (C#):
[HttpPost]
[AllowAnonymous]
public async Task<int> InitiateResetPassword(string email)
{
try
{
_identityRepository = new IdentityRepository(UserManager);
string callbackUrl = Request.Url.AbsoluteUri.Replace(Request.Url.AbsolutePath, "/account/reset?id=");
await _identityRepository.InitiatePasswordReset(email, callbackUrl);
return 0;
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
return 1;
}
}
Identity Repository/InitiatePasswordReset:
public async Task InitiatePasswordReset(string email, string callbackUrl)
{
try
{
var u = await _applicationUserManager.FindByEmailAsync(email);
string passwordResetToken = await GetResetToken(u);
callbackUrl = callbackUrl + HttpUtility.UrlEncode(passwordResetToken);
await _applicationUserManager.SendEmailAsync(u.Id, RESET_SUBJECT, string.Format(RESET_BODY, u.FirstName, u.LastName, callbackUrl));
}
catch(Exception ex)
{ //another vain attempt to catch the exception...
Console.WriteLine(ex.ToString());
throw ex;
}
}
The EmailService injected into the ASP.NET Identity "ApplicationUserManager"
public class EmailService : IIdentityMessageService
{
XYZMailer xyzMailer;
public EmailService()
{
xyzMailer = XYZMailer.getCMRMailer();
}
public async Task SendAsync(IdentityMessage message)
{
//original code as posted:
//await Task.FromResult(xyzMailer.SendMailAsync(message));
//solution from #sirrocco-
await xyzMailer.SendMailAsync(message);
}
}
and finally...the XYZMailer class
class XYZMailer
{
#region"Constants"
private const string SMTP_SERVER = "XYZEXCHANGE.XYZ.local";
private const string NO_REPLY = "noReply#XYZCorp.com";
private const string USER_NAME = "noreply";
private const string PASSWORD = "theMagicP#55word"; //NO, that is not really the password :)
private const int SMTP_PORT = 587;
private const SmtpDeliveryMethod SMTP_DELIVERY_METHOD = SmtpDeliveryMethod.Network;
#endregion//Constants
internal XYZMailer()
{
//default c'tor
}
private static XYZMailer _XYZMailer = null;
public static XYZMailer getXYZMailer()
{
if (_XYZMailer == null)
{
_XYZMailer = new XYZMailer();
}
return _XYZMailer;
}
public async Task<int> SendMailAsync(IdentityMessage message)
{
#if DEBUG
message.Body += "<br/><br/>DEBUG Send To: " + message.Destination;
message.Destination = "me#XYZCorp.com";
#endif
// Create the message:
var mail =
new MailMessage(NO_REPLY, message.Destination)
{
Subject = message.Subject,
Body = message.Body,
IsBodyHtml = true
};
// Configure the client:
using (SmtpClient client = new SmtpClient(SMTP_SERVER, SMTP_PORT)
{
DeliveryMethod = SMTP_DELIVERY_METHOD,
UseDefaultCredentials = false,
Credentials = new System.Net.NetworkCredential(USER_NAME, PASSWORD),
EnableSsl = true
})
{
// Send:
await client.SendMailAsync(mail);
}
return 0;
}
}
(note: originally the controller method was simply "public async Task InitiateResetPassword, I added the return type as an attempt to trap the error on the server. At runtime, return 0; does hit (breakpoint) the catch does not get hit and at the client")
At the moment I am simply filtering for the expected error message and telling javascript to treat it as a success. This solution has the benefit of 'actually working'... but it is not 'ideal'.
How do I prevent the error on the server?
or alternately,
How do I catch the error on the server?
You need to remove await Task.FromResult from EmailService because that makes it so the code executes synchronously instead of async.
As to why the the exception was still raised and bubbled up outside the try/catch - I suspect the Task.FromResult was the culprit here too - if you now raise an exception in SendAsync (just to test it) you should catch in the controller.
I have a signalR Server(Console Application) and a client application(Asp.net MVC5)
How I can send message to specific user in OAuth Membership.
Actually I can't resolve sender user from hub request context with.
Context.User.Identity.Name
My Hub
public class UserHub : Hub
{
#region Hub Methods
public void LoggedIn(string userName, string uniqueId, string ip)
{
Clients.All.userLoggedIn(userName, uniqueId, ip);
}
public void LoggedOut(string userName, string uniqueId, string ip)
{
var t = ClaimsPrincipal.Current.Identity.Name;
Clients.All.userLoggedOut(userName, uniqueId, ip);
}
public void SendMessage(string sendFromId, string userId, string sendFromName, string userName, string message)
{
Clients.User(userName).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
#endregion
}
Start hub class(Program.cs)
class Program
{
static void Main(string[] args)
{
string url = string.Format("http://localhost:{0}", ConfigurationManager.AppSettings["SignalRServerPort"]);
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
Keep connectionId with userName by creating a class as we know that Signalr only have the information of connectionId of each connected peers.
Create a class UserConnection
Class UserConnection{
public string UserName {set;get;}
public string ConnectionID {set;get;}
}
Declare a list
List<UserConnection> uList=new List<UserConnection>();
pass user name as querystring during connecting from client side
$.connection.hub.qs = { 'username' : 'anik' };
Push user with connection to this list on connected mthod
public override Task OnConnected()
{
var us=new UserConnection();
us.UserName = Context.QueryString['username'];
us.ConnectionID =Context.ConnectionId;
uList.Add(us);
return base.OnConnected();
}
From sending message search user name from list then retrive the user connectionid then send
var user = uList.Where(o=>o.UserName ==userName);
if(user.Any()){
Clients.Client(user.First().ConnectionID ).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
DEMO
All of these answers are unnecessarily complex. I simply override "OnConnected()", grab the unique Context.ConnectionId, and then immediately broadcast it back to the client javascript for the client to store and send with subsequent calls to the hub server.
public class MyHub : Hub
{
public override Task OnConnected()
{
signalConnectionId(this.Context.ConnectionId);
return base.OnConnected();
}
private void signalConnectionId(string signalConnectionId)
{
Clients.Client(signalConnectionId).signalConnectionId(signalConnectionId);
}
}
In the javascript:
$(document).ready(function () {
// Reference the auto-generated proxy for the SignalR hub.
var myHub = $.connection.myHub;
// The callback function returning the connection id from the hub
myHub.client.signalConnectionId = function (data) {
signalConnectionId = data;
}
// Start the connection.
$.connection.hub.start().done(function () {
// load event definitions here for sending to the hub
});
});
In order to be able to get "Context.User.identity.Name", you supposed to integrate your authentication into OWIN pipeline.
More info can be found in this SO answer: https://stackoverflow.com/a/52811043/861018
In ChatHub Class Use This for Spacific User
public Task SendMessageToGroup(string groupName, string message)
{
return Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId}: {message}");
}
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}.");
}
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}.");
}