Func <HttpClient, object[], Task> buildVoidTaskFuncForMethod(RestMethodInfo restMethod) { return(async(client, paramList) => { var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); var rq = factory(paramList); var resp = await client.SendAsync(rq); if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(resp, settings); } }); }
Func <HttpClient, object[], Task <T> > BuildTaskFuncForMethod <T>(RestMethodInfo restMethod) { var ret = BuildCancellableTaskFuncForMethod <T>(restMethod); return((client, paramList) => { if (restMethod.CancellationToken != null) { return ret(client, paramList.OfType <CancellationToken>().FirstOrDefault(), paramList); } return ret(client, CancellationToken.None, paramList); }); }
private void AddInterfaceHttpMethods(Type interfaceType, Dictionary <string, List <RestMethodInfo> > methods) { foreach (var methodInfo in interfaceType.GetMethods()) { var attrs = methodInfo.GetCustomAttributes(true); var hasHttpMethod = attrs.OfType <HttpMethodAttribute>().Any(); if (hasHttpMethod) { if (!methods.ContainsKey(methodInfo.Name)) { methods.Add(methodInfo.Name, new List <RestMethodInfo>()); } var restinfo = new RestMethodInfo(interfaceType, methodInfo, settings); methods[methodInfo.Name].Add(restinfo); } } }
Func <HttpClient, object[], IObservable <T> > buildRxFuncForMethod <T>(RestMethodInfo restMethod) { var taskFunc = buildCancellableTaskFuncForMethod <T>(restMethod); return((client, paramList) => { return new TaskToObservable <T>(ct => { var methodCt = CancellationToken.None; if (restMethod.CancellationToken != null) { methodCt = paramList.OfType <CancellationToken>().FirstOrDefault(); } // link the two var cts = CancellationTokenSource.CreateLinkedTokenSource(methodCt, ct); return taskFunc(client, cts.Token, paramList); }); }); }
Func <HttpClient, object[], Task> buildVoidTaskFuncForMethod(RestMethodInfo restMethod) { return(async(client, paramList) => { var factory = buildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = factory(paramList); var ct = CancellationToken.None; if (restMethod.CancellationToken != null) { ct = paramList.OfType <CancellationToken>().FirstOrDefault(); } using (var resp = await client.SendAsync(rq, ct).ConfigureAwait(false)) { if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(rq.RequestUri, restMethod.HttpMethod, resp, settings).ConfigureAwait(false); } } }); }
Func <HttpClient, object[], IObservable <T> > buildRxFuncForMethod <T>(RestMethodInfo restMethod) where T : class { var taskFunc = buildTaskFuncForMethod <T>(restMethod); return((client, paramList) => { var ret = new FakeAsyncSubject <T>(); taskFunc(client, paramList).ContinueWith(t => { if (t.Exception != null) { ret.OnError(t.Exception); } else { ret.OnNext(t.Result); ret.OnCompleted(); } }); return ret; }); }
Func <HttpClient, CancellationToken, object[], Task <T> > buildCancellableTaskFuncForMethod <T>(RestMethodInfo restMethod) { return(async(client, ct, paramList) => { var factory = buildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = factory(paramList); var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. return (T)(object)resp; } if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(rq.RequestUri, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false); } if (restMethod.SerializedReturnType == typeof(HttpContent)) { return (T)(object)resp.Content; } var ms = new MemoryStream(); using (var fromStream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) { await fromStream.CopyToAsync(ms, 4096, ct).ConfigureAwait(false); } var bytes = ms.ToArray(); var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); if (restMethod.SerializedReturnType == typeof(string)) { return (T)(object)content; } return JsonConvert.DeserializeObject <T>(content, settings.JsonSerializerSettings); }); }
Func <HttpClient, CancellationToken, object[], Task <T> > BuildCancellableTaskFuncForMethod <T>(RestMethodInfo restMethod) { return(async(client, ct, paramList) => { if (client.BaseAddress == null) { throw new InvalidOperationException("BaseAddress must be set on the HttpClient instance"); } var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = factory(paramList); HttpResponseMessage resp = null; var disposeResponse = true; try { resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { disposeResponse = false; // caller has to dispose // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. return (T)(object)resp; } if (!resp.IsSuccessStatusCode) { disposeResponse = false; throw await ApiException.Create(rq.RequestUri, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false); } if (restMethod.SerializedReturnType == typeof(HttpContent)) { disposeResponse = false; // caller has to clean up the content return (T)(object)resp.Content; } if (restMethod.SerializedReturnType == typeof(Stream)) { disposeResponse = false; // caller has to dispose return (T)(object)await resp.Content.ReadAsStreamAsync().ConfigureAwait(false); } using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var reader = new StreamReader(stream)) { if (restMethod.SerializedReturnType == typeof(string)) { return (T)(object)await reader.ReadToEndAsync().ConfigureAwait(false); } using (var jsonReader = new JsonTextReader(reader)) { return serializer.Deserialize <T>(jsonReader); } } } finally { // Ensure we clean up the request // Especially important if it has open files/streams rq.Dispose(); if (disposeResponse) { resp?.Dispose(); } } }); }
Func <object[], HttpRequestMessage> BuildRequestFactoryForMethod(RestMethodInfo restMethod, string basePath, bool paramsContainsCancellationToken) { return(paramList => { // make sure we strip out any cancelation tokens if (paramsContainsCancellationToken) { paramList = paramList.Where(o => o == null || o.GetType() != typeof(CancellationToken)).ToArray(); } var ret = new HttpRequestMessage { Method = restMethod.HttpMethod }; // set up multipart content MultipartFormDataContent multiPartContent = null; if (restMethod.IsMultipart) { multiPartContent = new MultipartFormDataContent("----MyGreatBoundary"); ret.Content = multiPartContent; } var urlTarget = (basePath == "/" ? string.Empty : basePath) + restMethod.RelativePath; var queryParamsToAdd = new List <KeyValuePair <string, string> >(); var headersToAdd = new Dictionary <string, string>(restMethod.Headers); for (var i = 0; i < paramList.Length; i++) { // if part of REST resource URL, substitute it in if (restMethod.ParameterMap.ContainsKey(i)) { urlTarget = Regex.Replace( urlTarget, "{" + restMethod.ParameterMap[i] + "}", settings.UrlParameterFormatter .Format(paramList[i], restMethod.ParameterInfoMap[i]) .Replace("/", "%2F"), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); continue; } // if marked as body, add to content if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item3 == i) { var streamParam = paramList[i] as Stream; var stringParam = paramList[i] as string; if (paramList[i] is HttpContent httpContentParam) { ret.Content = httpContentParam; } else if (streamParam != null) { ret.Content = new StreamContent(streamParam); } else if (stringParam != null) { ret.Content = new StringContent(stringParam); } else { switch (restMethod.BodyParameterInfo.Item1) { case BodySerializationMethod.UrlEncoded: ret.Content = new FormUrlEncodedContent(new FormValueDictionary(paramList[i])); break; case BodySerializationMethod.Json: var param = paramList[i]; switch (restMethod.BodyParameterInfo.Item2) { case false: ret.Content = new PushStreamContent((stream, _, __) => { using (var writer = new JsonTextWriter(new StreamWriter(stream))) { serializer.Serialize(writer, param); } }, "application/json"); break; case true: ret.Content = new StringContent( JsonConvert.SerializeObject(paramList[i], settings.JsonSerializerSettings), Encoding.UTF8, "application/json"); break; } break; } } continue; } // if header, add to request headers if (restMethod.HeaderParameterMap.ContainsKey(i)) { headersToAdd[restMethod.HeaderParameterMap[i]] = paramList[i]?.ToString(); continue; } // ignore nulls if (paramList[i] == null) { continue; } // for anything that fell through to here, if this is not // a multipart method, add the parameter to the query string if (!restMethod.IsMultipart) { var attr = restMethod.ParameterInfoMap[i].GetCustomAttribute <QueryAttribute>() ?? new QueryAttribute(); if (DoNotConvertToQueryMap(paramList[i])) { queryParamsToAdd.Add(new KeyValuePair <string, string>(restMethod.QueryParameterMap[i], settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]))); } else { foreach (var kvp in BuildQueryMap(paramList[i], attr.Delimiter)) { var path = !string.IsNullOrWhiteSpace(attr.Prefix) ? $"{attr.Prefix}{attr.Delimiter}{kvp.Key}" : kvp.Key; queryParamsToAdd.Add(new KeyValuePair <string, string>(path, settings.UrlParameterFormatter.Format(kvp.Value, restMethod.ParameterInfoMap[i]))); } } continue; } // we are in a multipart method, add the part to the content // the parameter name should be either the attachment name or the parameter name (as fallback) string itemName; string parameterName; if (!restMethod.AttachmentNameMap.TryGetValue(i, out var attachment)) { itemName = restMethod.QueryParameterMap[i]; parameterName = itemName; } else { itemName = attachment.Item1; parameterName = attachment.Item2; } // Check to see if it's an IEnumerable var itemValue = paramList[i]; var enumerable = itemValue as IEnumerable <object>; var typeIsCollection = false; if (enumerable != null) { Type tType = null; var eType = enumerable.GetType(); if (eType.GetTypeInfo().ContainsGenericParameters) { tType = eType.GenericTypeArguments[0]; } else if (eType.IsArray) { tType = eType.GetElementType(); } // check to see if it's one of the types we support for multipart: // FileInfo, Stream, string or byte[] if (tType == typeof(Stream) || tType == typeof(string) || tType == typeof(byte[]) || tType.GetTypeInfo().IsSubclassOf(typeof(MultipartItem)) || tType == typeof(FileInfo) ) { typeIsCollection = true; } } if (typeIsCollection) { foreach (var item in enumerable) { AddMultipartItem(multiPartContent, itemName, parameterName, item); } } else { AddMultipartItem(multiPartContent, itemName, parameterName, itemValue); } } // NB: We defer setting headers until the body has been // added so any custom content headers don't get left out. foreach (var header in headersToAdd) { SetHeader(ret, header.Key, header.Value); } // NB: The URI methods in .NET are dumb. Also, we do this // UriBuilder business so that we preserve any hardcoded query // parameters as well as add the parameterized ones. var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); var query = HttpUtility.ParseQueryString(uri.Query ?? ""); foreach (var key in query.AllKeys) { queryParamsToAdd.Insert(0, new KeyValuePair <string, string>(key, query[key])); } if (queryParamsToAdd.Any()) { var pairs = queryParamsToAdd.Select(x => HttpUtility.UrlEncode(x.Key) + "=" + HttpUtility.UrlEncode(x.Value)); uri.Query = string.Join("&", pairs); } else { uri.Query = null; } ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); return ret; }); }
Func <HttpClient, object[], Task <T> > buildTaskFuncForMethod <T>(RestMethodInfo restMethod) { var ret = buildCancellableTaskFuncForMethod <T>(restMethod); return((client, paramList) => ret(client, CancellationToken.None, paramList)); }
Func <HttpClient, object[], Task> BuildVoidTaskFuncForMethod(RestMethodInfo restMethod) { return(async(client, paramList) => { << << << < HEAD var factory = buildRequestFactoryForMethod(restMethod.Name, client.BaseAddress?.AbsolutePath, restMethod.CancellationToken != null);
Func <object[], Task <HttpRequestMessage> > BuildRequestFactoryForMethod(RestMethodInfo restMethod, string basePath, bool paramsContainsCancellationToken) { return(async paramList => { // make sure we strip out any cancelation tokens if (paramsContainsCancellationToken) { paramList = paramList.Where(o => o == null || o.GetType() != typeof(CancellationToken)).ToArray(); } var ret = new HttpRequestMessage { Method = restMethod.HttpMethod }; // set up multipart content MultipartFormDataContent multiPartContent = null; if (restMethod.IsMultipart) { multiPartContent = new MultipartFormDataContent("----MyGreatBoundary"); ret.Content = multiPartContent; } var urlTarget = (basePath == "/" ? string.Empty : basePath) + restMethod.RelativePath; var queryParamsToAdd = new List <KeyValuePair <string, string> >(); var headersToAdd = new Dictionary <string, string>(restMethod.Headers); for (var i = 0; i < paramList.Length; i++) { // if part of REST resource URL, substitute it in if (restMethod.ParameterMap.ContainsKey(i)) { urlTarget = Regex.Replace( urlTarget, "{" + restMethod.ParameterMap[i] + "}", Uri.EscapeDataString(settings.UrlParameterFormatter .Format(paramList[i], restMethod.ParameterInfoMap[i]) ?? string.Empty), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); continue; } // if marked as body, add to content if (restMethod.BodyParameterInfo != null && restMethod.BodyParameterInfo.Item3 == i) { if (paramList[i] is HttpContent httpContentParam) { ret.Content = httpContentParam; } else if (paramList[i] is Stream streamParam) { ret.Content = new StreamContent(streamParam); } // Default sends raw strings else if (restMethod.BodyParameterInfo.Item1 == BodySerializationMethod.Default && paramList[i] is string stringParam) { ret.Content = new StringContent(stringParam); } else { switch (restMethod.BodyParameterInfo.Item1) { case BodySerializationMethod.UrlEncoded: ret.Content = paramList[i] is string str ? (HttpContent) new StringContent(Uri.EscapeDataString(str), Encoding.UTF8, "application/x-www-form-urlencoded") : new FormUrlEncodedContent(new FormValueMultimap(paramList[i], settings)); break; case BodySerializationMethod.Default: #pragma warning disable CS0618 // Type or member is obsolete case BodySerializationMethod.Json: #pragma warning restore CS0618 // Type or member is obsolete case BodySerializationMethod.Serialized: var content = await serializer.SerializeAsync(paramList[i]).ConfigureAwait(false); switch (restMethod.BodyParameterInfo.Item2) { case false: ret.Content = new PushStreamContent( async(stream, _, __) => { using (stream) { await content.CopyToAsync(stream).ConfigureAwait(false); } }, content.Headers.ContentType); break; case true: ret.Content = content; break; } break; } } continue; } // if header, add to request headers if (restMethod.HeaderParameterMap.ContainsKey(i)) { headersToAdd[restMethod.HeaderParameterMap[i]] = paramList[i]?.ToString(); continue; } // ignore nulls if (paramList[i] == null) { continue; } // for anything that fell through to here, if this is not // a multipart method, add the parameter to the query string if (!restMethod.IsMultipart) { var attr = restMethod.ParameterInfoMap[i].GetCustomAttribute <QueryAttribute>() ?? new QueryAttribute(); if (DoNotConvertToQueryMap(paramList[i])) { if (paramList[i] is IEnumerable paramValues) { switch (attr.CollectionFormat) { case CollectionFormat.Multi: foreach (var paramValue in paramValues) { queryParamsToAdd.Add(new KeyValuePair <string, string>( restMethod.QueryParameterMap[i], settings.UrlParameterFormatter.Format(paramValue, restMethod.ParameterInfoMap[i]))); } continue; case CollectionFormat.Csv: case CollectionFormat.Ssv: case CollectionFormat.Tsv: case CollectionFormat.Pipes: var delimiter = attr.CollectionFormat == CollectionFormat.Csv ? "," : attr.CollectionFormat == CollectionFormat.Ssv ? " " : attr.CollectionFormat == CollectionFormat.Tsv ? "\t" : "|"; var formattedValues = paramValues .Cast <object>() .Select(v => settings.UrlParameterFormatter.Format(v, restMethod.ParameterInfoMap[i])); queryParamsToAdd.Add(new KeyValuePair <string, string>( restMethod.QueryParameterMap[i], string.Join(delimiter, formattedValues))); continue; } } queryParamsToAdd.Add(new KeyValuePair <string, string>(restMethod.QueryParameterMap[i], settings.UrlParameterFormatter.Format(paramList[i], restMethod.ParameterInfoMap[i]))); } else { foreach (var kvp in BuildQueryMap(paramList[i], attr.Delimiter)) { var path = !string.IsNullOrWhiteSpace(attr.Prefix) ? $"{attr.Prefix}{attr.Delimiter}{kvp.Key}" : kvp.Key; queryParamsToAdd.Add(new KeyValuePair <string, string>(path, settings.UrlParameterFormatter.Format(kvp.Value, restMethod.ParameterInfoMap[i]))); } } continue; } // we are in a multipart method, add the part to the content // the parameter name should be either the attachment name or the parameter name (as fallback) string itemName; string parameterName; if (!restMethod.AttachmentNameMap.TryGetValue(i, out var attachment)) { itemName = restMethod.QueryParameterMap[i]; parameterName = itemName; } else { itemName = attachment.Item1; parameterName = attachment.Item2; } // Check to see if it's an IEnumerable var itemValue = paramList[i]; var enumerable = itemValue as IEnumerable <object>; var typeIsCollection = enumerable != null; if (typeIsCollection) { foreach (var item in enumerable) { await AddMultipartItemAsync(multiPartContent, itemName, parameterName, item).ConfigureAwait(false); } } else { await AddMultipartItemAsync(multiPartContent, itemName, parameterName, itemValue).ConfigureAwait(false); } } // NB: We defer setting headers until the body has been // added so any custom content headers don't get left out. if (headersToAdd.Count > 0) { // We could have content headers, so we need to make // sure we have an HttpContent object to add them to, // provided the HttpClient will allow it for the method if (ret.Content == null && !bodylessMethods.Contains(ret.Method)) { ret.Content = new ByteArrayContent(new byte[0]); } foreach (var header in headersToAdd) { SetHeader(ret, header.Key, header.Value); } } // NB: The URI methods in .NET are dumb. Also, we do this // UriBuilder business so that we preserve any hardcoded query // parameters as well as add the parameterized ones. var uri = new UriBuilder(new Uri(new Uri("http://api"), urlTarget)); var query = HttpUtility.ParseQueryString(uri.Query ?? ""); foreach (var key in query.AllKeys) { queryParamsToAdd.Insert(0, new KeyValuePair <string, string>(key, query[key])); } if (queryParamsToAdd.Any()) { var pairs = queryParamsToAdd.Where(x => x.Key != null && x.Value != null) .Select(x => Uri.EscapeDataString(x.Key) + "=" + Uri.EscapeDataString(x.Value)); uri.Query = string.Join("&", pairs); } else { uri.Query = null; } ret.RequestUri = new Uri(uri.Uri.GetComponents(UriComponents.PathAndQuery, UriFormat.UriEscaped), UriKind.Relative); return ret; }); }
Func <HttpClient, CancellationToken, object[], Task <T> > BuildCancellableTaskFuncForMethod <T, TBody>(RestMethodInfo restMethod) { return(async(client, ct, paramList) => { if (client.BaseAddress == null) { throw new InvalidOperationException("BaseAddress must be set on the HttpClient instance"); } var factory = BuildRequestFactoryForMethod(restMethod, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = await factory(paramList).ConfigureAwait(false); HttpResponseMessage resp = null; HttpContent content = null; var disposeResponse = true; try { resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); content = resp.Content ?? new StringContent(string.Empty); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { disposeResponse = false; // caller has to dispose // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. return (T)(object)resp; } if (!resp.IsSuccessStatusCode) { disposeResponse = false; var exception = await ApiException.Create(rq, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false); if (restMethod.IsApiResponse) { return ApiResponse.Create <T>(resp, default(T), exception); } throw exception; } var serializedReturnType = restMethod.IsApiResponse ? restMethod.SerializedGenericArgument : restMethod.SerializedReturnType; if (serializedReturnType == typeof(HttpContent)) { disposeResponse = false; // caller has to clean up the content if (restMethod.IsApiResponse) { return ApiResponse.Create <T>(resp, content); } return (T)(object)content; } if (serializedReturnType == typeof(Stream)) { disposeResponse = false; // caller has to dispose var stream = (object)await content.ReadAsStreamAsync().ConfigureAwait(false); if (restMethod.IsApiResponse) { return ApiResponse.Create <T>(resp, stream); } return (T)stream; } if (serializedReturnType == typeof(string)) { using (var stream = await content.ReadAsStreamAsync().ConfigureAwait(false)) using (var reader = new StreamReader(stream)) { var str = (object)await reader.ReadToEndAsync().ConfigureAwait(false); if (restMethod.IsApiResponse) { return ApiResponse.Create <T>(resp, str); } return (T)str; } } var body = await serializer.DeserializeAsync <TBody>(content); if (restMethod.IsApiResponse) { return ApiResponse.Create <T>(resp, body); } // Unfortunate side-effect of having no 'class' or 'T : TBody' constraints. // However, we know that T must be the same as TBody because IsApiResponse != true so // this code is safe at runtime. return (T)(object)body; } finally { // Ensure we clean up the request // Especially important if it has open files/streams rq.Dispose(); if (disposeResponse) { resp?.Dispose(); content?.Dispose(); } } }); }
Func <HttpClient, CancellationToken, object[], Task <T> > BuildCancellableTaskFuncForMethod <T>(RestMethodInfo restMethod) { return(async(client, ct, paramList) => { var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = factory(paramList); var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. return (T)(object)resp; } if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(rq.RequestUri, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false); } if (restMethod.SerializedReturnType == typeof(HttpContent)) { return (T)(object)resp.Content; } if (restMethod.SerializedReturnType == typeof(Stream)) { return (T)(object)await resp.Content.ReadAsStreamAsync().ConfigureAwait(false); } using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) using (var reader = new StreamReader(stream)) { if (restMethod.SerializedReturnType == typeof(string)) { return (T)(object)await reader.ReadToEndAsync().ConfigureAwait(false); } using (var jsonReader = new JsonTextReader(reader)) { return serializer.Deserialize <T>(jsonReader); } } }); }
Func <HttpClient, CancellationToken, object[], Task <T> > BuildCancellableTaskFuncForMethod <T>(RestMethodInfo restMethod) { return(async(client, ct, paramList) => { var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath, restMethod.CancellationToken != null); var rq = factory(paramList); var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct).ConfigureAwait(false); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { // NB: This double-casting manual-boxing hate crime is the only way to make // this work without a 'class' generic constraint. It could blow up at runtime // and would be A Bad Idea if we hadn't already vetted the return type. return (T)(object)resp; } if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(rq.RequestUri, restMethod.HttpMethod, resp, restMethod.RefitSettings).ConfigureAwait(false); } if (restMethod.SerializedReturnType == typeof(HttpContent)) { return (T)(object)resp.Content; } var content = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); if (restMethod.SerializedReturnType == typeof(string)) { return (T)(object)content; } var deserializedObject = JsonConvert.DeserializeObject <T>(content, settings.JsonSerializerSettings); // enable message post-processor functionality on resulting object if implements interface, // so it is possible to access http response message even when resulting type // of the call is something more specific (e.g. to allow processing of response headers) if (deserializedObject is IHttpResponseMessagePostProcessor) { (deserializedObject as IHttpResponseMessagePostProcessor).PostProcessHttpResponseMessage(resp); } return deserializedObject; }); }
Func <HttpClient, CancellationToken, object[], Task <T> > buildCancellableTaskFuncForMethod <T>(RestMethodInfo restMethod) where T : class { return(async(client, ct, paramList) => { var factory = BuildRequestFactoryForMethod(restMethod.Name, client.BaseAddress.AbsolutePath); var rq = factory(paramList); var resp = await client.SendAsync(rq, HttpCompletionOption.ResponseHeadersRead, ct); if (restMethod.SerializedReturnType == typeof(HttpResponseMessage)) { return resp as T; } if (!resp.IsSuccessStatusCode) { throw await ApiException.Create(resp); } var ms = new MemoryStream(); var fromStream = await resp.Content.ReadAsStreamAsync(); await fromStream.CopyToAsync(ms, 4096, ct); var bytes = ms.ToArray(); var content = Encoding.UTF8.GetString(bytes, 0, bytes.Length); if (restMethod.SerializedReturnType == typeof(string)) { return content as T; } return JsonConvert.DeserializeObject <T>(content); }); }