Intro
This time, I will try communicating with SignalR from Unity applications.
For server side, I will use the application what I created last time.
Environments
- Unity ver.2020.2.1f1
- Microsoft.AspNetCore.SignalR.Client ver.5.0.4
- nuget.exe ver.5.8.1
AspNetCore.SignalR.Client
I can connect SignalR hub by AspNetCore.SignalR.Client.
I used JavaScript(TypeScript) version last time.
This time, I use C# version.
Though this library is named "AspNetCore", it complies .NET Standard.
So I can use it from WPF, Unity, and so on like the article above.
Use AspNetCore.SignalR.Client from Unity
How to install?
One important problem is I can't use NuGet for Unity applications.
So I must put dlls into the Assets/Plugins folder manually.
First, I tried installing AspNetCore.SignalR.Client in a .NET 5 console application, and copying dlls.
But the dlls were complied .NET Standard 2.1.
Because Unity applications only could use .NET Standard 2.0.
So I used NuGet of commandline version.
I just installed nuget.exe and executed "./nuget install Microsoft.AspNetCore.SignalR.Client".
Copy dlls
After installing, I could get packages.
And there were some dlls for several platforms.
So I copied netstandard2.0 one.
First, I copied "Microsoft.AspNetCore.SignalR.Client.Core.dll" and got errors.
It was because lacking of dependencies. So I copied them.
When I couldn't get .NET Standard2.0 packages, I copied older one.
Copied dlls
- Microsoft.AspNetCore.Http.Connections.Client.dll
- Microsoft.AspNetCore.Http.Connections.Common.dll
- Microsoft.AspNetCore.Http.Features.dll
- Microsoft.AspNetCore.SignalR.Client.Core.dll
- Microsoft.AspNetCore.SignalR.Client.dll
- Microsoft.AspNetCore.SignalR.Common.dll
- Microsoft.AspNetCore.SignalR.Protocols.Json.dll
- Microsoft.Bcl.AsyncInterfaces.dll
- Microsoft.Extensions.DependencyInjection.Abstractions.dll
- Microsoft.Extensions.DependencyInjection.dll
- Microsoft.Extensions.Logging.Abstractions.dll
- Microsoft.Extensions.Logging.dll
- Microsoft.Extensions.Options.dll
- Microsoft.Extensions.Primitives.dll
- System.Buffers.dll
- System.Diagnostics.DiagnosticSource.dll
- System.IO.Pipelines.dll
- System.Memory.dll
- System.Runtime.CompilerServices.Unsafe.dll
- System.Text.Encodings.Web.dll
- System.Text.Json.dll
- System.Threading.Channels.dll
System.Threading.Tasks.Extensions.dll
Communicate with SignalR
MainPage.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using Microsoft.AspNetCore.SignalR.Client;
public class MainPage : MonoBehaviour
{
public Text ReceivedText;
public InputField MessageInput;
public Button SendButton;
private SignalRConnector connector;
public async Task Start()
{
connector = new SignalRConnector();
connector.OnMessageReceived += UpdateReceivedMessages;
await connector.InitAsync();
SendButton.onClick.AddListener(SendMessage);
}
private void UpdateReceivedMessages(Message newMessage)
{
var lastMessages = this.ReceivedText.text;
if(string.IsNullOrEmpty(lastMessages) == false)
{
lastMessages += "\n";
}
lastMessages += $"User:{newMessage.UserName} Message:{newMessage.Text}";
this.ReceivedText.text = lastMessages;
}
private async void SendMessage()
{
await connector.SendMessageAsync(new Message
{
UserName = "Example",
Text = MessageInput.text,
});
}
}
Message.cs
public class Message
{
public string UserName { get; set; }
public string Text { get; set; }
}
SignalRConnector.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using Microsoft.AspNetCore.SignalR.Client;
public class SignalRConnector
{
public Action<Message> OnMessageReceived;
private HubConnection connection;
public async Task InitAsync()
{
connection = new HubConnectionBuilder()
.WithUrl("http://localhost:5000/chatHub")
.Build();
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
OnMessageReceived?.Invoke(new Message
{
UserName = user,
Text = message,
});
});
await StartConnectionAsync();
}
public async Task SendMessageAsync(Message message)
{
try
{
await connection.InvokeAsync("SendMessage",
message.UserName, message.Text);
}
catch (Exception ex)
{
UnityEngine.Debug.LogError($"Error {ex.Message}");
}
}
private async Task StartConnectionAsync()
{
try
{
await connection.StartAsync();
}
catch (Exception ex)
{
UnityEngine.Debug.LogError($"Error {ex.Message}");
}
}
}
--- 2021-04-01 Update ---
IL2CPP
When I set "Scripting Backent" to "IL2CPP", I will get error.
Because some classes are removed.
For avoiding this problem, I add "link.xml" into the Assets folder.
link.xml
<linker>
<assembly fullname="Microsoft.AspNetCore.Connections.Abstractions" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.Http.Connections.Client" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.Http.Connections.Common" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.Http.Features" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.SignalR.Client.Core" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.SignalR.Client" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.SignalR.Common" preserve="all"/>
<assembly fullname="Microsoft.AspNetCore.SignalR.Protocols.Json" preserve="all"/>
<assembly fullname="Microsoft.Bcl.AsyncInterfaces" preserve="all"/>
<assembly fullname="Microsoft.Extensions.DependencyInjection.Abstractions" preserve="all"/>
<assembly fullname="Microsoft.Extensions.DependencyInjection" preserve="all"/>
<assembly fullname="Microsoft.Extensions.Logging.Abstractions" preserve="all"/>
<assembly fullname="Microsoft.Extensions.Logging" preserve="all"/>
<assembly fullname="Microsoft.Extensions.Options" preserve="all"/>
<assembly fullname="Microsoft.Extensions.Primitives" preserve="all"/>
<assembly fullname="System.Buffers" preserve="all"/>
<assembly fullname="System.Diagnostics.DiagnosticSource" preserve="all"/>
<assembly fullname="System.IO.Pipelines" preserve="all"/>
<assembly fullname="System.Memory" preserve="all"/>
<assembly fullname="System.Runtime.CompilerServices.Unsafe" preserve="all"/>
<assembly fullname="System.Text.Encodings.Web" preserve="all"/>
<assembly fullname="System.Text.Json" preserve="all"/>
<assembly fullname="System.Threading.Channels" preserve="all"/>
<assembly fullname="System.Threading.Tasks.Extensions" preserve="all"/>
</linker>
Top comments (10)
Thanks for great tutorial!
I get the error in picture..
dev-to-uploads.s3.amazonaws.com/up...
I have to use the newtonsoft.Json 8.0.0.0 because I use it for IBM plugin...
What can I do?
Thanks!
Thank you for reading my post.
I think it's hard to resolve :(.
Because "Microsoft.AspNetCore.SignalR.Client" requires .NET Standard 2.0 compatibility from ver.1.0.0.
nuget.org/packages/Microsoft.AspNe...
But Newtonsoft.Json has supported .NET Standard 2.0 from ver.11.
github.com/JamesNK/Newtonsoft.Json...
So I think it's better to find out how to force to use Newtonsoft.Json version 11 or later.
Hey I think I resolved the problem!
At the begining I was following this tutorial:
youtube.com/watch?v=ntqxSSWG66g
And he uses Newtonsoft... then I pay attenion to your post and you didnt use it.
So I was following your tutorial post and it works good I dont need to change my newtonsoft to any versions
Thank you!
Hi!
In Mac OS it should work? Because when I run it on MAC I cannot connect to the Server by:
connection = new HubConnectionBuilder()
.WithUrl("localhost:5000/chatHub")
.Build();
The HubConnection.ConnectionID is empty. In others platforms it works but only in MacOS it doesn't work... Do you have any idea?
I have problem when build for android.
Environments
Error 1:
1 source)C:\Program Files\Unity\Hub\Editor\2021.3.11f1\Editor\Data\il2cpp\build\deploy\il2cpp.exe --convert-to-cpp --data-folder=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Library/Bee/artifacts/Android/il2cppOutput/data --generatedcppdir=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Library/Bee/artifacts/Android/il2cppOutput/cpp --enable-analytics --emit-null-checks --enable-array-bounds-check --dotnetprofile=unityaot-linux --profiler-report --profiler-output-file=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Library/Bee/artifacts/il2cpp_conv_oudx.traceevents --print-command-line
Error: IL2CPP error (no further information about what managed code was being converted is available)
System.InvalidOperationException: Sequence contains no elements
at System.Linq.ThrowHelper.ThrowNoElementsException()
at System.Linq.Enumerable.First[TSource](IEnumerable
at Unity.IL2CPP.DataModel.BuildLogic.DataModelBuilder.Initialize(UnderConstruction
2& systemAssembly)
at Unity.IL2CPP.DataModel.BuildLogic.DataModelBuilder.Build()
at Unity.IL2CPP.Contexts.Components.DataModelComponent.Load(LoadSettings loadSettings, Boolean ownsTypeContext, Boolean ownsBuilder, DataModelBuilder& builder)
at Unity.IL2CPP.AssemblyConversion.Phases.InitializePhase.Run(AssemblyConversionContext context)
at Unity.IL2CPP.AssemblyConversion.Classic.ClassicConverter.Run(AssemblyConversionContext context)
at Unity.IL2CPP.AssemblyConversion.AssemblyConverter.ConvertAssemblies(AssemblyConversionInputData data, AssemblyConversionParameters parameters, AssemblyConversionInputDataForTopLevelAccess dataForTopLevel)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)
Error 2:
Building D:\Nimets\Projects\KhmerChess\KhmerChessClient\Library\Bee\artifacts\unitylinker_xy1a.traceevents failed with output:
C:\Program Files\Unity\Hub\Editor\2021.3.11f1\Editor\Data\il2cpp\build\deploy\UnityLinker.exe --search-directory=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed --out=Library/Bee/artifacts/Android/ManagedStripped --include-link-xml=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed\MethodsToPreserve.xml --include-link-xml=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed\TypesInScenes.xml --include-link-xml=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed\SerializedTypes.xml --include-link-xml=D:\Nimets\Projects\KhmerChess\KhmerChessClient\Temp\burst.link.xml --include-link-xml=D:\Nimets\Projects\KhmerChess\KhmerChessClient\Assets\link.xml --include-link-xml=D:\Nimets\Projects\KhmerChess\KhmerChessClient\Assets\Packages\FacebookSDK\link.xml --include-link-xml=C:/Program Files/Unity/Hub/Editor/2021.3.11f1/Editor/Data/PlaybackEngines/AndroidPlayer/Tools/AndroidNativeLink.xml --include-directory=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed --profiler-report --profiler-output-file=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Library/Bee/artifacts/unitylinker_xy1a.traceevents --dotnetprofile=unityaot-linux --dotnetruntime=Il2Cpp --platform=Android --use-editor-options --enable-engine-module-stripping --engine-modules-asset-file=C:/Program Files/Unity/Hub/Editor/2021.3.11f1/Editor/Data/PlaybackEngines/AndroidPlayer/modules.asset --editor-data-file=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/EditorToUnityLinkerData.json --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/Assembly-CSharp.dll --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/Unity.RenderPipelines.Universal.Runtime.dll --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/Unity.TextMeshPro.dll --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/UnityEngine.UI.dll --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/Unity.RenderPipelines.Core.Runtime.dll --include-unity-root-assembly=D:/Nimets/Projects/KhmerChess/KhmerChessClient/Temp/StagingArea/Data/Managed/Facebook.Unity.Settings.dll --print-command-line --enable-analytics
Fatal error in Unity CIL Linker
System.NullReferenceException: Object reference not set to an instance of an object.
at Unity.Linker.Analytics.AnalyticsService.RecognizedReflectionAccessPattern(IMemberDefinition source, Instruction sourceInstruction, IMetadataTokenProvider accessedItem)
at Mono.Linker.Dataflow.ReflectionMethodBodyScanner.MarkTypeForDynamicallyAccessedMembers(ReflectionPatternContext& reflectionContext, TypeDefinition typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes)
at Mono.Linker.Dataflow.ReflectionMethodBodyScanner.RequireDynamicallyAccessedMembers(ReflectionPatternContext& reflectionContext, DynamicallyAccessedMemberTypes requiredMemberTypes, ValueNode value, IMetadataTokenProvider targetContext)
at Mono.Linker.Dataflow.ReflectionMethodBodyScanner.ProcessGenericArgumentDataFlow(GenericParameter genericParameter, TypeReference genericArgument, IMemberDefinition source)
at Mono.Linker.Steps.MarkStep.MarkGenericArgumentConstructors(IGenericInstance instance, IMemberDefinition sourceLocationMember)
at Mono.Linker.Steps.MarkStep.MarkGenericArguments(IGenericInstance instance, IMemberDefinition sourceLocationMember)
at Mono.Linker.Steps.MarkStep.GetOriginalType(TypeReference type, DependencyInfo reason, IMemberDefinition sourceLocationMember)
at Mono.Linker.Steps.MarkStep.MarkType(TypeReference reference, DependencyInfo reason, IMemberDefinition sourceLocationMember)
at Mono.Linker.Steps.MarkStep.MarkInterfaceImplementation(InterfaceImplementation iface, TypeDefinition type)
at Mono.Linker.Steps.MarkStep.MarkInterfaceImplementations(TypeDefinition type)
at Mono.Linker.Steps.MarkStep.MarkRequirementsForInstantiatedTypes(TypeDefinition type)
at Mono.Linker.Steps.MarkStep.MarkType(TypeReference reference, DependencyInfo reason, IMemberDefinition sourceLocationMember)
at Mono.Linker.Steps.MarkStep.InitializeType(TypeDefinition type)
at Mono.Linker.Steps.MarkStep.InitializeAssembly(AssemblyDefinition assembly)
at Unity.Linker.Steps.UnityMarkStep.InitializeAssembly(AssemblyDefinition assembly)
at Mono.Linker.Steps.MarkStep.Initialize()
at Mono.Linker.Steps.MarkStep.Process(LinkContext context)
at Unity.Linker.Steps.UnityMarkStep.Process(LinkContext context)
at Unity.Linker.UnityPipeline.ProcessStep(LinkContext context, IStep step)
at Mono.Linker.Pipeline.Process(LinkContext context)
at Unity.Linker.UnityDriver.UnityRun(Boolean noProfilerAllowed, ILogger customLogger)
at Unity.Linker.UnityDriver.RunDriverWithoutErrorHandling(ILogger customLogger, Boolean noProfilerAllowed)
at Unity.Linker.UnityDriver.RunDriver()
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)
Error 3:
BuildFailedException: Incremental Player build failed!
UnityEditor.Modules.BeeBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at <4e64905d831f4883a53259ef37fb023b>:0)
UnityEditor.Modules.DefaultBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args, UnityEditor.BuildProperties& outProperties) (at <4e64905d831f4883a53259ef37fb023b>:0)
UnityEditor.Android.AndroidBuildPostprocessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args, UnityEditor.BuildProperties& outProperties) (at <5e58a3838afa4e88a08dc92f05003dcc>:0)
UnityEditor.PostprocessBuildPlayer.Postprocess (UnityEditor.BuildTargetGroup targetGroup, UnityEditor.BuildTarget target, System.Int32 subtarget, System.String installPath, System.String companyName, System.String productName, System.Int32 width, System.Int32 height, UnityEditor.BuildOptions options, UnityEditor.RuntimeClassRegistry usedClassRegistry, UnityEditor.Build.Reporting.BuildReport report) (at <4e64905d831f4883a53259ef37fb023b>:0)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)
Problem solve by update editor to ver.2022.3.16f1
Hey Masui,
Thanks for the tutorial,
I can get it working for the editor, however when I build for android I'm getting errors in the app when calling .Build on the HubConnectionBuilder. It's possibly related to the DependancyInjection lib
I've posted in more detail here on the unity forums and was wondering if you could share any insight as to what may be causing this or if you have experienced this error before.
Thanks
For anyone else with this issue it was fixed using this work around github.com/dotnet/aspnetcore/issue...
You might be interested in this project I did github.com/PereViader/CSharpProjec...
It might help you integrate any c# project as a unity package
This is cool, but I want to put in something that simply shows updating data from another website. Which one of these scripts does that? Do I need to use two of them?