我需要 SignalR 开发人员的一些指导,什么是调整 HUB 方法参数序列化的最佳方法。
我开始将我的项目从 WCF 轮询双工 (Silverlight 5 - ASP.NET 4.5) 迁移到 SignalR (1.1.2)。消息(数据契约)是基于接口(interface)的多态的。 (如 IMessage、MessageA : IMessage 等 - 实际上有一个由类实现的接口(interface)层次结构,但对于这个问题来说意义不大)。
(我知道多态对象对客户端不利,但客户端会将其作为 JSON 处理,并且映射到对象仅在服务器端或客户端(如果它是 .NET/Silverlight)上完成)
在集线器上,我定义了这样的方法:
public void SendMessage(IMessage data) { .. }
我创建了自定义 JsonConverters 并验证了消息可以使用 Json.NET 进行序列化/反序列化。然后我用适当的设置替换了 DependencyResolver 中的 JsonNetSerializer。 Silverlight 客户端也是如此。到现在为止还挺好。
但是,当我将消息从客户端发送到服务器时(消息已正确序列化为 JSON - 在 Fiddler 中验证),服务器返回参数无法反序列化的错误。
在调试器的帮助下,我在 SignalR 中发现了一个错误(负责反序列化参数的 JRawValue 类在内部创建自己的 JsonSerializer 实例,忽略提供的实例)。通过更换似乎很容易修复
var settings = new JsonSerializerSettings
{
MaxDepth = 20
};
var serializer = JsonSerializer.Create(settings);
return serializer.Deserialize(jsonReader, type);
和
var serializer = GlobalHost.DependencyResolver.Resolve<IJsonSerializer>();
return serializer.Parse(jsonReader, type);
但我也发现接口(interface) IJsonSerializer 将在 SignalR 的 future 版本中被删除。基本上,我需要的是从 HUB 方法获取原始 JSON(或字节流),以便我可以自己反序列化它,或者可以通过指定转换器等来调整序列化程序。
现在,我最终定义了具有 JObject 参数类型的方法:
public void SendMessage(JObject data)
然后使用手动反序列化数据
JObject.ToObject<IMessage>(JsonSerializer)
方法。但我更愿意自定义序列化程序并在集线器方法上使用类型/接口(interface)。关于下一个 SignalR 的设计,“正确的方法”是什么?
我还发现有可能从我的代码中将原始 JSON 发送回客户端很有用,即,这样对象就不会再次被 SignalR 序列化。我怎么能做到这一点?
最佳答案
我尝试使用 EnableJsonTypeNameHandlingConverter
更改客户端和服务器序列化配置发表 here加上以下用于双向连接的客户端和服务器代码。
如您所见,有代码可以在客户端和服务器上设置自定义序列化......但它不起作用!
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Client;
using Newtonsoft.Json;
using Owin;
class Program
{
static void Main(string[] args)
{
// Ensure serialization and deserialization works outside SignalR
INameAndId nameId = new NameAndId(5, "Five");
string json = JsonConvert.SerializeObject(nameId, Formatting.Indented, new EnableJsonTypeNameHandlingConverter());
var clone = JsonConvert.DeserializeObject(json, typeof(INameAndId), new EnableJsonTypeNameHandlingConverter());
Console.WriteLine(json);
// Start server
// http://+:80/Temporary_Listen_Addresses is allowed by default - all other routes require special permission
string url = "http://+:80/Temporary_Listen_Addresses/example";
using (Microsoft.Owin.Hosting.WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
// Start client side
HubConnection conn = new HubConnection("http://127.0.0.1:80/Temporary_Listen_Addresses/example");
conn.JsonSerializer.Converters.Add(new EnableJsonTypeNameHandlingConverter());
// Note: SignalR requires CreateHubProxy() to be called before Start()
var hp = conn.CreateHubProxy(nameof(SignalRHub));
var proxy = new SignalRProxy(hp, new SignalRCallback());
conn.Start().Wait();
proxy.Foo();
// AggregateException on server: Could not create an instance of type
// SignalRSelfHost.INameAndId. Type is an interface or abstract class
// and cannot be instantiated.
proxy.Bar(nameId);
Console.ReadLine();
}
}
}
class Startup
{
// Magic method expected by OWIN
public void Configuration(IAppBuilder app)
{
//app.UseCors(CorsOptions.AllowAll);
var hubCfg = new HubConfiguration();
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new EnableJsonTypeNameHandlingConverter());
hubCfg.EnableDetailedErrors = true;
hubCfg.Resolver.Register(typeof(JsonSerializer), () => JsonSerializer.Create(jsonSettings));
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => JsonSerializer.Create(jsonSettings));
app.MapSignalR(hubCfg);
}
}
// Messages that can be sent to the server
public interface ISignalRInterface
{
void Foo();
void Bar(INameAndId param);
}
// Messages that can be sent back to the client
public interface ISignalRCallback
{
void Baz();
}
// Server-side hub
public class SignalRHub : Hub<ISignalRCallback>, ISignalRInterface
{
protected ISignalRCallback GetCallback(string hubname)
{
// Note: SignalR hubs are transient - they connection lives longer than the
// Hub - so it is generally unwise to store information in member variables.
// Therefore, the ISignalRCallback object is not cached.
return GlobalHost.ConnectionManager.GetHubContext<ISignalRCallback>(hubname).Clients.Client(Context.ConnectionId);
}
public virtual void Foo() { Console.WriteLine("Foo!"); }
public virtual void Bar(INameAndId param) { Console.WriteLine("Bar!"); }
}
// Client-side proxy for server-side hub
public class SignalRProxy
{
private IHubProxy _Proxy;
public SignalRProxy(IHubProxy proxy, ISignalRCallback callback)
{
_Proxy = proxy;
_Proxy.On(nameof(ISignalRCallback.Baz), callback.Baz);
}
public void Send(string method, params object[] args)
{
_Proxy.Invoke(method, args).Wait();
}
public void Foo() => Send(nameof(Foo));
public void Bar(INameAndId param) => Send(nameof(Bar), param);
}
public class SignalRCallback : ISignalRCallback
{
public void Baz() { }
}
[Serializable]
public class NameAndId : INameAndId
{
public NameAndId(int id, string name)
{
Id = id;
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
}
[EnableJsonTypeNameHandling]
public interface INameAndId
{
string Name { get; }
int Id { get; }
}
SignalR 调用传递给
GlobalHost.DependencyResolver
的 lambda不少于 8 次,但最终它忽略了提供的序列化程序。我找不到任何关于 SignalR 参数序列化的文档,所以我使用 Rider 的反编译调试器来帮助找出发生了什么。
SignalR 内部有一个
HubRequestParser.Parse
使用正确 JsonSerializer
的方法,但它实际上并没有反序列化参数。稍后在 DefaultParameterResolver.ResolveParameter()
中对参数进行反序列化间接调用CreateDefaultSerializerSettings()
在以下调用堆栈中:JsonUtility.CreateDefaultSerializerSettings() in Microsoft.AspNet.SignalR.Json, Microsoft.AspNet.SignalR.Core.dll
JsonUtility.CreateDefaultSerializer() in Microsoft.AspNet.SignalR.Json, Microsoft.AspNet.SignalR.Core.dll
JRawValue.ConvertTo() in Microsoft.AspNet.SignalR.Json, Microsoft.AspNet.SignalR.Core.dll
DefaultParameterResolver.ResolveParameter() in Microsoft.AspNet.SignalR.Hubs, Microsoft.AspNet.SignalR.Core.dll
Enumerable.<ZipIterator>d__61<ParameterDescriptor, IJsonValue, object>.MoveNext() in System.Linq, System.Core.dll
new Buffer<object>() in System.Linq, System.Core.dll
Enumerable.ToArray<object>() in System.Linq, System.Core.dll
DefaultParameterResolver.ResolveMethodParameters() in Microsoft.AspNet.SignalR.Hubs, Microsoft.AspNet.SignalR.Core.dll
HubDispatcher.InvokeHubPipeline() in Microsoft.AspNet.SignalR.Hubs, Microsoft.AspNet.SignalR.Core.dll
HubDispatcher.OnReceived() in Microsoft.AspNet.SignalR.Hubs, Microsoft.AspNet.SignalR.Core.dll
PersistentConnection.<>c__DisplayClass64_1.<ProcessRequestPostGroupRead>b__5() in Microsoft.AspNet.SignalR, Microsoft.AspNet.SignalR.Core.dll
TaskAsyncHelper.FromMethod() in Microsoft.AspNet.SignalR, Microsoft.AspNet.SignalR.Core.dll
PersistentConnection.<>c__DisplayClass64_0.<ProcessRequestPostGroupRead>b__4() in Microsoft.AspNet.SignalR, Microsoft.AspNet.SignalR.Core.dll
WebSocketTransport.OnMessage() in Microsoft.AspNet.SignalR.Transports, Microsoft.AspNet.SignalR.Core.dll
DefaultWebSocketHandler.OnMessage() in Microsoft.AspNet.SignalR.WebSockets, Microsoft.AspNet.SignalR.Core.dll
WebSocketHandler.<ProcessWebSocketRequestAsync>d__25.MoveNext() in Microsoft.AspNet.SignalR.WebSockets, Microsoft.AspNet.SignalR.Core.dll
AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext() in System.Runtime.CompilerServices, mscorlib.dll [5]
ExecutionContext.RunInternal() in System.Threading, mscorlib.dll [5]
ExecutionContext.Run() in System.Threading, mscorlib.dll [5]
AsyncMethodBuilderCore.MoveNextRunner.Run() in System.Runtime.CompilerServices, mscorlib.dll [5]
...
在 SignalR source code问题很明显:
// in DefaultParameterResolver
public virtual object ResolveParameter(ParameterDescriptor descriptor, IJsonValue value)
{
// [...]
return value.ConvertTo(descriptor.ParameterType);
}
// in JRawValue
public object ConvertTo(Type type)
{
// A non generic implementation of ToObject<T> on JToken
using (var jsonReader = new StringReader(_value))
{
var serializer = JsonUtility.CreateDefaultSerializer();
return serializer.Deserialize(jsonReader, type);
}
}
// in JsonUtility
public static JsonSerializer CreateDefaultSerializer()
{
return JsonSerializer.Create(CreateDefaultSerializerSettings());
}
public static JsonSerializerSettings CreateDefaultSerializerSettings()
{
return new JsonSerializerSettings() { MaxDepth = DefaultMaxDepth };
}
因此 SignalR 将您的自定义(反)序列化器用于其部分工作,而不是用于参数反序列化。
我想不通的是 2015 answer on this other question有 8 票,这似乎意味着该解决方案在过去 4 年中的某个时间点对某人有效,但如果是这样,那么一定有一个我们不知道的技巧。
也许 .NET Core version of SignalR解决了这个问题。看起来该版本已被显着重构,不再具有
DefaultParameterResolver.cs
文件。有人关心检查吗?
关于c# - SignalR 集线器方法参数序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18020897/