SignalR hub方法参数序列化

时间:2013-08-02 15:36:20

标签: signalr signalr-hub

我需要SignalR开发人员的一些指导,这是调整HUB方法参数序列化的最佳方法。

我开始将项目从WCF轮询双工(Silverlight 5 - ASP.NET 4.5)迁移到SignalR(1.1.2)。消息(数据契约)是基于接口的多态的。 (与IMessage,MessageA:IMessage等一样 - 实际上存在由类实现的接口层次结构,但对于该问题并没有多大意义)。 (我知道多态对象不适合客户端,但客户端会将其作为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);

但我也发现在未来的SignalR版本中将删除IJsonSerializer接口。基本上,我需要从HUB方法获取原始JSON(或字节流),这样我就可以自己反序列化它,或者通过指定转换器来调整序列化器等。

现在我最终用JObject参数类型定义了方法:

public void SendMessage(JObject data)

然后使用

手动反序列化数据
JObject.ToObject<IMessage>(JsonSerializer)

方法。但我更喜欢自定义序列化程序并在hub方法上使用类型/接口。关于设计下一个SignalR的“正确方法”是什么?

我还发现有可能从我的代码发回客户端原始JSON,即使SignalR不再再次序列化该对象。我怎么能做到这一点?

2 个答案:

答案 0 :(得分:0)

如果使用连接API而不是Hub API,则可以处理OnReceive事件并以原始JSON(字符串)形式获取请求。看看this example

在2.x版本中添加了使用Hub API向客户端发送预序列化数据的能力,我不知道在1.x中执行此操作的任何方法(参见github issue

答案 1 :(得分:0)

我尝试使用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文件。有人要检查吗?