I am using SignalR in my application in order to be able to automatically invoke one JavaScript method whenever a specific action method of a ASP.NET MVC controller is accessed/hit/invoke. Everything works fine on the first hit. But when I try to hit the controller action second time my HttpWebRequest times out. Following is my controller:
public class ShowRoomHubController : Controller
{
public void UpdateDressingRoomData()
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ShowRoomHub>();
hubContext.Clients.All.acceptGreet();
}
}
My hub is as follow:
public class ShowRoomHub : Hub
{
public void Hello()
{
Clients.All.hello();
}
public void GreetAll()
{
// Call the addNewMessageToPage method to update clients.
Clients.All.acceptGreet("test now");
}
}
Calling UpdateDressingRoomData action from HttpWebRequest or HttpClient works for first time only then it starts timing out. Here is my console application code (HTTP client):
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:81/ShowRoomHub/UpdateDressingRoomData");
var response = request.GetResponse();
response.Close();
Or
var httpClient = new HttpClient();
var response = httpClient.GetAsync("http://localhost:81/ShowRoomHub/UpdateDressingRoomData").Result;
Here is my javascript client side code:
connection = $.hubConnection();
//Creating proxy
this.proxy = connection.createHubProxy('showRoomHub');
connection.logging = true;
//Publishing an event when server pushes a greeting message
this.proxy.on('acceptGreet', function () {
//alert('message');
$rootScope.$emit("acceptGreet", null);
});
//Starting connection
connection.start().done(function () {
//alert('started');
});
Any idea what's wrong in my code?
Related
I'm developing a web application with ASP.NET Core (MVC) that will display some information to a group of users. They will be authenticated using Windows Athentication. I recently started using SignalR because I want them to receive push notifications based on certain actions that are triggered from another program that is written in Python. That Python program send HTTP post requests to an API controller within the web app. However I want those notifications to be sent to specific users.
Here's my post method in the api controller:
[HttpPost]
public void Post([FromBody] Notice notice)
{
//_pushHub.Clients.All.SendAsync("ReceiveMessage", notice.Title, notice.Body);
_pushHub.Clients.User(_nuidProvider.GetUserId(_connection)).SendAsync("ReceiveMessage", notice.Title, notice.Body);
}
It receives an object of class Notice which has two fields: Title and Body.
Then I send those values by using the method SendAsync(). When I execute it for all clients, it gets sent successfully. However, since I need it to be sent to specific users, I tried following these instructions: https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0
I created this class as suggested:
namespace MyApp.Services
{
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
}
Then I added this to the StartUp at ConfigureServices:
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
services.AddSignalR();
And lastly, I modified the JS file to include the sentence: options.UseDefaultCredentials = true; as suggested in the instructions:
const conn = new signalR.HubConnectionBuilder().withUrl('/pushHub', options => {
options.UseDefaultCredentials = true;
}).build();
conn.on('ReceiveMessage', (title, body) => {
const t = title.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
const b = body.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
const date = new Date().toLocaleTimeString();
const msg = date + ' ' + t + ' ' + b;
const li = document.createElement('li');
li.innerHTML = msg;
document.getElementById('msgsList').appendChild(li);
Push.create(title, {
body: body,
timeout: 9000
});
});
conn.start().catch(err => console.error(err.toString()));
The complete api controller looks like this, but I'm not sure if I'm doing it right:
namespace MyApp.Controllers.api
{
[Route("api/[controller]")]
[ApiController]
public class NoticesController : ControllerBase
{
private readonly IHubContext<PushHub> _pushHub;
private readonly IUserIdProvider _nuidProvider;
private readonly HubConnectionContext _connection;
public NoticesController(IHubContext<PushHub> pushHub, IUserIdProvider nuidProvider, HubConnectionContext connection)
{
_pushHub = pushHub;
_nuidProvider = nuidProvider;
_connection = connection;
}
// POST api/<NoticesController>
[HttpPost]
public void Post([FromBody] Notice notice)
{
//_pushHub.Clients.All.SendAsync("ReceiveMessage", notice.Title, notice.Body);
_pushHub.Clients.User(_nuidProvider.GetUserId(_connection)).SendAsync("ReceiveMessage", notice.Title, notice.Body);
}
}
}
I think I'm not passing a proper connection object to the GetUserId method.
Any advice will be completely appreciated. Thanks!
Using Audit.Net is it possible to create an audit scope for httpClient requests, in a similar way to the MVC.Core or WebAPI.Core Middleware?
I've tried something like this but not having much success, generally it'll timeout the app.
AuditScope scope = null;
try {
using(HttpClient client = new HttpClient) {
scope = await AuditScope.CreateAsync("",() => client)
// code to initialise the Httpclient
}
}
finally {
await scope.DisposeAsync();
}
I think the only option to hook into the HttpClient is to use a custom HttpClientHandler so you can intercept the rest calls.
Just as an example:
public class AuditClientHandler : HttpClientHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var options = new AuditScopeOptions()
{
EventType = $"{request.Method.Method} {request.RequestUri.AbsoluteUri}",
CreationPolicy = EventCreationPolicy.InsertOnStartReplaceOnEnd,
ExtraFields = new
{
request = GetRequestAudit(request)
}
};
using (var scope = AuditScope.Create(options))
{
var response = await base.SendAsync(request, cancellationToken);
scope.SetCustomField("response", GetResponseAudit(response));
return response;
}
}
}
I've used the InsertOnStartReplaceOnEnd creation policy, so the request is saved before it's sent to the server, and the response is added to the event and saved afterwards.
The implementation of GetRequestAudit / GetResponseAudit is up to you, just return an object (that can be serialized) with the information you want to log.
So each time you need to audit an HttpClient instance, you need to pass the handler to its constructor:
var cli = new HttpClient(new AuditClientHandler());
var response = await cli.GetAsync("http://google.com");
Anyway I will evaluate providing a new library (Audit.HttpClient?) with a configurable Handler so the implementation could be cleaner.
Update
You can now use the Audit.HttpClient extension for a cleaner implementation. Take a look at the documentation here
For some reason I'm having trouble simply calling a client-side method with Signal-R from my ASP.NET MVC App and was wondering if someone can assist.
Here's what I have so far. I have a Hub called "NotifyUser" with the following:
public class NotifyUser : Hub
{
public void Send(string name, string message)
{
Clients.All.addNewMessageToPage(name, message);
}
}
On the client in a javascript file in Document.Ready() I have:
$(function ()
{
var notifyUser = $.connection.notifyUser;
notifyUser.client.addNewMessageToPage= function (name, message)
{
var x = 5;
};
$.connection.hub.start();
})
and I'm trying to call the client method from a controller on the server like this:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<NotifyUser>();
hubContext.Clients.All.Send("George", "Hi");
When I set a breakpoint in the "addNewMessageToPage" function on the client it is never hit. Can someone assist? Thanks.
Pete
When you want call a hub method from a controller it must declare as static method like below:
public class NotifyUser : Hub
{
public static void Send(string name, string message)
{
Clients.All.addNewMessageToPage(name, message);
}
}
this changing solve your problem but note that if you want call a hub method from client java script it must be non-static so you have to declare two methods: one static for controller calling and the other one non-static for java-script calling
I have the following in the TreeController controller in a small web API:
[HttpGet("GetDirectories")]
public IActionResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir); ;
return Json(ret);
}
private List<DirectoryInfo> GetDirectories(string parentDir)
{
var dirInfo = new DirectoryInfo(parentDir);
return dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly).ToList();
}
When I try and call this action from Postman, I get told
Could not get any response There was an error connecting to
http://localhost:59243/api/Tree/GetDirectories.
Now the default, test, controller that comes with the project template is unchanged:
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] {"value1", "value2"};
}
...
}
And when I have the API running in Visual Studio, I can get a proper response from the Values controller, but not from the TreeController, yet they are almost exactly the same. And, when I call the Tree/GetDirectories` action, a breakpoint in that action method is hit, and I can single step through the very few lines that my method has, and they all execute fine.
The problem only becomes apparent when that last line of the action executes:
return Json(ret);
Then I get shown that Postman Could not get any response despite no exception being raised; while debugging the code, it looks like everything should work fine, and the requests to the Values controller work fine.
Your action method should return either the specific result type (JsonResult when you return Json ) or IActionResult.
Example:
[HttpGet("GetDirectories")]
public JsonResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir);
return Json(ret);
}
OR
[HttpGet("GetDirectories")]
public IActionResult GetDirectories()
{
var baseDir = _config["QuickShare:BaseDir"];
if (string.IsNullOrWhiteSpace(baseDir))
{
throw new InvalidOperationException("'QuickShare:BaseDir' is not configured");
}
var ret = GetDirectories(baseDir) ;
return Ok(ret);
}
You can get more help from Microsoft Documentation: Formatting Response Data
I have an mvc web apllication with signalr and i want to update the table in the published web api.
calling web api controller to get users inside the Onconnected method works fine:
public override async Task OnConnected()
{
var users = await _client.GetAsync("chats/users");
Clients.All.userConnected();
}
But when i place the code inside the OnDisconnected method it gives me an error:
public override async Task OnDisconnected(bool stopCalled)
{
var users = await _client.GetAsync("chats/users");
}
Why is this happening? this is the whole Hub code:
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
private HttpClient _client;
public ChatHub()
{
AccessDelegatingHandler handler = new AccessDelegatingHandler();
_client = HttpClientFactory.Create(handler);
_client.BaseAddress = new Uri(ClsConfig.GetConfiguration().APIBaseAddress);
}
// Send new message to group chat.
public static void SendGroupMessage(MessageDetailModel messageDetail)
{
hubContext.Clients.All.newGroupMessageReceived(messageDetail);
}
public override async Task OnConnected()
{
var users = await _client.GetAsync("chats/users");
Clients.All.userConnected();
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
public override async Task OnDisconnected(bool stopCalled)
{
var users = await _client.GetAsync("chats/users");
}
EDIT:
I found out that when i place var user = Context.User.Identity; inside the OnDisconnected method the user is IsAuthenticated = true but when i place a break point inside the AccessDelegatingHandler class the var identity = (ClaimsIdentity)HttpContext.Current.User.Identity; line gives an error and is IsAuthenticated = false
By the time the onDisconnected event fires, you are likely already disconnected, and there is no guarantee that your code will run, (its a known issue with Signalr) also are you monitoring the onDisconnected in the client or the server? It looks like you are trying to handle it from the server, and you should be handling it from the client.
This link should help to understand why this is the way it is.
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/handling-connection-lifetime-events#clientdisconnect