Having problem getting this to work.
I want my hub to handle a generic in the argument. So the parameter type is an abstract class which'll be implemented by a concrete generic type - since I can't possibly create a generic method. Like this:
public void Process(MyAbstractClass arg)
However the registration failed when I tell the client to serialize the type information.
This is the client (SignalR WinRT) serialization configuration.
_hubConnecton.JsonSerializer = new JsonSerializer()
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.Objects,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
};
This is the error I got from fiddler trace:
[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)
Apparently it sends the type information during registration which causes the above error to get thrown
GET 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
If I change the following line TypeNameHandling = TypeNameHandling.Objects
to TypeNameHandling = TypeNameHandling.Auto
, then I get an error complaining that MyAbstractClass
cannot be instantiated because it's an abstract type.
It almost seems like I need to handle the serialization manually, but I rather avoid that if I could.
Thoughts?
This can be done but not easily - someone at the SignalR team must have been trying real hard to make it near impossible to extend the parsing routine.
I saw a bunch of JSonSerializer instantiation, instead of feeding the ones already registered in the GlobalConfig.
Anyways here's how to do it:
On client side, implement IHttpClient. This impelementation will strip out type information from the message envelope. We do NOT need to preserve type info on the envelope, as far as we're concerned one envelope is the same as another. It's the content Type that's important. Plus the envelope for WinRT references WinRT framework that is not compatible with the standard framework.
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);
}
}
You want to start your connection like this on the client side (in my case an appstore app)
_hubConnecton.Start(new AutoTransport(new PolymorphicHttpClient(new DefaultHttpClient())))
I wished this was enough, but on the server side, the built in parser is a hot bungled mess, so I had to "hack" it together also.
You want to implement IJsonValue. The original implementation creates a new instance of JSonSerializer that does not respect your configuration.
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;
}
}
Then you want to create your own parser. Note the reflection hack, you may want to change the type to whatever ver. of SignalR you have. This was also why I said whoever wrote this section must really hate OO, because all of the modules are internals - making them real hard to extend.
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; }
}
}
Now everything is in place, you wanna start your SignalR service with the following overrides. Container being whatever DI you register with the host. In my case container is an instance of IUnityContainer
.
//Override the defauult json serializer behavior to follow our default settings instead.
container.RegisterInstance<IJsonSerializer>(
new JsonNetSerializer(Serialization.DefaultJsonSerializerSettings));
container.RegisterType<IHubRequestParser, PolymorphicHubRequestParser>();