/// <summary> /// Run a query against Nebulon ON /// </summary> /// <typeparam name="T">The Type of the target array</typeparam> /// <param name="name">Name of the query</param> /// <param name="parameters">Parameters for the query</param> /// <returns>List of items returned by the query</returns> public T[] RunQueryMany <T>(string name, GraphQLParameters parameters = null) where T : new() { try { // field types for objects are all marked with a property attribute // for all supported Neb* types. Every field that should be queried for // must be provided in the class properties as JsonPath attribute. string[] fields = GetQueryFields(typeof(T)); // this composes a new GraphQL method of type query. GraphQLMethod method = new GraphQLMethod( GraphQLMethodType.Query, name, parameters, fields ); // run the task object[] taskResults = Run <T>(method); // this is the most tricky part here as the type conversion from a generic // type to something else doesn't seem to be very reliable. However, // this approach seeps to work fine (according to Stack Overflow). return(CastObjects <T>(taskResults)); } catch (AggregateException errors) { string errorMessage = "Error executing query"; foreach (Exception err in errors.InnerExceptions) { errorMessage = string.Concat(errorMessage, ", ", err.Message); } Logger.WriteError(errorMessage); throw new Exception(errorMessage); } catch (TimeoutException err) { Logger.WriteError(err.Message); throw new Exception(err.Message); } catch (Exception err) { Logger.WriteError(err.Message); throw new Exception(err.Message); } }
/// <summary> /// Execute a GraphQL method against nebulon on. /// </summary> /// <typeparam name="T">Type of the object that we expect as a response</typeparam> /// <param name="method">GraphQL method</param> /// <returns></returns> private object[] Run <T>(GraphQLMethod method) where T : new() { // this could be made static as this URI should never really change. // the server doesn't end in a "/", but I think we should eventually // check for a malformed server. string uri = string.Concat(this._server, "/query"); Logger.WriteDebug(string.Format( "Sending '{0}' to {1}.", method, uri )); // prepare the request body. We need to make a JSON body // for UCPAI using JSON object with a single property "query". JObject jsonObject = new JObject(new JProperty(@"query", method.ToString())); string bodyString = JsonConvert.SerializeObject(jsonObject, Formatting.Indented); // we want to keep track of how long this request took, so we keep track // of the start time. If we later don't care about it anymore, we can // delete this. DateTime start = DateTime.UtcNow; HttpContent content = new StringContent( bodyString, Encoding.UTF8, @"application/json" ); content.Headers.Add("Nebulon-Client-App", _assemblyVersion); content.Headers.Add("Nebulon-Client-Platform", _platform); HttpResponseMessage response = Post(uri, content); string responseString = ReadResponseBodyString(response); // this is the earliest we should stop recording the time / duration of the // request as this is mostly now on the client side, not the server. double durationMs = (DateTime.UtcNow - start).TotalMilliseconds; Logger.WriteDebug(string.Format("Got response '{0}' in {1} ms.", response.StatusCode, durationMs)); Logger.WriteDebug(string.Concat("Response:", responseString)); // the UCAPI server response with JSON formatted data // with a well known structure. one property is "errors" and is // present when the request failed, and another property "data" // which is populated when the request succeeds. JObject json = JObject.Parse(responseString); // check for errors (any HTTP code outside of 2xx). We probably want to still // get the response body and bubble that up in the exception int statusCode = (int)response.StatusCode; if (200 < statusCode || statusCode >= 300 || json.ContainsKey("errors")) { string errorMessage = string.Format("Request error (HTTP {0})", statusCode); if (json.ContainsKey("errors")) { // get the errors that are specified in the response JSON // and add them to the error string. IEnumerable <JToken> errors = json.SelectTokens("errors[*].message", true); foreach (JToken error in errors) { // convert the JToken to a string and append it to the // error message string errorString = (string)error.ToObject(typeof(string)); errorMessage = string.Concat(errorMessage, ", ", errorString); } } throw new Exception(errorMessage); } // read data. This query could fail when the data is not present in the // response. So, we're checking for the value to not be null. string jsonPathData = string.Concat("$.data.", method.MethodName); JToken data = json.SelectToken(jsonPathData, false); if (data == null) { return(null); } if (data.Type != JTokenType.Array) { if (data.Type == JTokenType.Object) { JObject resultData = data.Value <JObject>(); return(new object[] { ObjectFromJObject(resultData, typeof(T)) }); } // Convert primitive types directly return(new object[] { data.Value <T>() }); } JArray dataArray = data.Value <JArray>(); List <object> resultDataArray = new List <object>(); foreach (JObject item in dataArray) { resultDataArray.Add(ObjectFromJObject(item, typeof(T))); } return(resultDataArray.ToArray()); }