ASP.NET MVC 5 Server code cannot call Javascript client SignalR function - c#

In our application I want to have a "realtime" grid of notifications that the user can monitor. That grid is a KendoUI grid with SignalR-transport and is read-only. So the only hub method defined is a Read method.
This is my hub that is hooked up to the KendoUI grid.:
[HubName( "NotificationsHub" )]
public class NotificationsHub : Hub
{
public IApplicationSupportService Service { get; set; } //injeccted with Autofac
public NotificationsHub()
{
}
public IEnumerable<EventViewModel> Read()
{
var events = from e in Service.FindEvents()
select new EventViewModel
{
EventId = e.EventId,
Text = e.Text,
CreatedOn = e.CreatedOn
};
return events;
}
}
I also have a NServiceBus message handler that is supposed to call a client side method called "addEventToPage". The notification handler receives a nevent from the service bus and is supposed to call all clients to update their grid.
This is the message handler on the server side, that uses a singleton helper to call the hub's clients via context.Clients.All.addEventToPage():
//NServiceBus message handler subscribes to an event, then calls clients
public class NotificationsHandler : IHandleMessages<IBusinessEventReceived>
{
public void Handle( INewEventWasSaved message )
{
NotifyTicker.Instance.UpdateClient( message.EventId, message.Text );
}
}
public class NotifyTicker
{
// Singleton instance
private static readonly Lazy<NotifyTicker> _instance = new Lazy<NotifyTicker>(
() => new NotifyTicker( GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>() ) );
private IHubContext _context;
private NotifyTicker( IHubContext context )
{
_context = context;
}
public static NotifyTicker Instance
{
get { return _instance.Value; }
}
public void UpdateClient( int eventId, string text )
{
_context.Clients.All.addNewEventToPage( eventId, text );
}
}
The client code:
<script>
var hubProxy;
var hubStart;
$(function () {
var connection = $.connection;
$.connection.hub.logging = true;
hubProxy = connection.NotificationsHub;
//this function is called by message handler on server
hubProxy.client.addNewEventToPage = function (eventId, text) {
console.log(eventId + ' ' + text);
};
hubStart = $.connection.hub.start()
.done(function () { console.log('Now connected, connection ID=' + $.connection.hub.id); })
.fail(function () { console.log('Could not Connect!'); });
$("#grid").kendoGrid({
editable: false,
columns: [
{ field: "Text" }
],
dataSource: {
type: "signalr",
autoSync: true,
schema: {
model: {
id: "EventId",
fields: {
"EventId": { editable: false, nullable: true },
"Text": { type: "string" }
}
}
},
transport: {
signalr: {
promise: hubStart,
hub: hubProxy,
server: {
read: "read"
},
client: {
read: "read"
}
}
}
}
});
});
</script>
As you can see I am adding the "addEventToPage" method BEFORE the hub proxy starts. But the method is not called, period.
Originally, that method was supposed to add a EventViewModel to the KendoUI grid's datasource like so:
dataSource.add({
EventId: eventId,
Text: text
});
But that didn't work. It can't even write to the console.
The connection is established successfully, with web sockets. I can confirm that in the Chrome console output.
What am I missing?
Is there maybe a better way to update the grid without a customer client -side function? Maybe I can tell teh grid to Re-Read?

Related

Get Data From Post Request Angular/.NET Core

I'm having trouble getting a JSON response from a POST Request from my .Net Core server. In essence I would be using this POST request like a GET request from the server. I believe I'm passing in the correct headers, however, in my console error I'm getting
ERROR TypeError: Cannot read property 'sessionId' of undefined
I suspect it's something that has to do with the type and/or model. Or possibly how I'm calling it in the service. If I need to add anything for clarification, lmk.
.NET CORE Server Code
Action.Dto
{
public class ActionDto
{
public string SessionId { get; set; }
public Tag ActionTag { get; set; }
public ActionParams Args { get; set; }
}
}
ActionService.cs
{
ActionResponse LaunchAction(string sessionId, Tag actionTag, ActionParams args, UserState userState);
}
Action Controller
public IActionResult LaunchAction([FromBody]ActionDto launchActionParameters)
{
var sessionId = launchActionParameters.SessionId;
var actionTag = launchActionParameters.ActionTag;
var args = launchActionParameters.Args;
UserState userState = null;
RunAction runAction = null;
Angular Client Code
Action Component
export interface ActionView {
actionName: string;
actionType: string;
primaryTable: string;
specialUse: string;
folder: string;
actionDescription: string;
actionTag: number;
chartType: string;
priority: number;
}
const ACTION_DATA: ActionView[] = [];
#Component({
templateUrl: 'home.component.html'
})
export class HomeComponent implements OnInit, OnDestroy {
// User Fields
currentUser: User;
users: User[] = [];
currentUserSubscription: Subscription;
// Action Fields
currentAction: Action;
actions: Action[] = [];
displayedColumns: string[] =
['actionName', 'actionType', 'primaryTable', 'specialUse',
'folder', 'actionDescription', 'actionTag', 'chartType',
'priority'];
dataSource: any = new MatTableDataSource(ACTION_DATA);
constructor(
private authenticationService: AuthenticationService,
private iconRegistry: MatIconRegistry,
private sanitizer: DomSanitizer,
private httpClient: HttpClient,
private actionService: ActionService
) {
this.currentUserSubscription = this.authenticationService.currentUser.subscribe(user => {
this.currentUser = user;
});
this.iconRegistry.addSvgIcon(
'thumbs-up',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/img/examples/thumbup-icon.svg'));
}
#ViewChild(MatSort) sort: MatSort;
public getActions() {
console.log('test');
this.actionService.getActions(
this.currentAction).subscribe((data) => {
this.dataSource = data;
});
}
ngOnInit() {
this.dataSource.sort = this.sort;
this.getActions();
}
ngOnDestroy() {
// unsubscribe to ensure no memory leaks
this.currentUserSubscription.unsubscribe();
}
}
Action Service
#Injectable({ providedIn: 'root' })
export class ActionService {
public apiURL = 'http://localhost:15217/api';
public currentUser: Observable<User>;
public currentAction: Observable<Action>;
constructor(private http: HttpClient) { }
// Http Options
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
getActions(action: Action): Observable<Action[]> {
return this.http.post<Action[]>(this.apiURL + '/actions/launchactions',
{
sessionId: action.sessionId,
tag: action.actionTag,
actionParams: action.actionParams
})
.pipe(
retry(1),
catchError(this.handleError)
);
}
// Error handling
handleError(error: any) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// Get client-side error
errorMessage = error.error.message;
} else {
// Get server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
window.alert(errorMessage);
return throwError(errorMessage);
}
add [FromBody] to controller side service, before the parameter. Post method pass parameters in body.
like
ActionResponse LaunchAction([FromBody]string sessionId, [FromBody]Tag actionTag, [FromBody]ActionParams args, [FromBody]UserState userState);
I don't know why microsoft did not decide to do this default.
You should remove {} from data: {}
By doing this you are assigning an empty object to that
What if you remove the "params" word from your angular http post call ?
So the http post call is this instead
return this.http.post<Action[]>(this.apiURL + '/actions/launchactions',
{
sessionId: action.sessionId,
tag: action.actionTag,
actionParams: action.actionParams
})
.pipe(
retry(1),
catchError(this.handleError)
);

Unable to receive events from server in ServiceStack

i'm having problem using events in my servicestack application.
I'm creating an SOA applicatin based on ServiceStack. I've had no problem creating a simple GET/POST manager within the host.
Now i would like to add events
I'm trying using an example, but the event is not received by the client
Does someone have an idea about that?
This is my server:
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
ServerEventsFeature serverEventsFeature = new ServerEventsFeature()
{
LimitToAuthenticatedUsers = false,
NotifyChannelOfSubscriptions = true,
OnPublish = (res, msg) =>
{
//fired after ever message is published
res.Write("\n\n\n\n\n\n\n\n\n\n");
res.Flush();
},
OnConnect = (eventSubscription, dictionary) =>
{
},
OnSubscribe = (eventSubscription) =>
{
}
};
Plugins.Add(serverEventsFeature);
container.Register<IServerEvents>(c => new MemoryServerEvents());
container.Register(c => new FrontendMessages(c.Resolve<IServerEvents>()));
container.Register<IWebServiceEventManager>(c => new WebServiceEventManager(DeviceManager, macroManager));
SetConfig(new HostConfig
{
DefaultContentType = MimeTypes.Json,
EnableFeatures = Feature.All.Remove(Feature.Html),
});
public class FrontendMessage
{
public string Level { get; set; }
public string Message { get; set; }
}
public class FrontendMessages
{
private readonly IServerEvents _serverEvents;
private Timer _timer;
public FrontendMessages(IServerEvents serverEvents)
{
if (serverEvents == null) throw new ArgumentNullException(nameof(serverEvents));
_serverEvents = serverEvents;
}
public void Start()
{
var ticks = 0;
_timer = new Timer(_ => {
Info($"Tick {ticks++}");
_timer.Change(500, Timeout.Infinite);
}, null, 500, Timeout.Infinite);
}
public void Info(string message, params object[] parameters)
{
var frontendMessage = new FrontendMessage
{
Level = "success",
Message = message
};
Console.WriteLine("Sending message: " + frontendMessage.Message);
_serverEvents.NotifyChannel("messages", frontendMessage);
}
This is my client:
public async void Connect()
{
try
{
Task.Delay(2000).Wait();
clientEvents = new ServerEventsClient("http://127.0.0.1:20001/", "messages");
clientEvents.OnConnect = (msg) =>
{
};
clientEvents.OnHeartbeat = () =>
{
};
clientEvents.OnCommand = (msg) =>
{
};
clientEvents.OnException = (msg) =>
{
};
clientEvents.OnMessage = (msg) =>
{
};
Dictionary<string, ServerEventCallback> handlers = new Dictionary<string, ServerEventCallback>();
handlers.Add("messages", (client, msg) =>
{
});
clientEvents.RegisterHandlers(handlers);
await clientEvents.Connect();
client = (IServiceClient)(clientEvents.ServiceClient);
}
catch (Exception e)
{
}
}
I'd first recommend looking at ServerEvents Examples and the docs for the C# ServerEventsClient for examples of working configurations.
Your extra ServerEventsFeature configuration isn't useful as you're just specifying the defaults and the Publish() new-line hack is not needed when you disable buffering in ASP.NET. So I would change it to:
Plugins.Add(new ServerEventsFeature());
Second issue is that you're use of Message Event handlers is incorrect, your C# ServerEventsClient is already connected to the messages channel. Your handlers is used to listen for messages sent to the cmd.* selector (e.g. cmd.FrontendMessage).
Since you're publishing a DTO to a channel, i.e:
_serverEvents.NotifyChannel("messages", frontendMessage);
You can use a Global Receiver to handle it, e.g:
public class GlobalReceiver : ServerEventReceiver
{
public void Any(FrontendMessage request)
{
...
}
}
client.RegisterReceiver<GlobalReceiver>();
Thanks mythz!
It works correectly.
Next step is to replicate the same behaviour on javascript client (events and get/post request). Do you have something to suggest me?
Thanks a lot!
Leo

How can I get session values in signal r

I want to access session value on my hub class
My jquery code is as below
var con = $.connection.MyHub;
$.connection.hub.start().done(function () {
con.server.send().done(function (data) {
displayservices(data);
})
})
con.client.addmessege = function (data) {
displayservices(data);
//alertSound();
};
My hub class is as bellow
public Class MyHub : Hub
{
public void send(string msg)
{
client.all.addmessage(msg)
}
}
How can I access session value in my hub class
You can get the current session from HttpContext, like this:
var sessionVar = HttpContext.Current.Session["SessionVar"];

SignalR Multiple Hub at Sametime

I defined 2 separate hub in my project, Which is seem they parse into single proxy... and i'm not against it, let it be.
Next i defined my client, the time i had one client i had no issue with it,
but now that i have two hub, and both come to work at a single page, once the first hub start it work well, request goes to server and back.
but once i call the partial page, which connect and talk to second hub, it goes to connection.start, but it wont break on server, mean server is not notified of this action... . now can any one help me?
Controller1:
var app = angular.module("chatApplication", []);
var myHub = $.connection.chatHub;
app.controller("ChatController", [
"$scope", "$timeout", "$templateCache",
function ($scope, $timeout, $templateCache) {
...
$scope.RegisterClientMethod(myHub);
myHub.connection.start().done(function () {
//Already uses OnConnect Override
//TODO: Link Events To Required Buttons And Actions
//like: $(x).click(fnX);
//TODO: Call Started Events
//myHub.server.hello();
}).fail(function (e) {
$scope.connectionMessage = "Connection Failed" + e.toString();
$scope.$apply();
});
}
]);
Controller2:
var myUserHub = $.connection.userHub;
app.controller("UserController", [
"$scope", "$timeout", "$templateCache",
function($scope, $timeout, $templateCache) {
...
$scope.RegisterClientMethod(myUserHub);
$scope.RegisterWatchers();
myUserHub.server.getUsers();
myUserHub.connection.start().done(function () {
//Since Connection is already open, by chatHub, we cannot relay on that
myUserHub.server.getUsers();
}).fail(function (e) {
$scope.connectionMessage = "Connection Failed" + e.toString();
$scope.$apply();
});
}
]);
Hub1:
namespace SignalRChatSystem.Hubs
{
[ChatAuthorize]
public class ChatHub : Hub
{
...
public override Task OnConnected()
{
//client.doSomething
return base.OnConnected();
}
...
}
}
Hub2:
namespace SignalRChatSystem.Hubs
{
[UserAuthorize]
public class UserHub : Hub
{
...
public override Task OnConnected()
{
//client.doSomething
return base.OnConnected();
}
...
}
}
Mapping
[assembly: OwinStartupAttribute(typeof(SignalRChatSystem.Startup))]
namespace SignalRChatSystem
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR();
}
}
}
You should call $.connection.hub.start() only once, even if using multiple hubs. See: http://www.asp.net/signalr/overview/guide-to-the-api/hubs-api-guide-javascript-client#establishconnection
If you are not sure if connection was started earlier on not, you could check before starting it, using the connectionState object.
$.signalR.connectionState
Object {connecting: 0, connected: 1, reconnecting: 2, disconnected: 4}
So your start method could be like this:
if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.disconnected) {
$.connection.hub.start();
//...
}
If your connection is already open, you can call directly what is in the .done() { body.
Maybe you can check before:
if ($.connection.hub && $.connection.hub.state === $.signalR.connectionState.connected) {
// ... logic for second hub here
}

connect to signalr hub

I have created a simple SignalR hub inside a console application:
class Program
{
static void Main(string[] args)
{
using (WebApp.Start<Startup>("http://localhost:1968"))
{
Console.WriteLine("Server running!");
Console.ReadLine();
}
}
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
[HubName("echo")]
public class EchoHub : Hub
{
public void Say(string message)
{
Trace.WriteLine("hub: "+message);
Clients.All.AddMessage(message);
}
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected();
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
When I try to connect this from a Silverlight App, it succeeds:
static Microsoft.AspNet.SignalR.Client.HubConnection signalR { get; set; }
public static Microsoft.AspNet.SignalR.Client.IHubProxy signalRhub { get; set; }
public static void StartSignalR()
{
var url = "http://localhost:1968";
signalR = new Microsoft.AspNet.SignalR.Client.HubConnection(url);
signalR.Received += signalR_Received;
signalRhub = signalR.CreateHubProxy("echo");
signalR.Start().Wait();
signalRhub.Invoke("Say", "hub invoked");
}
My next step is to connect the SignalR hub from jquery:
<script src="../Scripts/jquery-1.6.4.js"></script>
<script src="../Scripts/jquery.signalR-2.1.0.js"></script>
<script>
$(function ()
{
var connection = $.hubConnection("http://localhost:1968");
connection.start()
.done(function () {
console.log('connected');
connection.send("success?");
})
.fail(function (a) {
console.log('not connected'+a);
});
});
</script>
When I try to run this script, it gives the error:
"XMLHttpRequest cannot load localhost:1968/signalr/negotiate?clientProtocol=1.4&_=1404978593482. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin <code>http://localhost:55282</code> is therefore not allowed access."
Why can I connect to the hub from my Silverlight page (hosted in localhost:3926)
and fails it when I run the jquery script (hosted in localhost:55282)?
What can I do to get a working connection between my jQuery and SignalR hub?
Change
$(function ()
{
var connection = $.hubConnection("http://localhost:1968");
connection.start()
.done(function () {
console.log('connected');
connection.send("success?");
})
.fail(function (a) {
console.log('not connected'+a);
});
});
to
$(function ()
{
var connection = $.hubConnection("http://localhost:1968");
var hub = connection.createHubProxy("echo");
hub.on("AddMessage", Method);
connection.start({ jsonp: true })
.done(function () {
console.log('connected');
hub.say("success?");
})
.fail(function (a) {
console.log('not connected'+a);
});
});
function Method(messageFromHub)
{
alert(messageFromHub);
}
and
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
to
class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration() { EnableJSONP = true });}
}
It should do.
app.MapSignalR(new HubConfiguration() { EnableJSONP = true });}
and
connection.start({ jsonp: true })
Will allow cross site request
RPC on Server in SignalR with createHubProxy():
Thanks to the answer from Vishal Ravlani
But for me
hub.say("success?");
doesn't work! (Does it work for you?)
I have to write:
hub.invoke('say','success?');
And SignalR has automatically detected CrossOrigin on Client.
On Server I have used:
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR();
});
which was described on: http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-javascript-client#crossdomain

Categories

Resources