async Task<bool> RefreshConfigData() { // Get the latest config data: SHA1Hashed<JObject> config; try { // TODO: add HTTP fetch first and failover to file, like we did before. config = await FetchConfigDataFile(); } catch (Exception ex) { // Create a service collection that represents the parser error: this.services = SHA1Hashed.Create( new ServicesOffering( new ServiceCollection { Errors = new List<string> { "{0}".F(ex.Message) }, // Empty dictionary is easier than dealing with `null`: Services = new Dictionary<string, Service>(0, StringComparer.OrdinalIgnoreCase) }, (JObject)null ), SHA1Hashed.Zero ); return false; } Debug.Assert(config != null); // Parse the config object: var tmp = ParseConfigData(config.Value); Debug.Assert(tmp != null); // Store the new service collection paired with the JSON hash: this.services = SHA1Hashed.Create(new ServicesOffering(tmp, config.Value), config.Hash); return true; }
IHttpResponseAction errorsService(SHA1Hashed<ServicesOffering> main, Service service) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, serviceName = service.Name, serviceErrors = service.Errors, methodsErrors = service.Methods.Where(m => m.Value.Errors.Any()).ToDictionary( m => m.Key, m => new { errors = m.Value.Errors }, StringComparer.OrdinalIgnoreCase ) } ); }
IHttpResponseAction errorsAll(SHA1Hashed<ServicesOffering> main) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, rootErrors = main.Value.Services.Errors, servicesErrors = main.Value.Services.Services.Where(s => s.Value.Errors.Any() || s.Value.Methods.Any(m => m.Value.Errors.Any())).ToDictionary( s => s.Key, s => new { serviceErrors = s.Value.Errors, methodsErrors = s.Value.Methods.Where(m => m.Value.Errors.Any()).ToDictionary( m => m.Key, m => new { errors = m.Value.Errors }, StringComparer.OrdinalIgnoreCase ) }, StringComparer.OrdinalIgnoreCase ) } ); }
IHttpResponseAction configAll(SHA1Hashed<ServicesOffering> main) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, config = main.Value.Config } ); }
IHttpResponseAction errorsMethod(SHA1Hashed<ServicesOffering> main, Service service, Method method) { return new JsonRootResponse( links: new RestfulLink[] { }, meta: new { configHash = main.HashHexString, serviceName = service.Name, methodName = method.Name, methodErrors = method.Errors } ); }
IHttpResponseAction debugAll(SHA1Hashed<ServicesOffering> main) { // Report all service descriptors as links: return new JsonRootResponse( links: main.Value.Services.Services.Select(p => RestfulLink.Create(p.Key, "/debug/{0}".F(p.Key))).ToArray(), meta: new { configHash = main.HashHexString } ); }
IHttpResponseAction debugService(SHA1Hashed<ServicesOffering> main, Service service) { return new JsonRootResponse( links: new RestfulLink[] { RestfulLink.Create("self", "/debug/{0}".F(service.Name), "self"), RestfulLink.Create("parent", "/debug", "parent"), RestfulLink.Create("meta", "/meta/{0}".F(service.Name)), RestfulLink.Create("errors", "/errors/{0}".F(service.Name)) }, meta: new { configHash = main.HashHexString, service = new ServiceDebug(service) } ); }
IHttpResponseAction debugMethod(SHA1Hashed<ServicesOffering> main, Service service, Method method) { return new JsonRootResponse( links: new RestfulLink[] { RestfulLink.Create("self", "/debug/{0}/{1}".F(service.Name, method.Name), "self"), RestfulLink.Create("parent", "/debug/{0}".F(service.Name), "parent"), RestfulLink.Create("meta", "/meta/{0}/{1}".F(service.Name, method.Name)), RestfulLink.Create("errors", "/errors/{0}/{1}".F(service.Name, method.Name)), RestfulLink.Create("data", "/data/{0}/{1}".F(service.Name, method.Name)) }, meta: new { configHash = main.HashHexString, method = new MethodDebug(method) } ); }
async Task<IHttpResponseAction> dataMethod(SHA1Hashed<ServicesOffering> main, Method method, System.Collections.Specialized.NameValueCollection queryString) { // Check for descriptor errors: if (method.Errors.Count > 0) { return new JsonRootResponse( statusCode: 500, statusDescription: "Bad method descriptor", message: "Bad method descriptor", meta: new { configHash = main.HashHexString, serviceName = method.Service.Name, methodName = method.Name, errors = method.Errors.ToArray() } ); } // Check required parameters: if (method.Parameters != null) { // Create a hash set of the query-string parameter names: var q = new HashSet<string>(queryString.AllKeys, StringComparer.OrdinalIgnoreCase); // Create a list of missing required parameter names: var missingParams = new List<string>(method.Parameters.Count(p => !p.Value.IsOptional)); missingParams.AddRange( from p in method.Parameters where !p.Value.IsOptional && !q.Contains(p.Key) select p.Key ); if (missingParams.Count > 0) return new JsonRootResponse( statusCode: 400, statusDescription: "Missing required parameters", message: "Missing required parameters", meta: new { configHash = main.HashHexString, serviceName = method.Service.Name, methodName = method.Name }, errors: new[] { new { missingParams = missingParams.ToDictionary( p => p, p => new ParameterSerialized(method.Parameters[p]), StringComparer.OrdinalIgnoreCase ) } } ); missingParams = null; } // Open a connection and execute the command: using (var conn = new SqlConnection(method.ConnectionString)) using (var cmd = conn.CreateCommand()) { var parameterValues = new Dictionary<string, ParameterValue>(method.Parameters == null ? 0 : method.Parameters.Count); // Add parameters: if (method.Parameters != null) { foreach (var param in method.Parameters) { bool isValid = true; string message = null; object sqlValue, clrValue; var paramType = (param.Value.SqlType ?? param.Value.Type); string rawValue = queryString[param.Key]; if (param.Value.IsOptional & (rawValue == null)) { // Use the default value if the parameter is optional and is not specified on the query-string: sqlValue = param.Value.DefaultSQLValue; clrValue = param.Value.DefaultCLRValue; } else { try { sqlValue = getSqlValue(paramType.SqlDbType, rawValue); if (sqlValue == null) { isValid = false; message = "Unsupported SQL type '{0}'".F(paramType.SqlDbType); } } catch (Exception ex) { isValid = false; sqlValue = DBNull.Value; message = ex.Message; } try { clrValue = getCLRValue(paramType.SqlDbType, rawValue); } catch { clrValue = null; } } parameterValues.Add(param.Key, isValid ? new ParameterValue(clrValue) : new ParameterValue(message, rawValue)); // Add the SQL parameter: var sqlprm = cmd.Parameters.Add(param.Value.Name, paramType.SqlDbType); sqlprm.IsNullable = param.Value.IsOptional; if (paramType.Length != null) sqlprm.Precision = (byte)paramType.Length.Value; if (paramType.Scale != null) sqlprm.Scale = (byte)paramType.Scale.Value; sqlprm.SqlValue = sqlValue; } } // Abort if we have invalid parameters: var invalidParameters = parameterValues.Where(p => !p.Value.isValid); if (invalidParameters.Any()) { return new JsonRootResponse( statusCode: 400, statusDescription: "Invalid parameter value(s)", message: "Invalid parameter value(s)", meta: new { configHash = main.HashHexString, serviceName = method.Service.Name, methodName = method.Name }, errors: invalidParameters.Select(p => (object)new { name = p.Key, attemptedValue = p.Value.attemptedValue, message = p.Value.message }).ToArray() ); } //cmd.CommandTimeout = 360; // seconds cmd.CommandType = CommandType.Text; // Set TRANSACTION ISOLATION LEVEL and optionally ROWCOUNT before the query: const string setIsoLevel = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;\r\n"; var sbCmd = new StringBuilder(setIsoLevel, setIsoLevel.Length + method.Query.SQL.Length); //if (rowLimit > 0) // sbCmd.Append("SET ROWCOUNT {0};\r\n".F(rowLimit)); sbCmd.Append(method.Query.SQL); cmd.CommandText = sbCmd.ToString(); // Stopwatches used for precise timing: Stopwatch swOpenTime, swExecTime, swReadTime; swOpenTime = Stopwatch.StartNew(); try { // Open the connection asynchronously: await conn.OpenAsync(); swOpenTime.Stop(); } catch (Exception ex) { swOpenTime.Stop(); return getErrorResponse(ex); } // Execute the query: SqlDataReader dr; swExecTime = Stopwatch.StartNew(); try { // Execute the query asynchronously: dr = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.CloseConnection); swExecTime.Stop(); } catch (ArgumentException aex) { swExecTime.Stop(); // SQL Parameter validation only gives `null` for `aex.ParamName`. return new JsonRootResponse(400, aex.Message); } catch (Exception ex) { swExecTime.Stop(); return getErrorResponse(ex); } swReadTime = Stopwatch.StartNew(); try { var results = await ReadResult(method, dr, RowMapperUseMapping); swReadTime.Stop(); var meta = new MetadataSerialized { configHash = main.HashHexString, serviceName = method.Service.Name, methodName = method.Name, deprecated = method.DeprecatedMessage, parameters = parameterValues, // Timings are in msec: timings = new MetadataTimingsSerialized { open = Math.Round(swOpenTime.ElapsedTicks * 1000m / (decimal)Stopwatch.Frequency, 2), exec = Math.Round(swExecTime.ElapsedTicks * 1000m / (decimal)Stopwatch.Frequency, 2), read = Math.Round(swReadTime.ElapsedTicks * 1000m / (decimal)Stopwatch.Frequency, 2), total = Math.Round((swOpenTime.ElapsedTicks + swExecTime.ElapsedTicks + swReadTime.ElapsedTicks) * 1000m / (decimal)Stopwatch.Frequency, 2), } }; return new JsonRootResponse( links: new RestfulLink[] { }, meta: meta, results: results ); } catch (JsonResultException jex) { swReadTime.Stop(); return new JsonRootResponse(statusCode: jex.StatusCode, message: jex.Message); } catch (Exception ex) { swReadTime.Stop(); return getErrorResponse(ex); } } }
SHA1Hashed<JObject> ReadJSONStream(TextReader input) { #if TRACE // Send the JSON to Console.Out while it's being read: using (var tee = new TeeTextReader(input, (line) => Console.Write(line))) using (var sha1 = new SHA1TextReader(tee, UTF8.WithoutBOM)) #else using (var sha1 = new SHA1TextReader(input, UTF8.WithoutBOM)) #endif using (var jr = new JsonTextReader(sha1)) { // NOTE(jsd): Relying on parameter evaluation order for `sha1.GetHash()` to be correct. var result = new SHA1Hashed<JObject>(Json.Serializer.Deserialize<JObject>(jr), sha1.GetHash()); #if TRACE Console.WriteLine(); Console.WriteLine(); #endif return result; } }