예제 #1
0
        private HubMessage ParseMessage(TextReader textReader, IInvocationBinder binder)
        {
            try
            {
                // We parse using the JsonTextReader directly but this has a problem. Some of our properties are dependent on other properties
                // and since reading the json might be unordered, we need to store the parsed content as JToken to re-parse when true types are known.
                // if we're lucky and the state we need to directly parse is available, then we'll use it.

                int?     type           = null;
                string   invocationId   = null;
                string   target         = null;
                string   error          = null;
                var      hasItem        = false;
                object   item           = null;
                JToken   itemToken      = null;
                var      hasResult      = false;
                object   result         = null;
                JToken   resultToken    = null;
                bool     hasArguments   = false;
                object[] arguments      = null;
                JArray   argumentsToken = null;
                ExceptionDispatchInfo       argumentBindingException = null;
                Dictionary <string, string> headers = null;
                var completed = false;

                using (var reader = new JsonTextReader(textReader))
                {
                    reader.ArrayPool = JsonArrayPool <char> .Shared;

                    JsonUtils.CheckRead(reader);

                    // We're always parsing a JSON object
                    if (reader.TokenType != JsonToken.StartObject)
                    {
                        throw new InvalidDataException($"Unexpected JSON Token Type '{JsonUtils.GetTokenString(reader.TokenType)}'. Expected a JSON Object.");
                    }

                    do
                    {
                        switch (reader.TokenType)
                        {
                        case JsonToken.PropertyName:
                            string memberName = reader.Value.ToString();

                            switch (memberName)
                            {
                            case TypePropertyName:
                                var messageType = JsonUtils.ReadAsInt32(reader, TypePropertyName);

                                if (messageType == null)
                                {
                                    throw new InvalidDataException($"Missing required property '{TypePropertyName}'.");
                                }

                                type = messageType.Value;
                                break;

                            case InvocationIdPropertyName:
                                invocationId = JsonUtils.ReadAsString(reader, InvocationIdPropertyName);
                                break;

                            case TargetPropertyName:
                                target = JsonUtils.ReadAsString(reader, TargetPropertyName);
                                break;

                            case ErrorPropertyName:
                                error = JsonUtils.ReadAsString(reader, ErrorPropertyName);
                                break;

                            case ResultPropertyName:
                                JsonUtils.CheckRead(reader);

                                hasResult = true;

                                if (string.IsNullOrEmpty(invocationId))
                                {
                                    // If we don't have an invocation id then we need to store it as a JToken so we can parse it later
                                    resultToken = JToken.Load(reader);
                                }
                                else
                                {
                                    // If we have an invocation id already we can parse the end result
                                    var returnType = binder.GetReturnType(invocationId);
                                    result = PayloadSerializer.Deserialize(reader, returnType);
                                }
                                break;

                            case ItemPropertyName:
                                JsonUtils.CheckRead(reader);

                                hasItem = true;

                                if (string.IsNullOrEmpty(invocationId))
                                {
                                    // If we don't have an invocation id then we need to store it as a JToken so we can parse it later
                                    itemToken = JToken.Load(reader);
                                }
                                else
                                {
                                    var returnType = binder.GetReturnType(invocationId);
                                    item = PayloadSerializer.Deserialize(reader, returnType);
                                }
                                break;

                            case ArgumentsPropertyName:
                                JsonUtils.CheckRead(reader);

                                if (reader.TokenType != JsonToken.StartArray)
                                {
                                    throw new InvalidDataException($"Expected '{ArgumentsPropertyName}' to be of type {JTokenType.Array}.");
                                }

                                hasArguments = true;

                                if (string.IsNullOrEmpty(target))
                                {
                                    // We don't know the method name yet so just parse an array of generic JArray
                                    argumentsToken = JArray.Load(reader);
                                }
                                else
                                {
                                    try
                                    {
                                        var paramTypes = binder.GetParameterTypes(target);
                                        arguments = BindArguments(reader, paramTypes);
                                    }
                                    catch (Exception ex)
                                    {
                                        argumentBindingException = ExceptionDispatchInfo.Capture(ex);
                                    }
                                }
                                break;

                            case HeadersPropertyName:
                                JsonUtils.CheckRead(reader);
                                headers = ReadHeaders(reader);
                                break;

                            default:
                                // Skip read the property name
                                JsonUtils.CheckRead(reader);
                                // Skip the value for this property
                                reader.Skip();
                                break;
                            }
                            break;

                        case JsonToken.EndObject:
                            completed = true;
                            break;
                        }
                    }while (!completed && JsonUtils.CheckRead(reader));
                }

                HubMessage message;

                switch (type)
                {
                case HubProtocolConstants.InvocationMessageType:
                {
                    if (argumentsToken != null)
                    {
                        try
                        {
                            var paramTypes = binder.GetParameterTypes(target);
                            arguments = BindArguments(argumentsToken, paramTypes);
                        }
                        catch (Exception ex)
                        {
                            argumentBindingException = ExceptionDispatchInfo.Capture(ex);
                        }
                    }

                    message = BindInvocationMessage(invocationId, target, argumentBindingException, arguments, hasArguments, binder);
                }
                break;

                case HubProtocolConstants.StreamInvocationMessageType:
                {
                    if (argumentsToken != null)
                    {
                        try
                        {
                            var paramTypes = binder.GetParameterTypes(target);
                            arguments = BindArguments(argumentsToken, paramTypes);
                        }
                        catch (Exception ex)
                        {
                            argumentBindingException = ExceptionDispatchInfo.Capture(ex);
                        }
                    }

                    message = BindStreamInvocationMessage(invocationId, target, argumentBindingException, arguments, hasArguments, binder);
                }
                break;

                case HubProtocolConstants.StreamItemMessageType:
                    if (itemToken != null)
                    {
                        var returnType = binder.GetReturnType(invocationId);
                        item = itemToken.ToObject(returnType, PayloadSerializer);
                    }

                    message = BindStreamItemMessage(invocationId, item, hasItem, binder);
                    break;

                case HubProtocolConstants.CompletionMessageType:
                    if (resultToken != null)
                    {
                        var returnType = binder.GetReturnType(invocationId);
                        result = resultToken.ToObject(returnType, PayloadSerializer);
                    }

                    message = BindCompletionMessage(invocationId, error, result, hasResult, binder);
                    break;

                case HubProtocolConstants.CancelInvocationMessageType:
                    message = BindCancelInvocationMessage(invocationId);
                    break;

                case HubProtocolConstants.PingMessageType:
                    return(PingMessage.Instance);

                case HubProtocolConstants.CloseMessageType:
                    return(BindCloseMessage(error));

                case null:
                    throw new InvalidDataException($"Missing required property '{TypePropertyName}'.");

                default:
                    throw new InvalidDataException($"Unknown message type: {type}");
                }

                return(ApplyHeaders(message, headers));
            }
            catch (JsonReaderException jrex)
            {
                throw new InvalidDataException("Error reading JSON.", jrex);
            }
        }