Is it possible to connect to a SignalR server using Unity?
I'm making a Hololens application(so I build to windows store app) that need to communicate with a SignalR hub to post information. This server is already made and I have only the URI, hub name and the hub interface endpoints.
Current issues:
SignalR framework requires .net 4.5 which Unity's current mono version doesn't work with. Even if I manage to get the reference in, Unity won't be able to build it to a windows store app as Unity reverts the solutions version to 3.5 unity subset which doesn't include async functionality that is introduced in .net 4.0.
https://github.com/robink-teleopti/SignalR simply exploded with errors in unity.
https://github.com/jenyayel/SignalR.Client.20 lacks a bunch of definitions in Thread, Stream, LongLength, HTTPWebRequest.
Nivot.SignalR.Client.Net35 uses async functionality that Unity doesn't understand.
You can connect from the UW application to connect to signalR and use a bridge to communicate with the Unity application:
(App/Web-server) bridge:
internal class UnityBridge
{
private static UnityBridge _instance;
private UnityBridge()
{
SelectHandler.SelectedHandler = CallExternal;
}
public static UnityBridge Create()
{
return _instance ?? (_instance = new UnityBridge());
}
private void CallExternal(string nameTag)
{
var conn = new HubConnection("http://xxx.azurewebsites.net");
var proxy = conn.CreateHubProxy("MyHub");
conn.Start().Wait();
proxy.Invoke("Send", new EngineerAction {ExecutedAction = nameTag});
}
}
(App/Web-server) In your App.cs
private AppCallbacks m_AppCallbacks;
private UnityBridge _unityBridge;
public App()
{
m_AppCallbacks = new AppCallbacks();
// Allow clients of this class to append their own callbacks.
AddAppCallbacks(m_AppCallbacks);
}
virtual protected void AddAppCallbacks(AppCallbacks appCallbacks)
{
_unityBridge = UnityBridge.Create();
}
(Unity game-client/server) And in your unity scripts:
public class SelectHandler : MonoBehaviour
{
public delegate void SelectedHandlerDelegate(string nameTag);
public static SelectedHandlerDelegate SelectedHandler;
....
//call
UnityEngine.WSA.Application.InvokeOnUIThread(() => SelectedHandler("teststring"), false);
Related
I am migration an app from .NET Framework to .NET 6. It starts Kestrel web server internally and it is intended to run as a Windows Service - in the old version that was achieved using Topshelf library, so I continued using it. One of the requirements is to listen for session switch events and send messages to third-party application.
In the old version of the application it was achieved again with the help of Topshelf. However, in the callback there was used static DI container to get access to services:
return HostFactory.New((hostConfigurator) =>
{
// ...
hostConfigurator.EnableSessionChanged();
hostConfigurator.Service<WindowService>((serviceConfigurator) =>
{
serviceConfigurator.ConstructUsing(hostSettings => new WindowService());
// ...
serviceConfigurator
.WhenSessionChanged((serviceHelper, arguments) => serviceHelper.SessionChange(arguments));
// ...
});
// ...
});
And this is the callback:
private void SessionChange(SessionChangedArguments changeDescription)
{
var sessionService =
DependencyInjectionContainer.GetRequiredService<IUserSessionMonitoringService>();
if (sessionService == null)
{
LOG.Warning(...);
return;
}
sessionService.SessionChanged(...);
}
So, here's the problem: in the callback a static DependencyInjectionContainer class is used to get the needed service/s. However in the new version of the app this approach won't work as this static class was abandoned. Also, I don't even have access to built-in .NET DI Container, because during the time the Topshelf is configured, the container isn't build.
As further steps I investigated what is Topshelf using under the hood for this functionality - that's SystemEvents.SessinSwitch event. I tried to attach an event handler to it from a hosted service, in which the needed services are injected. For the sake of the test, I just wanted to log some text in a file from the event handler. However, when sign out/in there's nothing in the file from those logs.
public class SystemSessionMonitoringHostedService : IHostedService
{
private readonly IUserSessionMonitoringService userSessionMonitoring;
private readonly ILogger<SystemSessionMonitoringHostedService> logger;
public SystemSessionMonitoringHostedService(
IUserSessionMonitoringService userSessionMonitoring,
ILogger<SystemSessionMonitoringHostedService> logger)
{
this.userSessionMonitoring = userSessionMonitoring;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
SystemEvents.SessionSwitch += OnSessionChanged;
return Task.CompletedTask;
}
private void OnSessionChanged(object sender, SessionSwitchEventArgs eventArgs)
{
var json = JsonConvert.SerializeObject(eventArgs);
logger.LogCritical($"Object: {json}");
}
}
Does anyone know if those SystemEvents works in .NET Core/5+? Am I missing something? Suggestions for workarounds are also welcome.
I would to do a downloader app that save pictures to a folder. The app should work on windows and macos, and may be later on android and ios.
I haven't found a way to pick the target folder. Any idea on how it can be achieve either with blazor or xaml .NET MAUI app?
I've made a start implementing this for Windows and macOS. You can review the code here: https://github.com/jfversluis/MauiFolderPickerSample and wrote a little blog post about this as well here: https://blog.verslu.is/maui/folder-picker-with-dotnet-maui/
This follows kind of the basic pattern you'd want to use if you want to access platform-specific APIs:
Define an interface
Implement interface on each supported platform
Consume functionality
For this I have created a very simple but effective interface
public interface IFolderPicker
{
Task<string> PickFolder();
}
Then we create an implementation for Windows, by adding a new file FilePicker.cs to the Platforms\Windows\ folder. This makes it specific to Windows and allows us to write Windows specific code. The file contains this code:
using WindowsFolderPicker = Windows.Storage.Pickers.FolderPicker;
namespace MauiFolderPickerSample.Platforms.Windows
{
public class FolderPicker : IFolderPicker
{
public async Task<string> PickFolder()
{
var folderPicker = new WindowsFolderPicker();
// Make it work for Windows 10
folderPicker.FileTypeFilter.Add("*");
// Get the current window's HWND by passing in the Window object
var hwnd = ((MauiWinUIWindow)App.Current.Windows[0].Handler.PlatformView).WindowHandle;
// Associate the HWND with the file picker
WinRT.Interop.InitializeWithWindow.Initialize(folderPicker, hwnd);
var result = await folderPicker.PickSingleFolderAsync();
return result.Path;
}
}
}
Because I chose FolderPicker as the name for my own object here, there is a naming conflict with the Windows FolderPicker that is why there is that weird using at the top. If you go for MyFolderPicker as your object name that wouldn't be needed.
Now we register this interface and implementation with the generic host builder in our MauiProgram.cs:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Note: this part was added
#if WINDOWS
builder.Services.AddTransient<IFolderPicker, Platforms.Windows.FolderPicker>();
#elif MACCATALYST
builder.Services.AddTransient<IFolderPicker, Platforms.MacCatalyst.FolderPicker>();
#endif
builder.Services.AddTransient<MainPage>();
builder.Services.AddTransient<App>();
// Note: end added part
return builder.Build();
}
}
Note that I also added MainPage and App here so that our constructor injection works (have a look at MainPage.xaml.cs in the linked repository).
Now we can consume our functionality as follows:
namespace MauiFolderPickerSample;
public partial class MainPage : ContentPage
{
private readonly IFolderPicker _folderPicker;
public MainPage(IFolderPicker folderPicker)
{
InitializeComponent();
_folderPicker = folderPicker;
}
private async void OnPickFolderClicked(object sender, EventArgs e)
{
var pickedFolder = await _folderPicker.PickFolder();
FolderLabel.Text = pickedFolder;
SemanticScreenReader.Announce(FolderLabel.Text);
}
}
Implementing other platforms would require you to implement the interface for the platform you want to support and register it in the generic host builder. This should get you started for Windows and macOS.
Actually calling this should not be any different between .NET MAUI (regular) or .NET MAUI Blazor.
Here I am creating a multiplayer game with Unity Networking. What I want is to start the game when both Server and client is connected. In my game I have 2 players one will act as a server who hosts the game and the other acts as a client who connects to that host with the servers IPAddress. So when both are connected and ready to play i want to start the game. Can anyone help me how can i get to know that both are connected.
Here is the code what I am using.
public NetworkManager manager;
public void startServerr()
{
NetworkServer.Listen(9000);
NetworkServer.RegisterHandler(MsgType.Connect, OnConnected);
NetworkServer.RegisterHandler(MsgType.Disconnect, OnDisconnected);
NetworkServer.RegisterHandler(MsgType.Error, OnError);
manager.StartHost();
}
public void connectToServer()
{
manager.networkAddress = PlayerPrefs.GetString("oppPlayerIP");
manager.StartClient();
}
public void OnConnected(NetworkMessage netMsg)
{
Debug.Log("Client Connected");
}
public void OnDisconnected(NetworkMessage netMsg)
{
Debug.Log("Disconnected");
}
public void OnError(NetworkMessage netMsg)
{
Debug.Log("Error while connecting");
}
You are currently using NetworkManager... If you prefer to use NetworkManager over NetworkServer and NetworkClient that requires manual registering of the events, then your script must inherit from NetworkManager.
Once you inherit from NetworkManager, you can then override and use OnClientConnect and OnServerConnect functions to check when client is connect to server and when server is connected to client respectively.
Here is an example:
public class NetTest : NetworkManager
{
private NetworkManager manager;
public void startServerr()
{
manager = this;
manager.StartHost();
}
public void connectToServer()
{
//manager.networkAddress = PlayerPrefs.GetString("oppPlayerIP");
manager.StartClient();
}
public override void OnClientConnect(NetworkConnection conn)
{
//base.OnClientConnect(conn);
}
public override void OnClientDisconnect(NetworkConnection conn)
{
//base.OnClientDisconnect(conn);
}
public override void OnServerConnect(NetworkConnection conn)
{
//base.OnServerConnect(conn);
}
public override void OnServerDisconnect(NetworkConnection conn)
{
//base.OnServerDisconnect(conn);
}
}
You can find more network callback functions in the public function section here.
First of I recommend you reading all the documentation about unity networking before actually trying to make a connection. After you've done that you should know how to handle this request. If you don't know by then here is how to handle this specific request.
First you need to let the server handle all incoming connections by using the method OnConnected(NetworkMessage netMsg).
You should be able to recognize the individual players connected to the server.
Now you only need to create a ready button and send the ready status towards the server when clicked. If every player connected has send his isReady message towards the server you can start the game. this is done by sending a message from the server towards all the connected clients.
Hope this helps, any questions just ask.
I have a Topshelf windows service that acts as a TCP server. Inside this service, I also have a self-hosted (OWIN) WebAPI.
My goal is to somehow allow the WebAPI to communicate with the TCP server that's contained and running in the same service. Naturally I could simply use something like a "trigger" file or a shared DB that could be polled frequently, though I'd like to know of any more optimal/native ways to achieve this.
To get a better idea of the project, think of a single page application consuming my API and making certain calls with arbitrary string parameters. This data should then be passed to clients (C++ console apps using winsock) that are connected to the running TCP server.
The following Container is instantiated and passed to the Topshelf HostConfigurator
class ContainerService
{
private APIService _apiService;
private EngineService _engineService;
protected IDisposable WebAppHolder { get; set; }
public bool Start(HostControl hostControl)
{
var host = hostControl;
_apiService = new APIService();
_engineService = new EngineService();
// Initialize API service
if (WebAppHolder == null)
{
WebAppHolder = _apiService.Initialize();
}
// Initialize Engine service
_engineService.Initialize();
return true;
}
public bool Stop(HostControl hostControl)
{
// Stop API service
if (WebAppHolder != null)
{
WebAppHolder.Dispose();
WebAppHolder = null;
}
// Stop Engine service
_engineService.Stop();
return true;
}
}
Standard Topshelf stuff in program entry point (as mentioned above):
HostFactory.Run(hostConfigurator =>
{
hostConfigurator.Service<ContainerService>(containerService =>
{
containerService.WhenStarted((service, control) => service.Start(control));
containerService.WhenStopped((service, control) => service.Stop(control));
});
hostConfigurator.RunAsLocalSystem();
hostConfigurator.SetServiceName("Educe Service Host");
hostConfigurator.SetDisplayName("Communication Service");
hostConfigurator.SetDescription("Responsible for API and Engine services");
});
TCP Server:
public void Initialize()
{
_serverListener = new TcpListener(new IPEndPoint(hostAddress, (int)port));
_serverListener.Start();
_threadDoBeginAcceptTcpClient = new Thread(() => DoBeginAcceptTcpClient(_serverListener));
_threadDoBeginAcceptTcpClient.Start();
}
...
public void DoBeginAcceptTcpClient(TcpListener listener)
{
while(!_breakThread)
{
// Set the event to nonsignaled state.
TcpClientConnected.Reset();
// Start to listen for connections from a client.
Console.WriteLine("Waiting for a connection...");
// Accept the connection.
listener.BeginAcceptTcpClient(DoAcceptTcpClientCallback, listener);
// Wait until a connection is made and processed before continuing.
TcpClientConnected.WaitOne();
}
}
// Process the client connection.
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
// End the operation and display the received data on the console.
Console.WriteLine("Client connection completed");
Clients.Add(listener.EndAcceptTcpClient(ar));
// Signal the calling thread to continue.
TcpClientConnected.Set();
}
WebAPI Controller:
public class ValuesController : ApiController
{
// GET api/values/5
public string Get(int id)
{
return $"Foo: {id}";
}
}
As mentioned earlier, what I seek is "communication" between the WebAPI and the windows service. How can I pass the "id" parameter from the WebAPI call to the _engineService object in my windows service? Perhaps something similar to WPF's MVVM Light Messenger? The idea is that it would then be parsed and sent to the appropriate TcpClient that is stored in the Clients List.
Any advice on how to achieve this will be appreciated. Please feel free to ask for clarification/more code.
Did you find any answer to your issue yet ?
I don't quite understand what you try to achieve looking for a communication between the two of them ? Do you want to somehow rely on TCP/IP to relay this id or in-memory ?
Potentially, you could consider a Mediator pattern and use this kind of library that seems quite useful in the case I understood : https://github.com/jbogard/MediatR
In a simpler approach, I would rely on events to achieve what you are trying to do, which is having a reactive communication from the HTTP request to the c++ users.
Did I understand you needs ? I am quite curious about the solution
I'm assuming you are trying to take an HTTP GET request's ID parameter and send it to TCP clients who are connected to the EngineService. If your EngineService is initialized before your ApiService, I think this is a question of how to get a handle to the one-and-only EngineService instance from within an ApiService's controller instances.
If I'm following you, you could make the EngineService a public static property of your ContainerService and reference it as ContainerService.EngineService from the controller (or anywhere in the app for that matter) or better register your EngineService as a singleton in a DI container an inject it into the ApiService.
Solution (calls to WebAPI trigger EngineService)
I now use RabbitMQ/EasyNetQ to achieve communication between the WebApi and the EngineService object containing my TCP clients.
I have incidentally split them into two separate Projects/Topshelf services now.
The following is the new "communication" component and it is instantiated in the EngineService constructor.
public class Communication
{
private readonly Logger _logger;
private readonly IBus _bus;
public delegate void ReceivedEventHandler(string data);
public event ReceivedEventHandler Received;
protected virtual void OnReceive(string data)
{
Received?.Invoke(data);
}
public Communication()
{
_logger = new Logger();
_bus = RabbitHutch.CreateBus("host=localhost", reg => reg.Register<IEasyNetQLogger>(log => _logger));
SubscribeAllQueues();
}
private void SubscribeAllQueues()
{
_bus.Receive<Message>("pipeline", message =>
{
OnReceive(message.Body);
});
}
public void SubscribeQueue(string queueName)
{
_bus.Receive<Message>(queueName, message =>
{
OnReceive(message.Body);
});
}
}
An event handler is then added.
This means that as soon as a message arrives to the bus, the data will be relayed to the event handler which will subsequently relay it to the first connected TCP client in the list.
public void Handler(string data)
{
//Console.WriteLine(data);
_clients[0].Client.Send(Encoding.UTF8.GetBytes(data));
}
...
_comPipe.Received += Handler;
And finally on the WebApi's controller:
public string Get(int id)
{
ServiceCom.SendMessage("ID: " + id);
return "value";
}
ServiceCom class. Allows sending a string message on the bus.
public static class ServiceCom
{
public static void SendMessage(string messageBody)
{
var messageBus = RabbitHutch.CreateBus("host=localhost");
messageBus.Send("pipeline", new Message { Body = messageBody });
}
}
Now that this is done, I am now looking to implement a way for the connected TCP clients to trigger updates/events in an additional SPA project that will act as a Portal / Client Management App.
My approach will probably make use of KnockOut.js and SignalR to achieve dynamic Views where TCP client events are displayed immediately and similarly actions on to WebAPI will trigger events in the TCP clients. I know it sounds like a bizarre combination of processes but it is all according to plan and working out as expected :)
I am trying to add SignalR in my MVC project. I need to call a SignalR client method from my class library. I did below code
public class CommomHubManager : ICommomHubManager
{
readonly IHubContext Context;
public CommomHubManager()
{
Context = Helpers.Providers.HubContextProvider<Hubs.Notifications>.HubContext;
}
public Task AddUserToGroup(string groupName)
{
return Task.Factory.StartNew(() => {
Context.Clients.All.addUserToGroup(groupName);
});
}
}
Its not working, but when i try to call an another Hub class method from WebApi its working just fine. I want to know that is it possible to call SignalR Client method from normal C# class?
How to use SignalR hub instance outside of the hubpipleline
var context = GlobalHost.ConnectionManager.GetHubContext<CommomHubManager>();
context.Clients.All.Send("Admin", "stop the chat");
You can find out more in the SignalR documentation.