SignalR集线器方法参数序列化 - c#

我将需要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再次序列化。我怎样才能做到这一点?

参考方案

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

Microsoft Excel 2010和Python中的受保护视图 - python

这里没有代码示例。刚遇到Microsoft Excel 2010的问题,我在linux上有一个python脚本,可从csv文件中提取数据,将数据推送到excel,然后将该文件作为附件通过电子邮件发送到特定的电子邮件地址。我的问题是我在excel文件中使用公式,并且在它第一次打开时进入“ Protected View”。在单击“启用编辑”后,我的公式才会加载。…

bulit-in gradle插件的版本号是多少? - java

在我的gradle构建文件中,我有以下插件块plugins { `java-library` jacoco checkstyle } 这些都没有指定版本,但是一切正常。假定一个项目正在使用gradle 6.0和gradle包装器,但是系统已安装gradle 5.0。问题:如果我运行gradle wrapper ./gradlew build,将会执行grad…

Microsoft WPF转换器 - c#

因此,我今天在MSDN上找到了list转换器,现在我想使用其中的一些。但是,经过一番搜索,我似乎找不到任何有关它们的信息。我主要想使用IntToBoolConverter。但是我不知道如何使用转换,因为没有信息提供如何做到这一点(或在谷歌上)。我知道自己制作这个转换器很容易,但是我是一名程序员,当可以的时候,我的moto是懒惰的,而使已经存在的方法(转换器)…

循环结构之do-while循环 - #include in

#include <stdio.h>int main(){    int number=200;    int year=2014;    do{      year++;           number=number*1.2;     //这里是不是应该写点什么?    }while(number<1000);     //这里好像缺点…

编程练习 - public class HelloWo

public class HelloWorld {    public static void main(String[] args) {        // 变量保存成绩        int score = 53;         // 变量保存加分次数        int count = 0;        //打印输出加分前成绩         S…