C# Client Library
A C# Client Library for the AnalyzeRe REST API
API.Requests.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.IO;
6 using System.Linq;
7 using System.Net;
8 using System.Net.Sockets;
9 
10 using AnalyzeRe.APITypes;
11 using AnalyzeRe.Rest;
12 using AnalyzeRe.Utilities;
13 using RestSharp;
14 
15 namespace AnalyzeRe
16 {
18  public static partial class API
19  {
20 #if DEBUG
21  public static volatile bool RequestLogging = true;
24 #endif
25  #region OPTIONS
26  public static OptionsResponse Options(
33  IEnumerable<Parameter> requestParameters = null, int? timeout = null)
34  {
35  return RequestAndParse<OptionsResponse>("/", Method.OPTIONS,
36  new RequestParameters(requestParameters), timeout);
37  }
38  #endregion OPTIONS
39 
40  #region Explicit Get Methods
41  public static T Get<T>(
54  string id,
55  IEnumerable<Parameter> requestParameters = null,
56  int? timeout = null) where T : IAPIResource
57  {
58  ValidateResourceId(id);
59  string collectionName = GetCollectionName<T>();
60  return RequestAndParse<T>($"{collectionName}/{id}", Method.GET, requestParameters, timeout);
61  }
62 
63  // TODO: This isn't well tested yet (what if we pass a path with a different base URL?)
78  public static T GetUri<T>(
79  Uri href,
80  IEnumerable<Parameter> requestParameters = null,
81  int? timeout = null)
82  {
83  // TODO: What if the URL contains query parameters?
84  // How do we merge with GetOrPost requestParameters?
85  return RequestAndParse<T>(href.PathAndQuery, Method.GET, requestParameters, timeout);
86  }
87 
102  public static object GetUri(Type deserializeType,
103  Uri href,
104  IEnumerable<Parameter> requestParameters = null,
105  int? timeout = null)
106  {
107  return RequestAndParse(deserializeType, href.PathAndQuery, Method.GET,
108  requestParameters, timeout);
109  }
110  #endregion Explicit Get Methods
111 
112  #region GET Resource Collection
123  IEnumerable<Parameter> requestParameters = null,
124  string collectionNameOverride = null,
125  int? timeout = null)
126  where T : IAPIResource
127  {
128  string collectionName = DetermineCollectionNameAndBaseType(typeof(T),
129  collectionNameOverride, out Type baseType);
130 
131  if (timeout == null)
133  if (typeof(T) == baseType)
134  return RequestAndParse<CollectionResponse<T>>(collectionName,
135  Method.GET, requestParameters, timeout);
136  // If we have requested a list of some derived type, we must
137  // determine the run-time list type to query.
138  return (ICollectionResponse<T>)GetDerivedResourceList(typeof(T),
139  requestParameters, timeout.Value, collectionName, baseType);
140  }
141 
152  Type resourceType,
153  IEnumerable<Parameter> requestParameters = null,
154  string collectionNameOverride = null,
155  int? timeout = null)
156  {
157  string collectionName = DetermineCollectionNameAndBaseType(
158  resourceType, collectionNameOverride, out Type baseType);
159  return GetDerivedResourceList(resourceType, requestParameters,
160  timeout ?? DefaultRequestTimeoutCollections, collectionName, baseType);
161  }
162 
163  #region Private Helper Methods
164  // Challenge - type constraints allow this method to be called with types derived from the
165  // base type, but when the results come back (for instance for
166  // `getResourceListGenericHelper{CatXL}()`), the deserializer needs to be able to
167  // parse the json as a List of Layers, not a List of CatXL, so we need to actually
168  // discover the base type that defines the collection endpoint at which the objects
169  // can be retrieved. Calling a generic method with a type parameter determined at run-time
170  // requires the ugly reflection at the end of this implementation. This wouldn't be a
171  // problem if the deserializer type parameter was passed in as a run-time type (like the
172  // Microsoft serialization class) rather than a compile time generic method type parameter.
173  // TODO: These reflection operations can benefit from caching.
174  private static ICollectionResponse<IAPIResource> GetDerivedResourceList(
175  Type resourceType,
176  IEnumerable<Parameter> requestParameters,
177  int timeout,
178  string collectionName,
179  Type baseType)
180  {
181  if (resourceType != baseType)
182  {
183  // Try to determine a base resource type or interface that we can use to hold a
184  // list of server results.
185  if (baseType.ContainsGenericParameters) // TODO: Support something more elaborate
186  {
187  // This is a problem, we can't just instantiate something without generic
188  // arguments. We can attempt to fill them in from the constraints.
189  if (!baseType.IsInterface)
190  {
191  // This is a problem. We are about instantiate a sub-type with generic
192  // arguments. Even if these arguments are covariantly valid, this class
193  // can't be (because it's not an interface), so we won't be able to add
194  // elements to the list we're about to make, as covariance has to be
195  // supported all the way to the last generic argument to be able to store
196  // derived list elements. The solution is to guess at an interface
197  // implemented by this baseType that IS covariant. */
198 
199  // Get any interfaces that have generic parameters
200  // TODO: can we do better than this guess?
201  IEnumerable<Type> baseInterfaces = baseType.GetInterfaces()
202  .Where(i => i.ContainsGenericParameters);
203  // Pick the last applied interface with generic parameters.
204  baseType = baseInterfaces.Last().GetGenericTypeDefinition();
205  }
206  // Assuming there's only one generic argument, and assuming that its first
207  // generic parameter constraint is a covariantly valid interface parameter and
208  // matches the type the we got this interface from, this might work.
209  baseType = baseType.MakeGenericTypeFast(
210  baseType.GetGenericArguments()[0].GetGenericParameterConstraints()[0]);
211  // If we're lucky we now have an covariant base interface type for which
212  // elements of type T can be assigned.
213  }
214  }
215 
216  // TODO: Server behaviour is not consistent / well enough defined yet to use its
217  // type searching in an automated way.
218  /*/ Create the query parameter that (once supported by the server) will filter by type.
219  Parameter typeSearchParameter = new Parameter
220  {
221  Type = ParameterType.GetOrPost,
222  Name = "type",
223  Value = TypeResolver.GetTypeNameForAPIType(resourceType)
224  };
225  requestParameters = (requestParameters ?? new Parameter[] { })
226  .Concat(new[] { typeSearchParameter });
227  */
228 
229  // Execute the query on the server
230  IRestResponse queryResponse = ExecuteRestRequest(collectionName, Method.GET,
231  requestParameters, timeout);
232  // Parse the server response into a list of the base type
233  Type listOfBaseType = typeof(CollectionResponse<>).MakeGenericTypeFast(baseType);
235  ExtractRestResponse(queryResponse, listOfBaseType);
236 
237  // Filter result list to only items of type resourceType (which derive from baseType)
238  Type listOfResourceTypeType =
239  typeof(CollectionResponse<>).MakeGenericTypeFast(resourceType);
240  object targetTypeResult = Activator.CreateInstance(listOfResourceTypeType);
241  IList filteredResults = (IList)
242  Activator.CreateInstance(typeof(List<>).MakeGenericTypeFast(resourceType));
243 
244  // HACK: To support sub-type-specific searches (which currently isn't supported server
245  // side, we filter results to the specified type.
246  // TODO: It doesn't make much sense to include meta if we are cutting down the results
247  // to a specific derived type.
248  listOfResourceTypeType.GetProperty(nameof(result.meta))
249  .SetValue(targetTypeResult, result.meta, null);
250  foreach (IAPIResource item in result.items.Where(resourceType.IsInstanceOfType))
251  {
252  filteredResults.Add(item);
253  }
254  listOfResourceTypeType.GetProperty(nameof(result.items))
255  .SetValue(targetTypeResult, filteredResults, null);
256 
257  return (ICollectionResponse<IAPIResource>)targetTypeResult;
258  }
259 
260  // Helper method to resolve the resource collection name and the type that defined it.
261  private static string DetermineCollectionNameAndBaseType(
262  Type resourceType, string collectionNameOverride, out Type baseType)
263  {
264  baseType = resourceType;
265  string collectionName;
266  try
267  {
268  collectionName = collectionNameOverride ??
269  GetCollectionName(resourceType, out baseType);
270  }
271  catch (Exception e)
272  {
273  throw new ArgumentException(
274  "Could not resolve the collection name for this Resource, it must be " +
275  "provided manually via the 'collectionName' optional parameter.", e);
276  }
277  // If the defining type is generic, it is necessary to produce a
278  // covariant type which we can use to as a generic type
279  // parameter to an instantiable class wrapping this type.
280  if (baseType.IsGenericType)
281  baseType = TypeResolver.GetGenericCovariantBase(baseType);
282  return collectionName;
283  }
284  #endregion Private Helper Methods
285  #endregion GET Resource Collection
286 
287  #region Search Resource Collection
288  public enum SearchType
290  {
292  Basic,
295  Advanced
296  };
297 
316  string searchTerms,
317  IEnumerable<Parameter> requestParameters = null,
318  string collectionNameOverride = null,
319  int? timeout = null,
320  SearchType searchType = SearchType.Basic)
321  where T : IAPIResource
322  {
323  requestParameters = searchType == SearchType.Basic ?
324  Parameters.Search(searchTerms).AddParameters(requestParameters) :
325  Parameters.AdvancedSearch(searchTerms).AddParameters(requestParameters);
326  return GetResourceList<T>(requestParameters, collectionNameOverride, timeout);
327  }
328 
348  Type resourceType,
349  string searchTerms,
350  IEnumerable<Parameter> requestParameters = null,
351  string collectionNameOverride = null,
352  int? timeout = null,
353  SearchType searchType = SearchType.Basic)
354  {
355  requestParameters = searchType == SearchType.Basic ?
356  Parameters.Search(searchTerms).AddParameters(requestParameters) :
357  Parameters.AdvancedSearch(searchTerms).AddParameters(requestParameters);
358  return GetResourceList(resourceType, requestParameters, collectionNameOverride, timeout);
359  }
360  #endregion Search Resource Collection
361 
362  #region Batch Requests
363  public static ICollectionResponse<T> BatchGet<T>(
379  IEnumerable<string> ids,
380  IEnumerable<Parameter> requestParameters = null,
381  string collectionNameOverride = null,
382  int? timeout = null)
383  where T : IAPIResource
384  {
385  return GetResourceList<T>(Parameters.Ids(ids)
386  .AddParameters(Parameters.Page(0, ids.Count()))
387  .AddParameters(requestParameters),
388  collectionNameOverride, timeout);
389  }
390 
407  Type resourceType,
408  IEnumerable<string> ids,
409  IEnumerable<Parameter> requestParameters = null,
410  string collectionNameOverride = null,
411  int? timeout = null)
412  {
413  return GetResourceList(resourceType, Parameters.Ids(ids)
414  .AddParameters(Parameters.Page(0, ids.Count()))
415  .AddParameters(requestParameters),
416  collectionNameOverride, timeout);
417  }
418  #endregion Batch Requests
419 
420  #region Combined Request and Deserialization
421  public static T RequestAndParse<T>(
437  string resource,
438  Method method,
439  IEnumerable<Parameter> requestParameters = null,
440  int? timeout = null)
441  {
442  IRestResponse response = ExecuteRestRequest(resource, method, requestParameters, timeout);
443  return ExtractRestResponse<T>(response);
444  }
445 
463  public static object RequestAndParse(
464  Type deserializeType,
465  string resource,
466  Method method,
467  IEnumerable<Parameter> requestParameters = null,
468  int? timeout = null)
469  {
470  IRestResponse response = ExecuteRestRequest(resource, method, requestParameters, timeout);
471  return ExtractRestResponse(response, deserializeType);
472  }
473  #endregion Combined Request and Deserialization
474 
475  #region Basic REST Request
476  public static IRestResponse ExecuteRestRequest(
496  string resource,
497  Method method,
498  IEnumerable<Parameter> requestParameters = null,
499  int? timeout = null,
500  bool returnRawResponse = false,
501  Action<Stream> responseStreamReader = null)
502  {
503  int attempts = 1;
504  IRestResponse response;
505  DateTime? requestStartTime = null;
506  do
507  {
508 
509  RestRequest request = new RestRequest(resource, method);
510  // Add parameters (Cookies, HttpHeaders, GetOrPost Parameters, RequestBody...)
511  PrepareRequestParameters(ref request, requestParameters);
512  request.Timeout = timeout ?? DefaultRequestTimeout;
513 
514  // If the desired timeout exceeds the default ReadWriteTimeout (5 minutes),
515  // increase it as well.
516  if (request.Timeout > 300000)
517  request.ReadWriteTimeout = request.Timeout;
518 
519  // Set up the Stream reader if applicable
520  if (responseStreamReader != null)
521  request.ResponseWriter = responseStreamReader;
522 
523  // Execute the request
524  IRestClient client = GetRestClient();
525  if(requestStartTime == null)
526  requestStartTime = DateTime.UtcNow;
527  response = ExecuteRequest(client, request);
528 
529  // If configured not to check for HTTP status errors, return the response directly.
530  if (returnRawResponse)
531  return response;
532 
533  // If this gets set to true, the request may be retried
534  bool should_retry = false;
535 
536  // Determine whether we've been issued a redirect request.
537  string redirection = CheckForRedirect(response, client);
538  // Change the resource to the new location's relative path
539  if (redirection != null)
540  {
541  resource = redirection;
542  should_retry = true;
543  }
544 
545  // Returns TRUE if a temporary error was encountered and the request should be retried.
546  // Returns FALSE if the request completed successfully.
547  // Raises a meaningful exception if a permanent failure occurred.
548  should_retry = should_retry ||
549  CheckIncompleteResponse(response, requestStartTime.Value, attempts);
550 
551  // If the request did not raise any communication errors, we can return the response.
552  if (!should_retry)
553  return response;
554 
555  // In stream mode, we can't automatically retry the request (the stream has already been
556  // written to by the underlying RestSharp call and is tarnished) so we must raise an error.
557  if (responseStreamReader != null)
558  throw ServerErrorExceptionHelper(response, response.ErrorException, null,
559  "The initial request could not be completed due to a temporary " +
560  "communication error. Please reset the stream and try again. " +
561  "The error message was: " + response.ErrorMessage);
562 
563  // If nothing above threw an exception, and should_retry is true, then the initial
564  // request failed and the issue might be resolved by attempting the same request again.
565 
566  // This is not a standard feature of REST client libraries, but it
567  // reduces the overhead for client applications (like excel) by abstracting
568  // overarching configuration errors that would impact all individual requests.
569  } while (attempts++ <= MaxRequestRetries);
570 
571  // If we exceeded the maximum number of retries and never returned, raise an error.
572  throw ServerErrorExceptionHelper(response, response.ErrorException, null,
573  "The request encountered what appears to be a temporary error, " +
574  "but continues to fail after " + MaxRequestRetries + " consecutive attempts. " +
575  "The final error message was: " + response.ErrorMessage);
576  }
577 
578  private static IRestResponse ExecuteRequest(IRestClient client, IRestRequest request)
579  {
580 #if DEBUG
581  if (RequestLogging)
582  DebugRestRequest(request);
583 #endif
584  IRestResponse response = client.Execute(request);
585 #if DEBUG
586  // Only log the entire reset response if RequestLogging is turned on
587  // or if the response was an error.
588  if (RequestLogging || !response.IsSuccessful())
589  DebugRestResponse(response);
590 #endif
591  return response;
592  }
593  #endregion Basic REST Request
594 
595  #region IRestResponse deserialization
596  public static T ExtractRestResponse<T>(IRestResponse response)
605  {
606  return ExtractRestResponseHelper(response, typeof(T),
607  () => Converter.Deserialize<T>(response));
608  }
609 
618  public static object ExtractRestResponse(IRestResponse response, Type deserializeType)
619  {
620  return ExtractRestResponseHelper(response, deserializeType,
621  () => Converter.Deserialize(response.Content, deserializeType));
622  }
623  #endregion IRestResponse deserialization
624 
625  #region Private Helper Methods
626  #region Request Helpers
627  private static void PrepareRequestParameters(
634  ref RestRequest request,
635  IEnumerable<Parameter> requestParameters)
636  {
637  RequestParameters parameters = new RequestParameters(requestParameters);
638 
639  // If a global default authentication token has been configured, and no overriding
640  // Authorization header has been specified, add it to the request.
641  if (AuthenticationToken != null && !parameters.Any(p =>
642  p.Type == ParameterType.HttpHeader && p.Name.Equals(AUTH_HEADER)))
643  {
644  request.AddParameter(AUTH_HEADER, GetCurrentAuthorizationString(),
645  RestSharp.ParameterType.HttpHeader);
646  }
647 
648  // Hack: Rather than have a separate optional argument for a request body to be
649  // serialized to JSON, we allow the Rest body parameter to be passed in as an
650  // object (usually it can only be a string), then we serialize it to JSON for them.
651  IEnumerable<Parameter> requestBodyParameters =
652  parameters.Where(param => param.Type == ParameterType.RequestBody).ToList();
653  if (requestBodyParameters.Count() > 1)
654  throw new ArgumentException("More than one RequestBody parameter was supplied.");
655  Parameter requestBodyParameter = requestBodyParameters.FirstOrDefault();
656 
657  // Only serialize if we are sure it's meant to be JSON, but the body is not yet a string.
658  if (requestBodyParameter != null && requestBodyParameter.Name == JSON_CONTENT_TYPE &&
659  requestBodyParameter.Value.GetType() != typeof(string))
660  {
661  string serialized = Converter.Serialize(requestBodyParameter.Value);
662 
663  // Remove the RequestBody parameter now that we've attached it to the request.
664  parameters.Remove(requestBodyParameter);
665  parameters.Add(requestBodyParameter = new Parameter
666  {
667  Name = JSON_CONTENT_TYPE,
668  ContentType = JSON_CONTENT_TYPE,
669  Value = serialized,
670  Type = ParameterType.RequestBody
671  });
672  }
673 
674  // TODO: Experimental - zip the request body if it exceeds a certain size
675  //if (requestBodyParameter?.Value is string contentString && contentString.Length > 1)
676  //{
677  // // TODO: Either set the Content-Encoding
678  // request.AddParameter("Content-Encoding", "gzip", RestSharp.ParameterType.HttpHeader);
679  // // TODO: Or set the Content-Type (but probably not both)
680  // requestBodyParameter.Name = requestBodyParameter.ContentType = "application/gzip";
681 
682  // MemoryStream dataStream = new MemoryStream();
683  // using (GZipStream zipStream = new GZipStream(dataStream, CompressionLevel.Fastest))
684  // using (StreamWriter writer = new StreamWriter(zipStream))
685  // {
686  // writer.Write(contentString);
687  // }
688  // requestBodyParameter.Value = dataStream.ToArray();
689  //}
690 
691  request.Parameters.AddRange(parameters);
692  }
693  #endregion Request Helpers
694 
695  #region Response Helpers
696  private static string CheckForRedirect(IRestResponse response, IRestClient client)
702  {
703  // Determine whether we've been issued a redirect request.
704  bool redirected = (int)response.StatusCode >= 300 && (int)response.StatusCode < 400;
705  if (!redirected)
706  return null;
707 
708  string newLocation = response.Headers.FirstOrDefault(
709  h => h.Name.Equals("Location", StringComparison.OrdinalIgnoreCase))?.Value?.ToString();
710  string message = "The server returned a redirect status (" + response.StatusCode + ")";
711  Debug.WriteLine(message);
712  if (newLocation == null)
713  throw ServerErrorExceptionHelper(response, null, null, message +
714  ", but did specify a redirect location.");
715  // If the location is relative (presumably to the current host root), return it directly.
716  if (Uri.IsWellFormedUriString(newLocation, UriKind.Relative))
717  return newLocation;
718  // If the location is absolute, parse it
719  if (Uri.IsWellFormedUriString(newLocation, UriKind.Absolute))
720  {
721  Uri newUri = new Uri(newLocation, UriKind.Absolute);
722  // For now, assume it is unsafe to automatically redirect to a new host.
723  // The user will have to follow the redirect themselves manually if they trust it.
724  if (newUri.Host != client.BaseUrl.Host)
725  throw ServerErrorExceptionHelper(response, null, null, message +
726  ", but wants to switch to a new host URL (" + newUri.Host + "). " +
727  "You will have to manually reconfigure your client to use this " +
728  "new address if you trust it.");
729  // Return just the path (no host) to be used for the next request.
730  return newUri.AbsolutePath;
731  }
732  throw ServerErrorExceptionHelper(response, null, null, message +
733  ", but the redirect location was not well formed: " + newLocation);
734  }
735 
746  private static bool CheckIncompleteResponse(IRestResponse response,
747  DateTime requestStartTime, int sequentialAttempts)
748  {
749  // Check if there were any authentication errors, and handle them appropriately
750  // Will return should_retry=true if the initial request failed but we can attempt
751  // this same request again (either because new credentials have been supplied,
752  // or if a temporary SSL failure may have occurred).
753  if (TryHandleAuthenticationErrors(response, sequentialAttempts))
754  return true;
755 
756  // If the request completed successfully, we're done here
757  if (response.ResponseStatus == ResponseStatus.Completed)
758  return false;
759 
760  // If the request did not complete successfully, determine whether this is a temporary
761  // error (and return true to allow retry of the request) or raise a meaningful exception.
762 
763  // The following exception types occur sporadically and should be automatically retried:
764  if (response.ErrorException is IOException || response.ErrorException is SocketException)
765  return true;
766 
767  // Certain types of WebExceptions are also sporadic
768  if (response.ErrorException is WebException asWebException)
769  {
770  Debug.WriteLine("WebException Occurred with Status: " +
771  $"{asWebException.Status}\n{asWebException}");
772 
773  // Timeouts are permanent failures (since the user configures the timeout), but
774  // some timeouts don't raise a meaningful exception.
775  if (asWebException.Status == WebExceptionStatus.Timeout)
776  return SuppressTimeoutOrThrow(response, requestStartTime);
777 
778  // Sometimes a non-'timeout' error can occur because the timeout has elapsed.
779  // Detect these cases and treat them as a timeout.
780  // (See https://support.microsoft.com/en-us/help/2007873 )
781  // TODO: Such checks are culture (language) specific and may not work in other
782  // locales, Framework error messages might be translated.
783  const string socketTimeoutError = "A connection attempt failed because the " +
784  "connected party did not properly respond after a period of time, " +
785  "or established connection failed because connected host has failed to respond";
786  if ((asWebException.Status == WebExceptionStatus.KeepAliveFailure ||
787  asWebException.Status == WebExceptionStatus.ReceiveFailure) && (
788  asWebException.InnerException?.InnerException?.Message.Equals(
789  socketTimeoutError, StringComparison.CurrentCultureIgnoreCase) ?? false))
790  return SuppressTimeoutOrThrow(response, requestStartTime);
791 
792  // If the status is indicative of a temporary error, suggest a retry
793  if (TemporaryFailureStatuses.Contains(asWebException.Status))
794  return true;
795  }
796  else if (response.ErrorMessage.Contains("The operation has timed out"))
797  return SuppressTimeoutOrThrow(response, requestStartTime);
798  // For all other errors, return the message from the server.
799  throw ServerErrorExceptionHelper(response, response.ErrorException, null,
800  $"Server Communication Error - {response.ErrorMessage}");
801  }
802 
810  private static bool SuppressTimeoutOrThrow(IRestResponse response, DateTime requestStartTime)
811  {
812  TimeSpan elapsed = DateTime.UtcNow - requestStartTime;
813  string logPrefix = $"The request timed out after {elapsed.TotalMilliseconds} ms";
814 
815  // Occasionally, a low-level timeout is raised before the response object is created.
816  // This is likely not an "actual" timeout and can be retried.
817  if (response?.Request == null)
818  {
819  Debug.WriteLine($"{logPrefix}, but the request object is empty. " +
820  "This likely indicates that a low-level timeout occurred before " +
821  "the response could be instantiated. The request will be retried.");
822  return true;
823  }
824 
825  // If the timeout hasn't actually elapsed as far as we can tell, suggest a retry
826  if (response.Request.Timeout > elapsed.TotalMilliseconds)
827  {
828  Debug.WriteLine($"{logPrefix}, but the configured timeout period " +
829  $"({response.Request.Timeout} ms) hasn't elapsed. " +
830  "This likely indicates a timeout occurred in the underlying TCP protocol. " +
831  "The request will be retried.");
832  return true;
833  }
834 
835  // Otherwise, re-raise as a timeout exception since that is more meaningful.
836  Debug.WriteLine($"{logPrefix}. Re-raising the error returned (" +
837  $"{response.ErrorException.GetType().NiceTypeName()}) as a RequestTimeoutException.");
838  string message = $"{logPrefix}. The requested timeout was {response.Request.Timeout} ms. " +
839  "If this is a large request, you may need to wait longer for a response. " +
840  "Check your API.DefaultRequestTimeout or set a timeout directly in your API call.";
841  throw new RequestTimeoutException(message, response.ErrorException, response);
842  }
843 
854  private static T ExtractRestResponseHelper<T>(
855  IRestResponse response,
856  Type deserializeType,
857  Func<T> deserializeAction)
858  {
859  Exception parsingException = null;
860  if (response.IsSuccessful())
861  {
862  // See if the response can be parsed into the target object type.
863  try
864  {
865  T retVal;
866  // Response is empty, return null
867  if (response.StatusCode == HttpStatusCode.NoContent)
868  retVal = default(T);
869  // Special case: if user requests the response type of string,
870  // return the content directly without attempting to serialize it.
871  else if (deserializeType == typeof(string) || response.Content == null)
872  retVal = (T)Convert.ChangeType(response.Content, typeof(T));
873  // Similarly, if the user requests an IRestResponse result, return it directly.
874  else if (deserializeType.IsAssignableFrom(typeof(IRestResponse)))
875  retVal = (T)response;
876  // Otherwise, deserialize the content to the requested type
877  else
878  retVal = deserializeAction();
879 #if DEBUG
880  if (RequestLogging)
881  {
882  Debug.WriteLine("The response was successfully parsed and returned.\n");
883  }
884 #endif
885  return retVal;
886  }
887  catch (Exception e)
888  {
889  string parseMessage =
890  "The response could not be parsed into the desired type: \"" +
891  deserializeType.NiceTypeName() + "\"";
892 #if DEBUG
893  Debug.WriteLine(parseMessage + (RequestLogging ? "\n" : ""));
894 #endif
895  parsingException = new Exception(parseMessage, e);
896  }
897  }
898 #if DEBUG
899  // If the request failed and request info level logging is turned off, then the request
900  // that failed won't have been logged yet, so log it now at the warning level.
901  if (!RequestLogging)
902  {
903  Debug.WriteLine("The following request failed:");
904  DebugRestRequest(response.Request);
905  }
906 #endif
907 
908  // See if the response can be parsed into an error object.
909  ServerError serverError = ExtractErrorResponse(response, ref parsingException);
910 
911  // Throw an exception because the response was not successful.
912  // If we met a specific parsing exception, include it.
913  // If we parsed out a serverError object, include it.
914  throw ServerErrorExceptionHelper(response, parsingException, serverError);
915  }
916 
923  private static ServerError ExtractErrorResponse(
924  IRestResponse response,
925  ref Exception parsingException)
926  {
927  bool errorResponseExpected =
928  // Special case, we don't expect an error body for 503 responses
929  response.StatusCode != HttpStatusCode.ServiceUnavailable &&
930  // Otherwise, we expect non-successful responses to contain a ServerError
931  !response.IsSuccessful();
932 
933  try
934  {
935  // Handle empty responses upfront rather letting Deserialize throw an exception.
936  if (String.IsNullOrWhiteSpace(response.Content))
937  {
938  // If the response is empty and we aren't expecting an error object, return
939  if (!errorResponseExpected)
940  return null;
941  // Otherwise, complain that the response body was empty.
942  throw new ArgumentException(
943  "The server response was an error but no error details were supplied.");
944  }
945 
946  ServerError serverError = Converter.Deserialize<ServerError>(response);
947  if (serverError.message == null && serverError.traceback == null)
948  throw new ArgumentException("Response contain no ServerError properties.");
949 #if DEBUG
950  Debug.WriteLine("The response was received but contained a server error.");
951  if (RequestLogging) Debug.WriteLine("");
952 #endif
953  return serverError;
954  }
955  catch (Exception e)
956  {
957  // If we weren't expecting an error response, ignore the failed attempt to get it.
958  // This happens in scenarios where we fail to parse the successful response, but
959  // before returning that parsing exception, we want to make sure the server didn't
960  // actually return an error response object in spite of the successful status code.
961  if (!errorResponseExpected)
962  return null;
963  // If we were expecting an error response, and encountered an exception while
964  // trying to parse one out, return the parsing exception and log an error.
965  parsingException = e;
966 #if DEBUG
967  Debug.WriteLine("The response was received but could not be parsed.");
968  if (RequestLogging) Debug.WriteLine("");
969 #endif
970  return null;
971  }
972  }
973  #endregion Response Helpers
974  #endregion Private Helper Methods
975  }
976 }
static ICollectionResponse< T > SearchResourceList< T >(string searchTerms, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null, SearchType searchType=SearchType.Basic)
Get a collection of resources from the server that match the specified search criteria.
static RequestParameters Page(long offset, long limit)
When getting a collection of items, these parameters can be added to restrict the number of results y...
Helper class which makes it easier to build a set of request parameters.
static object ExtractRestResponse(IRestResponse response, Type deserializeType)
Attempts to deserialize an IRestResponse content to the specified run-time type
static T ExtractRestResponse< T >(IRestResponse response)
Attempts to deserialize an IRestResponse content to the specified type T
CollectionResponseMeta meta
The metadata associated with this collection get response.
string traceback
The server-provided error traceback.
Definition: ServerError.cs:17
static string GetCurrentAuthorizationString()
Return the Authorization header string formed by the current AuthenticationToken.
static ICollectionResponse< IAPIResource > GetResourceList(Type resourceType, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null)
Get a collection of resources from the server.
static RequestParameters Search(string searchTerms)
Can be added to your GET requests to send a search request to the server. If the endpoint supports se...
static object RequestAndParse(Type deserializeType, string resource, Method method, IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Perform a REST request on the server and serializes the response to the desired compile-time type...
SearchType
The search type determines the interpretation of the query string.
static bool TryHandleAuthenticationErrors(IRestResponse response, int sequentialAttempts=0)
Checks an IRestResponse object for authentication errors, then invokes configured authentication dele...
static RequestParameters Ids(IEnumerable< string > ids)
Can be added to your collection GET requests to return only the set of resources whose id matches one...
static T Get< T >(string id, IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Get the selected resource by ID. The appropriate collection is automatically determined by the resour...
Definition: API.Requests.cs:53
static ICollectionResponse< IAPIResource > SearchResourceList(Type resourceType, string searchTerms, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null, SearchType searchType=SearchType.Basic)
Get a collection of resources from the server that match the specified search criteria.
string message
The server-provided error message.
Definition: ServerError.cs:13
Used to deserialize server OPTIONS response.
static RequestParameters AdvancedSearch(string advancedSearchTerms)
Can be added to your GET requests to send a field filtering request to the server. If the endpoint supports data filtering, the results will be a filtered subset. Supports key/value search with logical operators. Currently supports the &#39;meta_data&#39; field.
static IRestResponse ExecuteRestRequest(string resource, Method method, IEnumerable< Parameter > requestParameters=null, int?timeout=null, bool returnRawResponse=false, Action< Stream > responseStreamReader=null)
Perform a REST request on the server.
static int DefaultRequestTimeoutCollections
The default timeout used when requesting a resource collection from the server, in milliseconds...
RequestParameters AddParameters(IEnumerable< Parameter > collection)
Adds the specified parameters to this list and returns the list.
static object GetUri(Type deserializeType, Uri href, IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Perform a GET on an arbitrary resource by extracting the path and query parameters from a manually sp...
static T RequestAndParse< T >(string resource, Method method, IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Perform a REST request on the server and serializes the response to the desired compile-time type...
static string GetCollectionName< T >()
Gets the collection name for the given APIResource type, whether it&#39;s instantiable or not...
static volatile int MaxRequestRetries
When a temporary communication failure occurs (such as a socket error or an authentication failure fo...
Parameters that can be added to your REST requests to access additional API features.
Used to deserialize server error messages.
Definition: ServerError.cs:8
static int DefaultRequestTimeout
The default timeout used for all simple server requests, in milliseconds.
Utility for resolving types given only the types name (Useful for parsing ambiguous JSON objects)...
Definition: TypeResolver.cs:13
static ICollectionResponse< T > GetResourceList< T >(IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null)
Get a collection of resources from the server.
static Type GetGenericCovariantBase(Type T)
Returns the most strongly typed covariant base type which is assignable from the specified type T...
static OptionsResponse Options(IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Perform an OPTIONS request on the API, which returns a list of features supported for the current aut...
Definition: API.Requests.cs:32
static ICollectionResponse< T > BatchGet< T >(IEnumerable< string > ids, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null)
Get a collection of resources from the server that match the set of resource ids specified.
Implements the reference entity interface, with support for resolving references using lazy loading...
static T GetUri< T >(Uri href, IEnumerable< Parameter > requestParameters=null, int?timeout=null)
Perform a GET on an arbitrary resource by extracting the path and query parameters from a manually sp...
Definition: API.Requests.cs:78
Extends the TimeoutException class to contain the IRestResponse that timed out.
static ICollectionResponse< IAPIResource > BatchGet(Type resourceType, IEnumerable< string > ids, IEnumerable< Parameter > requestParameters=null, string collectionNameOverride=null, int?timeout=null)
Get a collection of resources from the server that match the set of resource ids specified.
static IAccessToken AuthenticationToken
The AccessToken storing authentication information for requests made to the server.
Interface for Base class used by all resources.
Definition: IAPIResource.cs:8
static string GetCollectionName(Type requestType)
Gets the collection name for the given APIResource type, whether it&#39;s instantiable or not...