Disconnect client from IHubContext<THub> - asp.net-core-signalr

I can call InvokeAsync from server code using the IHubContext interface, but sometimes I want to force these clients to disconnect.
So, is there any way to disconnect clients from server code that references the IHubContext interface?

Step 1:
using Microsoft.AspNetCore.Connections.Features;
using System.Collections.Generic;
using Microsoft.AspNetCore.SignalR;
public class ErrorService
{
readonly HashSet<string> PendingConnections = new HashSet<string>();
readonly object PendingConnectionsLock = new object();
public void KickClient(string ConnectionId)
{
//TODO: log
if (!PendingConnections.Contains(ConnectionId))
{
lock (PendingConnectionsLock)
{
PendingConnections.Add(ConnectionId);
}
}
}
public void InitConnectionMonitoring(HubCallerContext Context)
{
var feature = Context.Features.Get<IConnectionHeartbeatFeature>();
feature.OnHeartbeat(state =>
{
if (PendingConnections.Contains(Context.ConnectionId))
{
Context.Abort();
lock (PendingConnectionsLock)
{
PendingConnections.Remove(Context.ConnectionId);
}
}
}, Context.ConnectionId);
}
}
Step 2:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<ErrorService>();
...
}
Step 3:
[Authorize(Policy = "Client")]
public class ClientHub : Hub
{
ErrorService errorService;
public ClientHub(ErrorService errorService)
{
this.errorService = errorService;
}
public async override Task OnConnectedAsync()
{
errorService.InitConnectionMonitoring(Context);
await base.OnConnectedAsync();
}
....
Disconnecting without Abort() method:
public class TestService
{
public TestService(..., ErrorService errorService)
{
string ConnectionId = ...;
errorService.KickClient(ConnectionId);

In alpha 2 there is the Abort() on HubConnectionContext you could use to terminate a connection. I don't see, however, an easy way to access it from outside the hub.
Because you control the clients you could just invoke a client method and tell the client to disconnect. The advantage is that the client disconnect gracefully. The disadvantage is that it requires sending the message to the client instead of disconnecting the client solely on the server side.

Related

.net core: run big tasks in the background

I created a .net core web api project. It has gotten kinda big and I want to program a "delete" operation which deletes a lot of stuff from the database. Since there are a lot of things to delete, this will be a long running process. So I thought maybe I can run this in the background and just write status updates somewhere for the user to see whats happening.
I googled this and I found BackgroundWorkerQueue and thought this might be my solution.
So I registered the service and everything and here is my method that calls it:
public class DeleteController : ControllerBase {
private readonly BackgroundWorkerQueue _backgroundWorkerQueue;
public AdminController(BackgroundWorkerQueue backgroundWorkerQueue){
_backgroundWorkerQueue = backgroundWorkerQueue;
}
public async Task<ActionResult> HugeDeleteMethod(int id)
{
// some prechecks here...
// and here I thought I'd start the background task
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>
{
var a = _context.StatusTable.Find(id);
a.Status += "Blablablabla\n";
_context.StatusTable.Update(a);
await _context.SaveChangesAsync();
//now start doing delete operations
});
}
}
And that class looks like this:
public class BackgroundWorkerQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
There is also a DeleteService, which is also called in my startup, but I am not sure what it does:
public class DeleteService : BackgroundService
{
private readonly BackgroundWorkerQueue queue;
public NukeService(BackgroundWorkerQueue queue)
{
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Both are added in my startup.cs:
services.AddHostedService<DeleteService>();
services.AddSingleton<BackgroundWorkerQueue>();
Well, maybe I'm going about this all wrong. This is never called it seems, the StatusTable field "Status" is always empty. So how do I do this?
You just need to subclass BackgroundService class or implement IHostedService and than register your service as hosted service.
This will run a service in the background. Than in your service you can leverage the BlockingQueue that will perform tasks only when they are added, e.g. like this:
public class MyService : BackgroundService {
private readonly BlockingCollection<long> queue;
public MyService(){
this.queue = new BlockingCollection<long>();
Task.Run(async () => await this.Execute());
}
public void AddId(long id) {
this.queue.Add(id);
}
private async Task Execute()
{
foreach (var id in this.queue.GetConsumingEnumerable())
{
... do your stuff ...
}
}
}
services.AddHostedService<MyService>();
Here is the docu: Background services in .net core

Xamarin - Socket IO issue

I have to make a chat for a Xamarin Forms (PCL) application. I'm using the NuGet package SocketIoClientDotNet for socket.
At first I could not connect at all. After many researches on internet I found this open issue on Github, so I downgraded the library but also all the dependencies:
EngineIOClient.Net V0.9.22
SocketIOClientDotNet V0.9.13
WebSocket4Net V0.14.1.0
It was better, the connection seemed to work but I encountered a new issue: the connection is very instable and it's difficult for me to test anything cause of that. One time it can connect multiple times, one time it not connect at all, it's very annoying...
My code is very simple:
Common Code:
ISocketIO interface:
public interface ISocketIO
{
void Connect(string url);
void On(string eventString, Action<object> action);
}
MsgService class:
readonly string EVENT_CONNECT = "connect";
public MsgService(ISocketIO socket)
{
Socket = socket;
if (Socket != null)
{
Socket.On(EVENT_CONNECT, () =>
{
(code here...)
});
}
}
public void Connect()
{
if (Socket != null)
{
Socket.Connect("chat_url_here");
}
}
App class:
public partial class App : Application
{
public static MsgService MsgService;
public App(ISocketIO socket)
{
InitializeComponent();
Language = Language.FRENCH;
MsgService = new MsgService(socket);
MsgService.Connect();
MainPage = new NavigationPage(new MainPage());
}
...
}
iOS code (same for Android):
Class SocketIO
[assembly: Xamarin.Forms.Dependency(typeof(SocketIO))]
namespace MeetYou.iOS
{
public class SocketIO : ISocketIO
{
Socket _socket;
public void Connect(string url)
{
IO.Options opt = new IO.Options
{
Path = "path_here"
};
_socket = IO.Socket(url, opt);
_socket.Connect();
}
}
}
AppDelegate:
[Register("AppDelegate")]
public class AppDelegate : Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
{
Xamarin.Forms.Forms.Init();
LoadApplication(new App(new SocketIO()));
return base.FinishedLaunching(uiApplication, launchOptions);
}
}
Maybe I'm doing something wrong of maybe it exists an other plugin I could use instead this one.
Have you tried running this without this line?
_socket.Connect();
I managed to make my example work in my application only using the
_socket = IO.Socket("wss://" + HostUrl + "/");

How to connect client and server with socket?

How do I actually run this client/server project on ActionScript 3? I'm using flashbuilder. I'm not sure if I'm even doing the right thing, i made a new as mobile project and put in the code for my client which is this:
public class client extends Sprite
{
private var socket:Socket;
private var IP:String = "127.0.0.1";
public function client()
{
super();
socket = new Socket();
socket.addEventListener(Event.CONNECT, onConnected);
socket.connect(IP, 5555);
}
protected function onConnected(event:Event):void
{
socket.writeUTFBytes("communication between Sockets (Client socket and server socket)");
socket.flush();
}
}
and then I made a new class within the package that has the client class (as above) in it and put in this as the server class:
public class NewSocketServerTest extends Sprite
{
private var serverSocket:ServerSocket = new ServerSocket();
private var clientSocket:Socket;
private var txt:TextField;
public function NewSocketServerTest()
{
createUI();
serverSocket.bind(5555);
serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, onConnected);
serverSocket.listen();
}
protected function onConnected(event:ServerSocketConnectEvent):void
{
txt.appendText("This is a demonstration of \n" );
clientSocket = event.socket;
clientSocket.addEventListener(ProgressEvent.SOCKET_DATA, onDataHandler);
}
protected function onDataHandler(event:ProgressEvent):void
{
var str:String = clientSocket.readUTFBytes(clientSocket.bytesAvailable);
txt.appendText(str);
}
private function createUI():void
{
txt = new TextField();
txt.width=400;
txt.height=400;
txt.appendText("Hello! \n");
addChild(txt);
}
}
When I try run this in an emulator it's just a white screen and I'm not sure what I'm doing wrong.
Try specifying the serverSocket hostname.
serverSocket.bind(5555,"127.0.0.1");

Best way to handle incoming messages with XMPP

Is there a work-around to get Spring to handle incoming messages from XMPP? I have tried many different configurations to get an inbound-channel-adapter to respond to incoming XMPP messages and nothing happens. I know that they show up at the Spring Integration layer (I can see that in the logs) but they are ignored. Is there any way to get them into my application layer? I hope to avoid needing to make changes to Spring Integration itself if I can.
Here is my integration configuration:
<int-xmpp:inbound-channel-adapter id="gcmIn"
channel="gcmInChannel"
xmpp-connection="gcmConnection"
auto-startup="true"
/>
<bean id="inboundBean" class="example.integration.GcmInputHandler"/>
<int:service-activator input-channel="gcmInChannel" output-channel="nullChannel" ref="inboundBean" method="handle"/>
Using the outbound-channel-adapter works fine. I can send messages over GCM 100% easily. But inbound does nothing, even though I know the messages are coming in.
Thanks
Not a very clean one, you would need to overwrite the ChatMessageListeningEndpoint, which drops all empty body messages.
This one needs then to be used as inbound-channel adapter in your config.
In addition you need to register the GCM package extension on the Smack Provider Manager, otherwise you lose the JSON message.
Working on a sample project -- so if you need more help let me know and I will post a link as soon it works somehow in a understandable way.
Here a sample GCM Input Adapter
public class GcmMessageListeningEndpoint extends ChatMessageListeningEndpoint {
private static final Logger LOG = LoggerFactory.getLogger(GcmMessageListeningEndpoint.class);
#Setter
protected PacketListener packetListener = new GcmPacketListener();
protected XmppHeaderMapper headerMapper = new DefaultXmppHeaderMapper();
public GcmMessageListeningEndpoint(XMPPConnection connection) {
super(connection);
ProviderManager.addExtensionProvider(GcmPacketExtension.GCM_ELEMENT_NAME, GcmPacketExtension.GCM_NAMESPACE,
new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
String json = parser.nextText();
return new GcmPacketExtension(json);
}
});
}
#Override
public void setHeaderMapper(XmppHeaderMapper headerMapper) {
super.setHeaderMapper(headerMapper);
this.headerMapper = headerMapper;
if (this.headerMapper == null) throw new IllegalArgumentException("Null XmppHeaderMapper isn't supported!");
}
public String getComponentType() {
return "xmpp:inbound-channel-adapter-gcm";
}
#Override
protected void doStart() {
Assert.isTrue(this.initialized, this.getComponentName() + " [" + this.getComponentType() + "] must be initialized");
this.xmppConnection.addPacketListener(this.packetListener, null);
}
#Override
protected void doStop() {
if (this.xmppConnection != null) {
this.xmppConnection.removePacketListener(this.packetListener);
}
}
class GcmPacketListener implements PacketListener {
#Override
public void processPacket(Packet packet) throws NotConnectedException {
if (packet instanceof org.jivesoftware.smack.packet.Message) {
org.jivesoftware.smack.packet.Message xmppMessage = (org.jivesoftware.smack.packet.Message) packet;
Map<String, ?> mappedHeaders = headerMapper.toHeadersFromRequest(xmppMessage);
sendMessage(MessageBuilder.withPayload(xmppMessage).copyHeaders(mappedHeaders).build());
} else {
LOG.warn("Unsuported Packet {}", packet);
}
}
}
}
And here the new configuration for the inbound-channel-adapter remove the one in XML:
#Bean
public GcmMessageListeningEndpoint inboundAdpater(XMPPConnection connection, MessageChannel gcmInChannel) {
GcmMessageListeningEndpoint endpoint = new GcmMessageListeningEndpoint(connection);
endpoint.setOutputChannel(gcmInChannel);
return endpoint;
}

ServiceBase.RequestAdditionalTime() from NServiceBus.Host

We have an NServiceBus Windows service that takes a while to register modules when starting up. We would like to request additional time from the Service Manager to start up properly. For services not using NServiceBus, this would be done using ServiceBase.RequestAdditionalTime. How would this be done using NServiceBus.Host?
We ended up removing NServiceBus.Host and using Topshelf instead. Note additional config required on service startup.
public static void Main(string[] args)
{
HostFactory.Run(hf =>
{
hf.Service<MailboxListenerService>(svc =>
{
svc.ConstructUsing(mls => new MailboxListenerService());
svc.WhenStarted((mls, control) => mls.Start(control));
svc.WhenStopped(mls => mls.Stop());
});
});
}
public class MailboxListenerService
{
private RunWebApi _webApiRunner;
public MailboxListenerService()
{
}
public bool Start(HostControl hostControl)
{
hostControl.RequestAdditionalTime(TimeSpan.FromSeconds(60));
var kernel = new StandardKernel(new MailboxListenerModule());
Configure.With()
.DefineEndpointName("my.endpoint.name")
.DefiningEventsAs(t => typeof(Messaging.Markers.IEvent).IsAssignableFrom(t))
.Log4Net<NlogAppenderForLog4Net>(a => { })
.NinjectBuilder(kernel)
.MsmqTransport()
.MsmqSubscriptionStorage("my.subscription.storage")
.DisableTimeoutManager()
.DisableSecondLevelRetries()
.UnicastBus()
.ImpersonateSender(false);
_webApiRunner = kernel.Get<RunWebApi>();
_webApiRunner.Run();
return true;
}
public void Stop()
{
_webApiRunner.Stop();
}
}
Unfortunately no, we have opened up an issue to support this in future versions
https://github.com/NServiceBus/NServiceBus/issues/1046
On workaround would be to increase the timeout for you service using the registry:
http://support.microsoft.com/kb/824344