Beispiel #1
0
        /// <summary>
        /// Asynchronously sends a request payload and returns the response <see cref="Msg"/>.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="subject">The subject to publish <paramref name="data"/> to over
        /// the current connection.</param>
        /// <param name="data">The data to to publish to the connected NATS server.</param>
        /// <param name="timeout">Optional timeout in milliseconds.</param>
        /// <param name="token">Optional cancellation token.</param>
        /// <returns>A <see cref="Msg"/> with the response from the NATS server.</returns>
        /// <remarks>
        /// <para>
        /// NATS supports two flavors of request-reply messaging: point-to-point or one-to-many. Point-to-point
        /// involves the fastest or first to respond. In a one-to-many exchange, you set a limit on the number of
        /// responses the requestor may receive and instead must use a subscription (<see cref="ISubscription.AutoUnsubscribe(int)"/>).
        /// In a request-response exchange, publish request operation publishes a message with a reply subject expecting
        /// a response on that reply subject.
        /// </para>
        /// <para>
        /// This method will create an unique inbox for this request, sharing a single
        /// subscription for all replies to this <see cref="IConnection"/> instance. However, if
        /// <see cref="Options.UseOldRequestStyle"/> is set, each request will have its own underlying subscription.
        /// The old behavior is not recommended as it may cause unnecessary overhead on connected NATS servers.
        /// </para>
        /// </remarks>
        public static async Task <Msg <TResponse> > RequestAsync <TRequest, TResponse>(
            this IConnection connection,
            string subject,
            TRequest data,
            int timeout             = 0,
            CancellationToken token = default)

            where TRequest : class, IRoundtripData, new()
            where TResponse : class, IRoundtripData, new()
        {
            Covenant.Requires <ArgumentNullException>(data != null);

            Msg response;

            if (timeout == 0)
            {
                response = await connection.RequestAsync(subject, data.ToBytes(), token);
            }
            else
            {
                response = await connection.RequestAsync(subject, data.ToBytes(), timeout, token);
            }

            var payload = RoundtripDataFactory.CreateFrom <TResponse>(response.Data);

            return(new Msg <TResponse>(response.Subject, response.Reply, payload)
            {
                ArrivalSubscription = response.ArrivalSubcription
            });
        }
Beispiel #2
0
        /// <summary>
        /// Constructs an instance from a low-level <see cref="Msg"/>.
        /// </summary>
        /// <param name="msg">The low-level message.</param>
        internal Msg(Msg msg)
        {
            Covenant.Requires <ArgumentNullException>(msg != null, nameof(msg));

            this.subject = msg.Subject;
            this.reply   = msg.Reply;
            this.data    = RoundtripDataFactory.CreateFrom <TMessage>(msg.Data);
            this.sub     = msg.ArrivalSubscription;
            this.cached  = msg;
        }
Beispiel #3
0
        /// <inheritdoc/>
        public object[] FromDataArray(byte[] content, params Type[] valueTypes)
        {
            Covenant.Requires <ArgumentNullException>(valueTypes != null, nameof(valueTypes));

            if (valueTypes.Length == 0)
            {
                return(Array.Empty <object>());
            }

            Covenant.Requires <ArgumentException>(content.Length > 0, nameof(content));

            var jsonText  = Encoding.UTF8.GetString(content);
            var jsonLines = jsonText.Split(newlineArray, StringSplitOptions.RemoveEmptyEntries);

            if (jsonLines.Length != valueTypes.Length)
            {
                throw new ArgumentException($"Number of arguments [{jsonLines.Length}] passed does not match the method parameter count [{valueTypes.Length}].");
            }

            var output = new object[valueTypes.Length];

            for (int i = 0; i < valueTypes.Length; i++)
            {
                var type   = valueTypes[i];
                var line   = jsonLines[i];
                var jToken = JToken.Parse(line);

                if (type.Implements <IRoundtripData>())
                {
                    switch (jToken.Type)
                    {
                    case JTokenType.Null:

                        output[i] = null;
                        break;

                    case JTokenType.Object:

                        output[i] = RoundtripDataFactory.CreateFrom(type, (JObject)jToken);
                        break;

                    default:

                        Covenant.Assert(false, $"Unexpected JSON token [{jToken}].");
                        break;
                    }
                }
                else
                {
                    output[i] = NeonHelper.JsonDeserialize(type, line);
                }
            }

            return(output);
        }
Beispiel #4
0
        /// <inheritdoc/>
        public object[] FromDataArray(byte[] content, params Type[] valueTypes)
        {
            Covenant.Requires <ArgumentNullException>(content != null);
            Covenant.Requires <ArgumentNullException>(content.Length > 0);
            Covenant.Requires <ArgumentNullException>(valueTypes != null);

            var jToken = JToken.Parse(Encoding.UTF8.GetString(content));

            if (jToken.Type != JTokenType.Array)
            {
                throw new ArgumentException($"Content encodes a [{jToken.Type}] instead of the expected [{JTokenType.Array}].");
            }

            var jArray = (JArray)jToken;

            if (jArray.Count != valueTypes.Length)
            {
                throw new ArgumentException($"Content array length [{jArray.Count}] does not match the expected number of values [{valueTypes.Length}].");
            }

            var output = new object[valueTypes.Length];

            for (int i = 0; i < valueTypes.Length; i++)
            {
                var type = valueTypes[i];
                var item = jArray[i];

                if (type.Implements <IRoundtripData>())
                {
                    switch (item.Type)
                    {
                    case JTokenType.Null:

                        output[i] = null;
                        break;

                    case JTokenType.Object:

                        output[i] = RoundtripDataFactory.CreateFrom(type, (JObject)item);
                        break;

                    default:

                        throw new ArgumentException($"Unexpected [{item.Type}] in JSON array.  Only [{nameof(JTokenType.Object)}] or [{nameof(JTokenType.Null)}] are allowed.");
                    }
                }
                else
                {
                    output[i] = item.ToObject(type);
                }
            }

            return(output);
        }
Beispiel #5
0
        /// <summary>
        /// Sends a request payload and returns the response <see cref="Msg"/>.
        /// </summary>
        /// <param name="connection">The connection.</param>
        /// <param name="subject">The subject to publish <paramref name="data"/> to over
        /// the current connection.</param>
        /// <param name="data">The data to to publish to the connected NATS server.</param>
        /// <returns>A <see cref="Msg"/> with the response from the NATS server.</returns>
        /// <remarks>
        /// <para>
        /// NATS supports two flavors of request-reply messaging: point-to-point or one-to-many. Point-to-point
        /// involves the fastest or first to respond. In a one-to-many exchange, you set a limit on the number of
        /// responses the requestor may receive and instead must use a subscription (<see cref="ISubscription.AutoUnsubscribe(int)"/>).
        /// In a request-response exchange, publish request operation publishes a message with a reply subject expecting
        /// a response on that reply subject.
        /// </para>
        /// <para>
        /// This method will create an unique inbox for this request, sharing a single
        /// subscription for all replies to this <see cref="IConnection"/> instance. However, if
        /// <see cref="Options.UseOldRequestStyle"/> is set, each request will have its own underlying subscription.
        /// The old behavior is not recommended as it may cause unnecessary overhead on connected NATS servers.
        /// </para>
        /// </remarks>
        public static Msg <TResponse> Request <TRequest, TResponse>(this IConnection connection, string subject, TRequest data)
            where TRequest : class, IRoundtripData, new()
            where TResponse : class, IRoundtripData, new()
        {
            Covenant.Requires <ArgumentNullException>(data != null);

            var response = connection.Request(subject, data.ToBytes());
            var payload  = RoundtripDataFactory.CreateFrom <TResponse>(response.Data);

            return(new Msg <TResponse>(response.Subject, response.Reply, payload)
            {
                ArrivalSubscription = response.ArrivalSubcription
            });
        }
Beispiel #6
0
        /// <inheritdoc/>
        public object FromData(Type type, byte[] content)
        {
            Covenant.Requires <ArgumentNullException>(type != null);
            Covenant.Requires <ArgumentNullException>(content != null);
            Covenant.Requires <ArgumentNullException>(content.Length > 0);

            if (type.Implements <IRoundtripData>())
            {
                return(RoundtripDataFactory.CreateFrom(type, content));
            }
            else
            {
                return(NeonHelper.JsonDeserialize(type, content, strict: false));
            }
        }
Beispiel #7
0
        /// <inheritdoc/>
        public T FromData <T>(byte[] content)
        {
            Covenant.Requires <ArgumentNullException>(content != null);
            Covenant.Requires <ArgumentNullException>(content.Length > 0);

            var type = typeof(T);

            if (type.Implements <IRoundtripData>())
            {
                return((T)RoundtripDataFactory.CreateFrom(type, content));
            }
            else
            {
                return(NeonHelper.JsonDeserialize <T>(content, strict: false));
            }
        }
Beispiel #8
0
        /// <inheritdoc/>
        public override async Task <InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
        {
            var request = context.HttpContext.Request;

            if (request.Body == null)
            {
                return(await InputFormatterResult.SuccessAsync(null));
            }
            else
            {
                var result = await RoundtripDataFactory.TryCreateFromAsync(context.ModelType, request.Body, Encoding.UTF8);

                if (result.Item1)
                {
                    return(await InputFormatterResult.SuccessAsync(result.Item2));
                }
                else
                {
                    return(await InputFormatterResult.SuccessAsync(NeonHelper.JsonDeserialize(context.ModelType, Encoding.UTF8.GetString(await request.Body.ReadToEndAsync()))));
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Helper method for retrieving a complex property serialized as a JSON string.
        /// </summary>
        /// <typeparam name="T">The property type.</typeparam>
        /// <param name="key">The property key.</param>
        /// <returns>The parsed value if the property exists or <c>null</c>.</returns>
        /// <remarks>
        /// <note>
        /// <para>
        /// <b>IMPORTANT:</b> Be very careful when referencing properties that use this
        /// method because the behavior will probably be unexepected.  You should:
        /// </para>
        /// <list type="bullet">
        ///     <item>
        ///     When you need to access multiple subfields of the property value,
        ///     dereference the property once, save the value to a variable and
        ///     then use the variable to access the subproperty.  Not doing this
        ///     will result in the JSON being parsed again for each property
        ///     reference.
        ///     </item>
        ///     <item>
        ///     Dereferencing the property and changing a subproperty value won't
        ///     actually persist the change back to the underlying property.  You'll
        ///     need to dereference the property to a variable, change the subproperty,
        ///     and then use <see cref="SetJsonProperty{T}(PropertyNameUtf8, T)"/> to persist the
        ///     change.
        ///     </item>
        /// </list>
        /// <para>
        /// These restrictions are a bit odd but we're not actually expecting to
        /// be doing any of these things within the <b>cadence-client</b> code.
        /// </para>
        /// </note>
        /// </remarks>
        internal T GetJsonProperty <T>(PropertyNameUtf8 key)
            where T : class, new()
        {
            if (Properties.TryGetValue(key, out var value))
            {
                if (value == null)
                {
                    return(null);
                }

                if (typeof(T).Implements <IRoundtripData>())
                {
                    return(RoundtripDataFactory.CreateFrom <T>(JObject.Parse(value)));
                }
                else
                {
                    return(NeonHelper.JsonDeserialize <T>(value, strict: false));
                }
            }
            else
            {
                return(null);
            }
        }