【问题标题】:Polymorphism in SignalRSignalR 中的多态性
【发布时间】:2018-04-05 22:47:43
【问题描述】:

让这个工作有问题。

我希望我的集线器处理参数中的泛型。所以参数类型是一个抽象类,它将由一个具体的泛型类型实现——因为我不可能创建一个泛型方法。像这样:

         public void Process(MyAbstractClass arg)

但是当我告诉客户端序列化类型信息时注册失败。

这是客户端 (SignalR WinRT) 序列化配置。

        _hubConnecton.JsonSerializer = new JsonSerializer()
                                       {
                                           PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                                           TypeNameHandling = TypeNameHandling.Objects,
                                           TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
                                       };

这是我从提琴手跟踪得到的错误:

[JsonSerializationException]: Could not load assembly 'Microsoft.AspNet.SignalR.Client'.
   at Newtonsoft.Json.Serialization.DefaultSerializationBinder.GetTypeFromTypeNameKey(TypeNameKey typeNameKey)
   at Newtonsoft.Json.Utilities.ThreadSafeStore`2.AddValue(TKey key)
   at Newtonsoft.Json.Utilities.ThreadSafeStore`2.Get(TKey key)
   at Newtonsoft.Json.Serialization.DefaultSerializationBinder.BindToType(String assemblyName, String typeName)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadSpecialProperties(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue, Object& newValue, String& id)
[JsonSerializationException]: Error resolving type specified in JSON 'Microsoft.AspNet.SignalR.Client.Hubs.HubRegistrationData, Microsoft.AspNet.SignalR.Client'. Path '[0].$type', line 1, position 111.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadSpecialProperties(JsonReader reader, Type& objectType, JsonContract& contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue, Object& newValue, String& id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateList(IWrappedCollection wrappedList, JsonReader reader, JsonArrayContract contract, JsonProperty containerProperty, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(TextReader reader, Type objectType)
   at Microsoft.AspNet.SignalR.Json.JsonNetSerializer.Parse(TextReader reader, Type targetType)
   at Microsoft.AspNet.SignalR.Json.JsonSerializerExtensions.Parse[T](IJsonSerializer serializer, String json)
   at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.AuthorizeRequest(IRequest request)
   at Microsoft.AspNet.SignalR.PersistentConnection.Authorize(IRequest request)
   at Microsoft.AspNet.SignalR.Owin.CallHandler.Invoke(IDictionary`2 environment)
   at Microsoft.AspNet.SignalR.Owin.Handlers.HubDispatcherHandler.Invoke(IDictionary`2 environment)
   at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Execute()
   at Microsoft.Owin.Host.SystemWeb.OwinHttpHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object extraData)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.Owin.Host.SystemWeb.Infrastructure.ErrorState.Rethrow()
   at Microsoft.Owin.Host.SystemWeb.CallContextAsyncResult.End(IAsyncResult result)
   at Microsoft.Owin.Host.SystemWeb.OwinHttpHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

显然它在注册期间发送类型信息导致上述错误被抛出

获取 http://127.0.0.1:81/signalr/connect?transport=serverSentEvents&connectionToken=JKbyIAOOvt5BYGu_Ly2Yk9dNYVR7B180TobrrJpc5BYN5-DxdSwXs6i71pF0nJrLC3C7kaB-4VwD8Lu76vgVbIoWLE5Ux42GhJOJ_REslxuvo0bcCkbvf3rfki3Rk6TJ0&connectionData=[%7B%22$id%22:%221%22,%22$type%22:%22Microsoft.AspNet .SignalR.Client.Hubs.HubRegistrationData,%20Microsoft.AspNet.SignalR.Client%22,%22Name%22:%22BusGatewayHub%22%7D] HTTP/1.1

如果我将以下行 TypeNameHandling = TypeNameHandling.Objects 更改为 TypeNameHandling = TypeNameHandling.Auto,则会收到一条错误消息,抱怨 MyAbstractClass 无法实例化,因为它是抽象类型。

似乎我需要手动处理序列化,但如果可以的话,我宁愿避免这样做。

想法?

【问题讨论】:

    标签: .net windows-runtime windows-store-apps json.net signalr


    【解决方案1】:

    这可以做到,但并不容易 - SignalR 团队中的某个人一定非常努力地使扩展解析例程几乎不可能。

    我看到一堆 JSonSerializer 实例化,而不是提供已经在 GlobalConfig 中注册的那些。

    无论如何,这里是如何做到的:

    在客户端,实现 IHttpClient。这种实施将从消息信封中去除类型信息。我们不需要在信封上保留类型信息,就我们而言,一个信封与另一个相同。重要的是内容类型。加上 WinRT 的包络引用了与标准框架不兼容的 WinRT 框架。

    public class PolymorphicHttpClient : IHttpClient
    {
        private readonly IHttpClient _innerClient;
    
        private Regex _invalidTypeDeclaration = new Regex(@"""?\$type.*?:.*?"".*?SignalR\.Client.*?"",?");
    
        public PolymorphicHttpClient(IHttpClient innerClient)
        {
            _innerClient = innerClient;
        }
    
        public Task<IResponse> Get(string url, Action<IRequest> prepareRequest)
        {
            url = _invalidTypeDeclaration.Replace(url, "");
            return _innerClient.Get(url, prepareRequest);
        }
    
        public Task<IResponse> Post(string url, Action<IRequest> prepareRequest, IDictionary<string, string> postData)
        {
            if (postData != null)
            {
                var postedDataDebug = postData;
                    //TODO: check out what the data looks like and strip out irrelevant type information.
                var revisedData = postData.ToDictionary(_ => _.Key,
                                                        _ =>
                                                        _.Value != null
                                                            ? _invalidTypeDeclaration.Replace(_.Value, "")
                                                            : null);
                return _innerClient.Post(url, prepareRequest, revisedData);
            }
    
            return _innerClient.Post(url, prepareRequest, null);
        }
    }
    

    你想在客户端开始这样的连接(在我的例子中是一个应用商店应用)

     _hubConnecton.Start(new AutoTransport(new PolymorphicHttpClient(new DefaultHttpClient())))
    

    我希望这已经足够了,但是在服务器端,内置的解析器是一团糟,所以我也不得不一起“破解”它。

    您想实现 IJsonValue。原始实现创建了一个不尊重您的配置的新的 JSonSerializer 实例。

     public class SerializerRespectingJRaw : IJsonValue
    {
        private readonly IJsonSerializer _jsonSerializer;
        private readonly JRaw _rawJson;
    
        public SerializerRespectingJRaw(IJsonSerializer jsonSerializer, JRaw rawJson)
        {
            _jsonSerializer = jsonSerializer;
            _rawJson = rawJson;
        }
    
        public object ConvertTo(Type type)
        {
            return _jsonSerializer.Parse<object>(_rawJson.ToString());            
        }
    
        public bool CanConvertTo(Type type)
        {
            return true;
        }
    }
    

    然后您想创建自己的解析器。请注意反射黑客,您可能希望将类型更改为任何版本。 SignalR 你有。这也是为什么我说写这部分的人一定非常讨厌 OO,因为所有模块都是内部的 - 使得它们很难扩展。

    public class PolymorphicHubRequestParser : IHubRequestParser
    {
        private readonly IJsonSerializer _jsonSerializer;
        private JsonConverter _converter;
    
        public PolymorphicHubRequestParser(IJsonSerializer jsonSerializer)
        {
            _converter =
                (JsonConverter) Type.GetType(
                    "Microsoft.AspNet.SignalR.Json.SipHashBasedDictionaryConverter, Microsoft.AspNet.SignalR.Core, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")
                    .GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
                    .Single(_ => !_.GetParameters().Any())
                    .Invoke(null);
    
            _jsonSerializer = jsonSerializer;
        }
    
        private IDictionary<string, object> GetState(HubInvocation deserializedData)
        {
            if (deserializedData.State == null)
                return (IDictionary<string, object>)new Dictionary<string, object>();
            string json = ((object)deserializedData.State).ToString();
            if (json.Length > 4096)
                throw new InvalidOperationException("Maximum length exceeded.");
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Converters.Add(_converter);
            return JsonSerializerExtensions.Parse<IDictionary<string, object>>((IJsonSerializer)new JsonNetSerializer(settings), json);
        }
    
        public HubRequest Parse(string data)
        {
            var deserializedInvocation = new JsonNetSerializer().Parse<HubInvocation>(data);
            var secondPass = new HubRequest()
                                 {
                                     Hub = deserializedInvocation.Hub,
                                     Id = deserializedInvocation.Id,
                                     Method = deserializedInvocation.Method,
                                     State = GetState(deserializedInvocation),
                                     ParameterValues =
                                         deserializedInvocation.Args.Select(
                                             _ => new SerializerRespectingJRaw(_jsonSerializer, _))
                                                               .Cast<IJsonValue>()
                                                               .ToArray()
                                 };
            return secondPass;
        }
    
        private class HubInvocation
        {
            [JsonProperty("H")]
            public string Hub { get; set; }
    
            [JsonProperty("M")]
            public string Method { get; set; }
    
            [JsonProperty("I")]
            public string Id { get; set; }
    
            [JsonProperty("S")]
            public JRaw State { get; set; }
    
            [JsonProperty("A")]
            public JRaw[] Args { get; set; }
        }
    }
    

    现在一切就绪,您想使用以下覆盖启动 SignalR 服务。容器是您向主机注册的任何 DI。在我的例子中,容器是IUnityContainer 的一个实例。

            //Override the defauult json serializer behavior to follow our default settings instead.
            container.RegisterInstance<IJsonSerializer>(
                new JsonNetSerializer(Serialization.DefaultJsonSerializerSettings));
            container.RegisterType<IHubRequestParser, PolymorphicHubRequestParser>();
    

    【讨论】:

    • 这看起来很可怕。可能有一种更简单的方法可以让这个工作成为一个 JSON 转换器。
    • 在这种情况下,使用持久连接而不是集线器似乎更容易。然后在任一侧酌情对string data 进行反序列化/序列化。我想知道这是否会双重序列化。 github.com/SignalR/SignalR/wiki/…
    • 我已经在这里写下了我的答案,我认为这是一个很好的方法,只需少量代码更改。 stackoverflow.com/a/64890938/261082
    【解决方案2】:

    我认为您最好为 JSON 序列化创建一个中间类型,其中包含手动重新生成具体类型所需的所有数据。

    另一种选择是为每个实现MyAbstractClass 的具体类型创建具有不同名称(未重载)的 N 个方法。然后,每个方法都可以简单地将其参数传递给您的 Process 方法,该方法接受抽象类型。您必须小心以正确的类型调用正确的集线器方法,但它可能会起作用。

    【讨论】:

      【解决方案3】:

      或者...您可以改用连接Received 事件,返回一个字符串。然后,使用 JObject 对其进行解析,确定其类型并进行相应的反序列化。

      【讨论】:

        【解决方案4】:
                RootObject rootObject = JsonConvert.DeserializeObject<RootObject>(data);
        
            A msgBase = rootObject.A[0];
        
            if (msgBase.CommandName == "RequestInfo")
            {
                RootObjectRequestInfo rootObjectRequestInfo = JsonConvert.DeserializeObject<RootObjectRequestInfo>(data);
                RequestInfo requestInfo = rootObjectRequestInfo.RequestInfo[0];
        
                ConnectionID = requestInfo.ConnectionID;
            }
            else if (msgBase.CommandName == "TalkWord")
            {
                RootObjectTalkWord rootObjectTalkWord = JsonConvert.DeserializeObject<RootObjectTalkWord>(data);
                TalkWord talkWord = rootObjectTalkWord.TalkWord[0];
        
                textBoxAll.Text += talkWord.Word + "\r\n";
            }
        

        【讨论】:

          【解决方案5】:

          有一个简单的解决方案,只需几行代码,但它需要一个丑陋的反射 hack。好处是这个解决方案适用于序列化和反序列化,即向客户端发送数据并从中接收。

          在客户端使用TypeNameHandling.Auto 可以在发送和接收中实现完全的多态性。

          var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
          GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => JsonSerializer.CreateDefault(settings));
          GlobalHost.DependencyResolver.Register(typeof(IParameterResolver), () => new CustomResolver());
          
          class CustomResolver : DefaultParameterResolver
          {
              public override object ResolveParameter(ParameterDescriptor descriptor, Microsoft.AspNet.SignalR.Json.IJsonValue value)
              {
                  var _value = (string)value.GetType().GetField("_value", System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(value);
                  var obj = JsonConvert.DeserializeObject(_value, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Auto });
                  return obj;
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-04-11
            • 1970-01-01
            • 1970-01-01
            • 2016-03-19
            • 1970-01-01
            • 2011-05-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多