/// <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 }); }
/// <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; }
/// <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); }
/// <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); }
/// <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 }); }
/// <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)); } }
/// <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)); } }
/// <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())))); } } }
/// <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); } }