// Copyright (c) ServiceStack, Inc. All Rights Reserved. // License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Reflection; using System.Threading; using ServiceStack.Auth; using ServiceStack.Logging; using ServiceStack.Messaging; using ServiceStack.Text; using ServiceStack.Web; #if !(__IOS__ || SL5) #endif #if SL5SendOneWay using ServiceStack.Text; #endif namespace ServiceStack { /** * Need to provide async request options * http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx */ public abstract class ServiceClientBase : IServiceClient, IMessageProducer, IHasCookieContainer, IServiceClientMeta { private static readonly ILog log = LogManager.GetLogger(typeof(ServiceClientBase)); private AuthenticationInfo authInfo = null; /// /// The request filter is called before any request. /// This request filter is executed globally. /// public static Action GlobalRequestFilter { get => globalRequestFilter; set { globalRequestFilter = value; AsyncServiceClient.GlobalRequestFilter = value; } } private static Action globalRequestFilter; /// /// The response action is called once the server response is available. /// It will allow you to access raw response information. /// This response action is executed globally. /// Note that you should NOT consume the response stream as this is handled by ServiceStack /// public static Action GlobalResponseFilter { get => globalResponseFilter; set { globalResponseFilter = value; AsyncServiceClient.GlobalResponseFilter = value; } } private static Action globalResponseFilter; /// /// Gets the collection of headers to be added to outgoing requests. /// public INameValueCollection Headers { get; private set; } public const string DefaultHttpMethod = HttpMethods.Post; public static string DefaultUserAgent = "ServiceStack .NET Client " + Env.ServiceStackVersion; readonly AsyncServiceClient asyncClient; protected ServiceClientBase() { this.HttpMethod = DefaultHttpMethod; this.Headers = PclExportClient.Instance.NewNameValueCollection(); asyncClient = new AsyncServiceClient { ContentType = ContentType, StreamSerializer = AsyncSerializeToStream, StreamDeserializer = AsyncDeserializeFromStream, UserName = this.UserName, Password = this.Password, RequestFilter = this.RequestFilter, ResponseFilter = this.ResponseFilter, ResultsFilter = this.ResultsFilter, Headers = this.Headers, }; this.CookieContainer = new CookieContainer(); this.StoreCookies = true; //leave this.UserAgent = DefaultUserAgent; asyncClient.HandleCallbackOnUiThread = this.HandleCallbackOnUiThread = true; asyncClient.ShareCookiesWithBrowser = this.ShareCookiesWithBrowser = true; JsConfig.InitStatics(); } protected ServiceClientBase(string syncReplyBaseUri, string asyncOneWayBaseUri) : this() { this.SyncReplyBaseUri = syncReplyBaseUri; this.AsyncOneWayBaseUri = asyncOneWayBaseUri; } /// /// Sets all baseUri properties, using the Format property for the SyncReplyBaseUri and AsyncOneWayBaseUri /// /// Base URI of the service public void SetBaseUri(string baseUri) { this.BaseUri = baseUri; this.asyncClient.BaseUri = baseUri; this.SyncReplyBaseUri = baseUri.WithTrailingSlash() + Format + "/reply/"; this.AsyncOneWayBaseUri = baseUri.WithTrailingSlash() + Format + "/oneway/"; } /// /// Whether to Accept Gzip,Deflate Content-Encoding and to auto decompress responses /// public bool DisableAutoCompression { get => disableAutoCompression; set { disableAutoCompression = value; asyncClient.DisableAutoCompression = value; } } private bool disableAutoCompression; public string RequestCompressionType { get; set; } /// /// The user name for basic authentication /// public string UserName { get => username; set { username = value; asyncClient.UserName = value; } } private string username; /// /// The password for basic authentication /// public string Password { get => password; set { password = value; asyncClient.Password = value; } } private string password; /// /// Sets the username and the password for basic authentication. /// public void SetCredentials(string userName, string password) { this.UserName = userName; this.Password = password; } /// /// The Authorization Bearer Token to send with this request /// public string BearerToken { get => bearerToken; set { bearerToken = value; asyncClient.BearerToken = value; } } private string bearerToken; public string BaseUri { get; set; } public abstract string Format { get; } public string SyncReplyBaseUri { get; set; } public string AsyncOneWayBaseUri { get; set; } private int version; public int Version { get => version; set { this.version = value; this.asyncClient.Version = value; } } public string SessionId { get; set; } public string UserAgent { get => userAgent; set { userAgent = value; asyncClient.UserAgent = value; } } private string userAgent; public TimeSpan? Timeout { get => this.timeout; set { this.timeout = value; this.asyncClient.Timeout = value; } } private TimeSpan? timeout; public TimeSpan? ReadWriteTimeout { get => this.readWriteTimeout; set { this.readWriteTimeout = value; // TODO implement ReadWriteTimeout in asyncClient //this.asyncClient.ReadWriteTimeout = value; } } private TimeSpan? readWriteTimeout; public virtual string Accept => ContentType; public abstract string ContentType { get; } public string HttpMethod { get; set; } /// /// Whether to execute async callbacks on the same Synchronization Context it was called from. /// public bool CaptureSynchronizationContext { get => asyncClient.CaptureSynchronizationContext; set => asyncClient.CaptureSynchronizationContext = value; } public bool HandleCallbackOnUiThread { get => asyncClient.HandleCallbackOnUiThread; set => asyncClient.HandleCallbackOnUiThread = value; } public bool EmulateHttpViaPost { get => asyncClient.EmulateHttpViaPost; set => asyncClient.EmulateHttpViaPost = value; } public ProgressDelegate OnDownloadProgress { get => asyncClient.OnDownloadProgress; set => asyncClient.OnDownloadProgress = value; } public ProgressDelegate OnUploadProgress { get => asyncClient.OnUploadProgress; set => asyncClient.OnUploadProgress = value; } private bool shareCookiesWithBrowser; public bool ShareCookiesWithBrowser { get => this.shareCookiesWithBrowser; set => asyncClient.ShareCookiesWithBrowser = this.shareCookiesWithBrowser = value; } #if !SL5 public IWebProxy Proxy { get; set; } #endif private ICredentials credentials; /// /// Gets or sets authentication information for the request. /// Warning: It's recommened to use and for basic auth. /// This property is only used for IIS level authentication. /// public ICredentials Credentials { get => this.credentials; set { this.credentials = value; this.asyncClient.Credentials = value; } } /// /// Determines if the basic auth header should be sent with every request. /// By default, the basic auth header is only sent when "401 Unauthorized" is returned. /// public bool AlwaysSendBasicAuthHeader { get => alwaysSendBasicAuthHeader; set => asyncClient.AlwaysSendBasicAuthHeader = alwaysSendBasicAuthHeader = value; } private bool alwaysSendBasicAuthHeader; /// /// Specifies if cookies should be stored /// public bool StoreCookies { get => storeCookies; set => asyncClient.StoreCookies = storeCookies = value; } private bool storeCookies; public CookieContainer CookieContainer { get => cookieContainer; set => asyncClient.CookieContainer = cookieContainer = value; } private CookieContainer cookieContainer; public bool AllowAutoRedirect { get => allowAutoRedirect; set => allowAutoRedirect = value; } private bool allowAutoRedirect = true; /// /// Called before request resend, when the initial request required authentication /// public Action OnAuthenticationRequired { get => onAuthenticationRequired; set { onAuthenticationRequired = value; asyncClient.OnAuthenticationRequired = value; } } private Action onAuthenticationRequired; /// /// If a request fails with a 401 Unauthorized and a BearerToken is present the client /// will automatically fetch a new AccessToken using this RefreshToken and retry the request /// public string RefreshToken { get => refreshToken; set { refreshToken = value; asyncClient.RefreshToken = value; } } private string refreshToken; /// /// Send the Request to get the AccessToken with the RefreshToken at a non-default location /// public string RefreshTokenUri { get => refreshTokenUri; set { refreshTokenUri = value; asyncClient.RefreshTokenUri = value; } } private string refreshTokenUri; /// /// The request filter is called before any request. /// This request filter only works with the instance where it was set (not global). /// public Action RequestFilter { get => requestFilter; set { requestFilter = value; asyncClient.RequestFilter = value; } } private Action requestFilter; /// /// The ResultsFilter is called before the Request is sent allowing you to return a cached response. /// public ResultsFilterDelegate ResultsFilter { get => resultsFilter; set { resultsFilter = value; asyncClient.ResultsFilter = value; } } private ResultsFilterDelegate resultsFilter; /// /// The ResultsFilterResponse is called before returning the response allowing responses to be cached. /// public ResultsFilterResponseDelegate ResultsFilterResponse { get => resultsFilterResponse; set { resultsFilterResponse = value; asyncClient.ResultsFilterResponse = value; } } private ResultsFilterResponseDelegate resultsFilterResponse; /// /// Called with requestUri, ResponseType when server returns 304 NotModified /// public ExceptionFilterDelegate ExceptionFilter { get => exceptionFilter; set { exceptionFilter = value; asyncClient.ExceptionFilter = value; } } private ExceptionFilterDelegate exceptionFilter; /// /// The response action is called once the server response is available. /// It will allow you to access raw response information. /// Note that you should NOT consume the response stream as this is handled by ServiceStack /// public Action ResponseFilter { get => responseFilter; set { responseFilter = value; asyncClient.ResponseFilter = value; } } private Action responseFilter; public UrlResolverDelegate UrlResolver { get; set; } public TypedUrlResolverDelegate TypedUrlResolver { get; set; } public virtual string ToAbsoluteUrl(string relativeOrAbsoluteUrl) { return relativeOrAbsoluteUrl.StartsWith("http:") || relativeOrAbsoluteUrl.StartsWith("https:") ? relativeOrAbsoluteUrl : this.BaseUri.CombineWith(relativeOrAbsoluteUrl); } public virtual string ResolveUrl(string httpMethod, string relativeOrAbsoluteUrl) { return ToAbsoluteUrl(UrlResolver?.Invoke(this, httpMethod, relativeOrAbsoluteUrl) ?? relativeOrAbsoluteUrl); } public virtual string ResolveTypedUrl(string httpMethod, object requestDto) { this.PopulateRequestMetadata(requestDto); return ToAbsoluteUrl(TypedUrlResolver?.Invoke(this, httpMethod, requestDto) ?? requestDto.ToUrl(httpMethod, Format)); } [Obsolete("Renamed to ToAbsoluteUrl")] public virtual string GetUrl(string relativeOrAbsoluteUrl) { return ToAbsoluteUrl(relativeOrAbsoluteUrl); } internal void AsyncSerializeToStream(IRequest requestContext, object request, Stream stream) { SerializeRequestToStream(request, stream); } public abstract void SerializeToStream(IRequest requestContext, object request, Stream stream); public abstract T DeserializeFromStream(Stream stream); public abstract StreamDeserializerDelegate StreamDeserializer { get; } internal object AsyncDeserializeFromStream(Type type, Stream fromStream) { return StreamDeserializer(type, fromStream); } protected T Deserialize(string text) { using (var ms = MemoryStreamFactory.GetStream(text.ToUtf8Bytes())) { return DeserializeFromStream(ms); } } public virtual List SendAll(IEnumerable requests) { var elType = requests.GetType().GetCollectionType(); var requestUri = this.SyncReplyBaseUri.WithTrailingSlash() + elType.Name + "[]"; var client = SendRequest(HttpMethods.Post, ResolveUrl(HttpMethods.Post, requestUri), requests); try { var webResponse = PclExport.Instance.GetResponse(client); return HandleResponse>(webResponse); } catch (Exception ex) { List response; if (!HandleResponseException(ex, requests, requestUri, () => SendRequest(HttpMethods.Post, requestUri, requests), c => PclExport.Instance.GetResponse(c), out response)) { throw; } return response; } } public virtual TResponse Send(object request) { if (typeof(TResponse) == typeof(object)) return (TResponse)this.Send(this.GetResponseType(request), request); if (request is IVerb) { if (request is IGet) return Get(request); if (request is IPost) return Post(request); if (request is IPut) return Put(request); if (request is IDelete) return Delete(request); if (request is IPatch) return Patch(request); } var httpMethod = HttpMethod ?? DefaultHttpMethod; var requestUri = ResolveUrl(httpMethod, UrlResolver == null ? this.SyncReplyBaseUri.WithTrailingSlash() + request.GetType().Name : Format + "/reply/" + request.GetType().Name); if (ResultsFilter != null) { var response = ResultsFilter(typeof(TResponse), httpMethod, requestUri, request); if (response is TResponse) return (TResponse)response; } var client = SendRequest(httpMethod, requestUri, request); try { var webResponse = PclExport.Instance.GetResponse(client); ApplyWebResponseFilters(webResponse); var response = GetResponse(webResponse); ResultsFilterResponse?.Invoke(webResponse, response, httpMethod, requestUri, request); DisposeIfRequired(webResponse); return response; } catch (Exception ex) { TResponse response; if (!HandleResponseException(ex, request, requestUri, () => SendRequest(HttpMethods.Post, requestUri, request), c => PclExport.Instance.GetResponse(c), out response)) { throw; } return response; } } /// /// Called by Send method if an exception occurs, for instance a System.Net.WebException because the server /// returned an HTTP error code. Override if you want to handle specific exceptions or always want to parse the /// response to a custom ErrorResponse DTO type instead of ServiceStack's ErrorResponse class. In case ex is a /// System.Net.WebException, do not use /// createWebRequest/getResponse/HandleResponse<TResponse> to parse the response /// because that will result in the same exception again. Use /// ThrowWebServiceException<YourErrorResponseType> to parse the response and to throw a /// WebServiceException containing the parsed DTO. Then override Send to handle that exception. /// protected virtual bool HandleResponseException(Exception ex, object request, string requestUri, Func createWebRequest, Func getResponse, out TResponse response) { var webEx = ex as WebException; try { if (WebRequestUtils.ShouldAuthenticate(webEx, (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) || credentials != null || bearerToken != null || refreshToken != null || OnAuthenticationRequired != null)) { if (RefreshToken != null) { var refreshRequest = new GetAccessToken { RefreshToken = RefreshToken }; var uri = this.RefreshTokenUri ?? this.BaseUri.CombineWith(refreshRequest.ToPostUrl()); GetAccessTokenResponse tokenResponse; try { tokenResponse = uri.PostJsonToUrl(refreshRequest).FromJson(); } catch (WebException refreshEx) { var webServiceEx = ToWebServiceException(refreshEx, stream => DeserializeFromStream(stream), ContentType); if (webServiceEx != null) throw new RefreshTokenException(webServiceEx); throw new RefreshTokenException(refreshEx.Message, refreshEx); } var accessToken = tokenResponse?.AccessToken; if (string.IsNullOrEmpty(accessToken)) throw new RefreshTokenException("Could not retrieve new AccessToken from: " + uri); var refreshClient = (HttpWebRequest) createWebRequest(); if (this.GetTokenCookie() != null) { this.SetTokenCookie(accessToken); refreshClient.CookieContainer.SetTokenCookie(BaseUri, accessToken); } else { refreshClient.AddBearerToken(this.BearerToken = accessToken); } var refreshResponse = getResponse(refreshClient); response = HandleResponse(refreshResponse); return true; } OnAuthenticationRequired?.Invoke(); var client = createWebRequest(); HandleAuthException(ex, client); var webResponse = getResponse(client); response = HandleResponse(webResponse); return true; } } catch (WebServiceException /*retrhow*/) { throw; } catch (Exception subEx) { // Since we are effectively re-executing the call, // the new exception should be shown to the caller rather // than the old one. // The new exception is either this one or the one thrown // by the following method. ThrowResponseTypeException(request, subEx, requestUri); throw; } if (ExceptionFilter != null && webEx?.Response != null) { var cachedResponse = ExceptionFilter(webEx, webEx.Response, requestUri, typeof(TResponse)); if (cachedResponse is TResponse) { response = (TResponse)cachedResponse; return true; } } // If this doesn't throw, the calling method // should rethrow the original exception upon // return value of false. ThrowResponseTypeException(request, ex, requestUri); response = default(TResponse); return false; } private void HandleAuthException(Exception ex, WebRequest client) { var webEx = ex as WebException; if (webEx?.Response != null) { var headers = ((HttpWebResponse)webEx.Response).Headers; var doAuthHeader = PclExportClient.Instance.GetHeader(headers, HttpHeaders.WwwAuthenticate, x => x.Contains("realm")); if (doAuthHeader == null) { client.AddBasicAuth(this.UserName, this.Password); } else { this.authInfo = new AuthenticationInfo(doAuthHeader); if (authInfo?.method == "basic" || authInfo?.method == "digest") { client.AddAuthInfo(this.UserName, this.Password, authInfo); } else if (UserName != null && Password != null) { client.AddBasicAuth(this.UserName, this.Password); } } } } readonly ConcurrentDictionary> ResponseHandlers = new ConcurrentDictionary>(); private void ThrowResponseTypeException(object request, Exception ex, string requestUri) { var responseType = WebRequestUtils.GetErrorResponseDtoType(request); Action responseHandler; if (!ResponseHandlers.TryGetValue(responseType, out responseHandler)) { var mi = GetType().GetInstanceMethod("ThrowWebServiceException") .MakeGenericMethod(new[] { responseType }); responseHandler = (Action)mi.CreateDelegate( typeof(Action), this); ResponseHandlers[responseType] = responseHandler; } responseHandler(ex, requestUri); } public static WebServiceException ToWebServiceException(WebException webEx, Func parseDtoFn, string contentType) { if (webEx?.Response != null #if !(SL5 || PCL || NETSTANDARD1_1) && webEx.Status == WebExceptionStatus.ProtocolError #endif ) { var errorResponse = (HttpWebResponse)webEx.Response; log.Error(webEx); if (log.IsDebugEnabled) { log.DebugFormat("Status Code : {0}", errorResponse.StatusCode); log.DebugFormat("Status Description : {0}", errorResponse.StatusDescription); } var serviceEx = new WebServiceException(errorResponse.StatusDescription) { StatusCode = (int)errorResponse.StatusCode, StatusDescription = errorResponse.StatusDescription, ResponseHeaders = errorResponse.Headers }; try { if (string.IsNullOrEmpty(errorResponse.ContentType) || errorResponse.ContentType.MatchesContentType(contentType)) { var bytes = errorResponse.GetResponseStream().ReadFully(); var stream = MemoryStreamFactory.GetStream(bytes); serviceEx.ResponseBody = bytes.FromUtf8Bytes(); serviceEx.ResponseDto = parseDtoFn?.Invoke(stream); if (stream.CanRead) stream.Dispose(); //alt ms throws when you dispose twice } else { serviceEx.ResponseBody = errorResponse.GetResponseStream().ToUtf8String(); } } catch (Exception innerEx) { // Oh, well, we tried return new WebServiceException(errorResponse.StatusDescription, innerEx) { StatusCode = (int)errorResponse.StatusCode, StatusDescription = errorResponse.StatusDescription, ResponseBody = serviceEx.ResponseBody }; } //Escape deserialize exception handling and return here return serviceEx; } return null; } public void ThrowWebServiceException(Exception ex, string requestUri) { var webEx = ToWebServiceException( ex as WebException, stream => DeserializeFromStream(stream), ContentType); if (webEx != null) throw webEx; var authEx = ex as AuthenticationException; if (authEx != null) { throw WebRequestUtils.CreateCustomException(requestUri, authEx); } } protected virtual WebRequest SendRequest(string httpMethod, string requestUri, object request) { return PrepareWebRequest(httpMethod, requestUri, request, client => { using (var requestStream = PclExport.Instance.GetRequestStream(client)) { SerializeRequestToStream(request, requestStream); } }); } protected virtual void SerializeRequestToStream(object request, Stream requestStream, bool keepOpen=false) { var str = request as string; var bytes = request as byte[]; var stream = request as Stream; if (str != null) { requestStream.Write(str); } else if (bytes != null) { requestStream.Write(bytes, 0, bytes.Length); } else if (stream != null) { stream.WriteTo(requestStream); } else { #if !SL5 if (RequestCompressionType == CompressionTypes.Deflate) { requestStream = new System.IO.Compression.DeflateStream(requestStream, System.IO.Compression.CompressionMode.Compress); } else if (RequestCompressionType == CompressionTypes.GZip) { requestStream = new System.IO.Compression.GZipStream(requestStream, System.IO.Compression.CompressionMode.Compress); } #endif SerializeToStream(null, request, requestStream); if (!keepOpen) { requestStream.Close(); } } } protected WebRequest PrepareWebRequest(string httpMethod, string requestUri, object request, Action sendRequestAction) { if (httpMethod == null) throw new ArgumentNullException(nameof(httpMethod)); this.PopulateRequestMetadata(request); if (!httpMethod.HasRequestBody() && request != null) { var queryString = QueryStringSerializer.SerializeToString(request); if (!string.IsNullOrEmpty(queryString)) { requestUri += "?" + queryString; } } var client = PclExport.Instance.CreateWebRequest(requestUri, emulateHttpViaPost: EmulateHttpViaPost); try { client.Accept = Accept; client.Method = httpMethod; PclExportClient.Instance.AddHeader(client, Headers); #if !SL5 if (Proxy != null) client.Proxy = Proxy; #endif PclExport.Instance.Config(client, allowAutoRedirect: AllowAutoRedirect, timeout: this.Timeout, readWriteTimeout: ReadWriteTimeout, userAgent: UserAgent); if (this.authInfo != null && !string.IsNullOrEmpty(this.UserName)) client.AddAuthInfo(this.UserName, this.Password, authInfo); else if (this.BearerToken != null) client.Headers[HttpHeaders.Authorization] = "Bearer " + this.BearerToken; else if (this.Credentials != null) client.Credentials = this.Credentials; else if (this.AlwaysSendBasicAuthHeader) client.AddBasicAuth(this.UserName, this.Password); if (!DisableAutoCompression) { PclExport.Instance.AddCompression(client); } if (StoreCookies) { PclExportClient.Instance.SetCookieContainer(client, this); } ApplyWebRequestFilters(client); if (httpMethod.HasRequestBody()) { client.ContentType = ContentType; if (RequestCompressionType != null) client.Headers[HttpHeaders.ContentEncoding] = RequestCompressionType; sendRequestAction?.Invoke(client); } } catch (AuthenticationException ex) { throw WebRequestUtils.CreateCustomException(requestUri, ex) ?? ex; } return client; } private void ApplyWebResponseFilters(WebResponse webResponse) { if (!(webResponse is HttpWebResponse)) return; ResponseFilter?.Invoke((HttpWebResponse)webResponse); GlobalResponseFilter?.Invoke((HttpWebResponse)webResponse); } private void ApplyWebRequestFilters(HttpWebRequest client) { RequestFilter?.Invoke(client); GlobalRequestFilter?.Invoke(client); } private byte[] DownloadBytes(string httpMethod, string requestUri, object request) { var webRequest = SendRequest(httpMethod, requestUri, request); using (var response = PclExport.Instance.GetResponse(webRequest)) { ApplyWebResponseFilters(response); using (var stream = response.GetResponseStream()) return stream.ReadFully(); } } public virtual void Publish(object requestDto) { SendOneWay(requestDto); } public void PublishAll(IEnumerable requests) { var elType = requests.GetType().GetCollectionType(); var requestUri = this.AsyncOneWayBaseUri.WithTrailingSlash() + elType.Name + "[]"; SendOneWay(HttpMethods.Post, ResolveUrl(HttpMethods.Post, requestUri), requests); } public void Publish(T requestDto) { SendOneWay(requestDto); } public void Publish(IMessage message) { var requestDto = message.GetBody(); if (message.CreatedDate != default(DateTime)) Headers.Set("X-CreatedDate", message.CreatedDate.ToJsv()); if (message.Priority != default(int)) Headers.Set("X-Priority", message.Priority.ToString()); if (message.RetryAttempts != default(int)) Headers.Set("X-RetryAttempts", message.RetryAttempts.ToString()); if (message.ReplyId != null) Headers.Set("X-ReplyId", message.ReplyId.Value.ToString()); if (message.ReplyTo != null) Headers.Set("X-ReplyTo", message.ReplyTo); if (message.Tag != null) Headers.Set("X-Tag", message.Tag); SendOneWay(requestDto); } public static string GetExplicitMethod(object request) { if (!(request is IVerb)) return null; return request is IGet ? HttpMethods.Get : request is IPost ? HttpMethods.Post : request is IPut ? HttpMethods.Put : request is IDelete ? HttpMethods.Delete : request is IPatch ? HttpMethods.Patch : null; } public virtual void SendOneWay(object request) { var requestUri = this.AsyncOneWayBaseUri.WithTrailingSlash() + request.GetType().Name; var httpMethod = GetExplicitMethod(request) ?? HttpMethod ?? DefaultHttpMethod; SendOneWay(httpMethod, ResolveUrl(httpMethod, requestUri), request); } public virtual void SendOneWay(string relativeOrAbsoluteUrl, object request) { var httpMethod = GetExplicitMethod(request) ?? HttpMethod ?? DefaultHttpMethod; SendOneWay(httpMethod, ResolveUrl(httpMethod, relativeOrAbsoluteUrl), request); } public virtual void SendAllOneWay(IEnumerable requests) { PublishAll(requests); } public virtual void SendOneWay(string httpMethod, string relativeOrAbsoluteUrl, object requestDto) { var requestUri = ToAbsoluteUrl(relativeOrAbsoluteUrl); try { DownloadBytes(httpMethod, requestUri, requestDto); } catch (Exception ex) { HttpWebResponse response; if (!HandleResponseException( ex, requestDto, requestUri, () => SendRequest(httpMethod, requestUri, requestDto), c => PclExport.Instance.GetResponse(c), out response)) { throw; } using (response) { } //auto dispose } } public virtual Task SendAsync(object request, CancellationToken token) { if (typeof(TResponse) == typeof(object)) return this.SendAsync(this.GetResponseType(request), request, token) .ContinueWith(t => (TResponse)t.Result, token); if (request is IVerb) { if (request is IGet) return GetAsync(request); if (request is IPost) return PostAsync(request); if (request is IPut) return PutAsync(request); if (request is IDelete) return DeleteAsync(request); if (request is IPatch) return PatchAsync(request); } var httpMethod = HttpMethod ?? DefaultHttpMethod; var requestUri = ResolveUrl(httpMethod, UrlResolver == null ? this.SyncReplyBaseUri.WithTrailingSlash() + request.GetType().Name : Format + "/reply/" + request.GetType().Name); return asyncClient.SendAsync(httpMethod, requestUri, request, token); } public Task> SendAllAsync(IEnumerable requests, CancellationToken token) { var elType = requests.GetType().GetCollectionType(); var requestUri = this.SyncReplyBaseUri.WithTrailingSlash() + elType.Name + "[]"; return asyncClient.SendAsync>(HttpMethods.Post, ResolveUrl(HttpMethods.Post, requestUri), requests, token); } public Task PublishAsync(object request, CancellationToken token) { var requestUri = this.AsyncOneWayBaseUri.WithTrailingSlash() + request.GetType().Name; return asyncClient.SendAsync(HttpMethods.Post, ResolveUrl(HttpMethods.Post, requestUri), request, token); } public Task PublishAllAsync(IEnumerable requests, CancellationToken token) { var elType = requests.GetType().GetCollectionType(); var requestUri = this.AsyncOneWayBaseUri.WithTrailingSlash() + elType.Name + "[]"; return asyncClient.SendAsync(HttpMethods.Post, ResolveUrl(HttpMethods.Post, requestUri), requests, token); } public virtual Task SendAsync(object request) { return SendAsync(request, default(CancellationToken)); } public Task SendAsync(string httpMethod, string absoluteUrl, object request, CancellationToken token = default(CancellationToken)) { return asyncClient.SendAsync(httpMethod, absoluteUrl, request, token); } public virtual Task GetAsync(IReturn requestDto) { return asyncClient.SendAsync(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } public virtual Task GetAsync(object requestDto) { return asyncClient.SendAsync(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } public virtual Task GetAsync(string relativeOrAbsoluteUrl) { return asyncClient.SendAsync(HttpMethods.Get, ResolveUrl(HttpMethods.Get, relativeOrAbsoluteUrl), null); } public virtual Task GetAsync(IReturnVoid requestDto) { return asyncClient.SendAsync(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } public virtual Task DeleteAsync(IReturn requestDto) { return asyncClient.SendAsync(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual Task DeleteAsync(object requestDto) { return asyncClient.SendAsync(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual Task DeleteAsync(string relativeOrAbsoluteUrl) { return asyncClient.SendAsync(HttpMethods.Delete, ResolveUrl(HttpMethods.Delete, relativeOrAbsoluteUrl), null); } public virtual Task DeleteAsync(IReturnVoid requestDto) { return asyncClient.SendAsync(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual Task PostAsync(IReturn requestDto) { return asyncClient.SendAsync(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual Task PostAsync(object requestDto) { return asyncClient.SendAsync(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual Task PostAsync(string relativeOrAbsoluteUrl, object request) { return asyncClient.SendAsync(HttpMethods.Post, ResolveUrl(HttpMethods.Post, relativeOrAbsoluteUrl), request); } public virtual Task PostAsync(IReturnVoid requestDto) { return asyncClient.SendAsync(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual Task PutAsync(IReturn requestDto) { return asyncClient.SendAsync(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual Task PutAsync(object requestDto) { return asyncClient.SendAsync(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual Task PutAsync(string relativeOrAbsoluteUrl, object request) { return asyncClient.SendAsync(HttpMethods.Put, ResolveUrl(HttpMethods.Put, relativeOrAbsoluteUrl), request); } public virtual Task PutAsync(IReturnVoid requestDto) { return asyncClient.SendAsync(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual Task PatchAsync(IReturn requestDto) { return asyncClient.SendAsync(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual Task PatchAsync(object requestDto) { return asyncClient.SendAsync(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual Task PatchAsync(string relativeOrAbsoluteUrl, object request) { return asyncClient.SendAsync(HttpMethods.Patch, ResolveUrl(HttpMethods.Patch, relativeOrAbsoluteUrl), request); } public virtual Task PatchAsync(IReturnVoid requestDto) { return asyncClient.SendAsync(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual Task CustomMethodAsync(string httpVerb, IReturn requestDto) { return CustomMethodAsync(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestDto); } public virtual Task CustomMethodAsync(string httpVerb, object requestDto) { return CustomMethodAsync(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestDto); } public virtual Task CustomMethodAsync(string httpVerb, string relativeOrAbsoluteUrl, object request) { if (!HttpMethods.HasVerb(httpVerb)) throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb); var requestBody = httpVerb.HasRequestBody() ? request : null; return asyncClient.SendAsync(httpVerb, ResolveUrl(httpVerb, relativeOrAbsoluteUrl), requestBody); } public virtual Task CustomMethodAsync(string httpVerb, IReturnVoid requestDto) { if (!HttpMethods.HasVerb(httpVerb)) throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb); var requestBody = httpVerb.HasRequestBody() ? requestDto : null; return asyncClient.SendAsync(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestBody); } public virtual void CancelAsync() { asyncClient.CancelAsync(); } public virtual TResponse Send(string httpMethod, string relativeOrAbsoluteUrl, object request) { var requestUri = ToAbsoluteUrl(relativeOrAbsoluteUrl); if (ResultsFilter != null) { var response = ResultsFilter(typeof(TResponse), httpMethod, requestUri, request); if (response is TResponse) return (TResponse)response; } var client = SendRequest(httpMethod, requestUri, request); try { var webResponse = PclExport.Instance.GetResponse(client); ApplyWebResponseFilters(webResponse); var response = GetResponse(webResponse); ResultsFilterResponse?.Invoke(webResponse, response, httpMethod, requestUri, request); DisposeIfRequired(webResponse); return response; } catch (Exception ex) { TResponse response; if (!HandleResponseException( ex, request, requestUri, () => SendRequest(httpMethod, requestUri, request), c => PclExport.Instance.GetResponse(c), out response)) { throw; } return response; } } public void AddHeader(string name, string value) { Headers[name] = value; } public void ClearCookies() { CookieContainer = new CookieContainer(); } public Dictionary GetCookieValues() { return CookieContainer.ToDictionary(BaseUri); } public void SetCookie(string name, string value, TimeSpan? expiresIn = null) { this.SetCookie(new Uri(BaseUri), name, value, expiresIn != null ? DateTime.UtcNow.Add(expiresIn.Value) : (DateTime?)null); } public virtual void Get(IReturnVoid requestDto) { Send(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } /// /// APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } /// public virtual HttpWebResponse Get(object requestDto) { return Send(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } /// /// APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } /// public virtual HttpWebResponse Get(string relativeOrAbsoluteUrl) { return Send(HttpMethods.Get, ResolveUrl(HttpMethods.Get, relativeOrAbsoluteUrl), null); } public virtual TResponse Get(IReturn requestDto) { return Send(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } public virtual TResponse Get(object requestDto) { return Send(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, requestDto), null); } public virtual TResponse Get(string relativeOrAbsoluteUrl) { return Send(HttpMethods.Get, ResolveUrl(HttpMethods.Get, relativeOrAbsoluteUrl), null); } public virtual IEnumerable GetLazy(IReturn> queryDto) { var query = (IQuery)queryDto; if (query.Include == null || query.Include.IndexOf("total", StringComparison.OrdinalIgnoreCase) >= 0) { if (!string.IsNullOrEmpty(query.Include)) query.Include += ","; query.Include += "Total"; } QueryResponse response; do { response = Send>(HttpMethods.Get, ResolveTypedUrl(HttpMethods.Get, queryDto), null); foreach (var result in response.Results) { yield return result; } query.Skip = query.Skip.GetValueOrDefault(0) + response.Results.Count; } while (response.Results.Count + response.Offset < response.Total); } public virtual void Delete(IReturnVoid requestDto) { Send(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual HttpWebResponse Delete(object requestDto) { return Send(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual HttpWebResponse Delete(string relativeOrAbsoluteUrl) { return Send(HttpMethods.Delete, ResolveUrl(HttpMethods.Delete, relativeOrAbsoluteUrl), null); } public virtual TResponse Delete(IReturn requestDto) { return Send(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual TResponse Delete(object requestDto) { return Send(HttpMethods.Delete, ResolveTypedUrl(HttpMethods.Delete, requestDto), null); } public virtual TResponse Delete(string relativeOrAbsoluteUrl) { return Send(HttpMethods.Delete, ResolveUrl(HttpMethods.Delete, relativeOrAbsoluteUrl), null); } public virtual void Post(IReturnVoid requestDto) { Send(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual HttpWebResponse Post(object requestDto) { return Send(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual TResponse Post(IReturn requestDto) { return Send(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual TResponse Post(object requestDto) { return Send(HttpMethods.Post, ResolveTypedUrl(HttpMethods.Post, requestDto), requestDto); } public virtual TResponse Post(string relativeOrAbsoluteUrl, object requestDto) { return Send(HttpMethods.Post, ResolveUrl(HttpMethods.Post, relativeOrAbsoluteUrl), requestDto); } public virtual void Put(IReturnVoid requestDto) { Send(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual HttpWebResponse Put(object requestDto) { return Send(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual TResponse Put(IReturn requestDto) { return Send(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual TResponse Put(object requestDto) { return Send(HttpMethods.Put, ResolveTypedUrl(HttpMethods.Put, requestDto), requestDto); } public virtual TResponse Put(string relativeOrAbsoluteUrl, object requestDto) { return Send(HttpMethods.Put, ResolveUrl(HttpMethods.Put, relativeOrAbsoluteUrl), requestDto); } public virtual void Patch(IReturnVoid requestDto) { Send(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual HttpWebResponse Patch(object requestDto) { return Send(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual TResponse Patch(IReturn requestDto) { return Send(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual TResponse Patch(object requestDto) { return Send(HttpMethods.Patch, ResolveTypedUrl(HttpMethods.Patch, requestDto), requestDto); } public virtual TResponse Patch(string relativeOrAbsoluteUrl, object requestDto) { return Send(HttpMethods.Patch, ResolveUrl(HttpMethods.Patch, relativeOrAbsoluteUrl), requestDto); } public virtual void CustomMethod(string httpVerb, IReturnVoid requestDto) { Send(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestDto); } public virtual HttpWebResponse CustomMethod(string httpVerb, object requestDto) { var requestBody = httpVerb.HasRequestBody() ? requestDto : null; return Send(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestBody); } public virtual HttpWebResponse CustomMethod(string httpVerb, string relativeOrAbsoluteUrl, object requestDto) { if (!HttpMethods.AllVerbs.Contains(httpVerb.ToUpper())) throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb); return Send(httpVerb, ResolveUrl(httpVerb, relativeOrAbsoluteUrl), requestDto); } public virtual TResponse CustomMethod(string httpVerb, IReturn requestDto) { var requestBody = httpVerb.HasRequestBody() ? requestDto : null; return Send(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestBody); } public virtual TResponse CustomMethod(string httpVerb, object requestDto) { var requestBody = httpVerb.HasRequestBody() ? requestDto : null; return CustomMethod(httpVerb, ResolveTypedUrl(httpVerb, requestDto), requestBody); } public virtual TResponse CustomMethod(string httpVerb, string relativeOrAbsoluteUrl, object requestDto = null) { if (!HttpMethods.AllVerbs.Contains(httpVerb.ToUpper())) throw new NotSupportedException("Unknown HTTP Method is not supported: " + httpVerb); return Send(httpVerb, relativeOrAbsoluteUrl, requestDto); } public virtual HttpWebResponse Head(IReturn requestDto) { return Send(HttpMethods.Head, ResolveTypedUrl(HttpMethods.Head, requestDto), requestDto); } public virtual HttpWebResponse Head(object requestDto) { return Send(HttpMethods.Head, ResolveTypedUrl(HttpMethods.Head, requestDto), requestDto); } public virtual HttpWebResponse Head(string relativeOrAbsoluteUrl) { return Send(HttpMethods.Head, ResolveUrl(HttpMethods.Head, relativeOrAbsoluteUrl), null); } public virtual TResponse PostFilesWithRequest(object request, IEnumerable files) { return PostFilesWithRequest(ResolveTypedUrl(HttpMethods.Post, request), request, files.ToArray()); } public virtual TResponse PostFilesWithRequest(string relativeOrAbsoluteUrl, object request, IEnumerable files) { return PostFilesWithRequest(ResolveUrl(HttpMethods.Post, relativeOrAbsoluteUrl), request, files.ToArray()); } private TResponse PostFilesWithRequest(string requestUri, object request, UploadFile[] files) { var fileCount = 0; long currentStreamPosition = 0; Func createWebRequest = () => { var webRequest = PrepareWebRequest(HttpMethods.Post, requestUri, null, null); var queryString = QueryStringSerializer.SerializeToString(request); var nameValueCollection = PclExportClient.Instance.ParseQueryString(queryString); var boundary = Guid.NewGuid().ToString("N"); webRequest.ContentType = "multipart/form-data; boundary=\"" + boundary + "\""; boundary = "--" + boundary; var newLine = "\r\n"; using (var outputStream = PclExport.Instance.GetRequestStream(webRequest)) { foreach (var key in nameValueCollection.AllKeys) { outputStream.Write(boundary + newLine); outputStream.Write($"Content-Disposition: form-data;name=\"{key}\"{newLine}"); outputStream.Write($"Content-Type: text/plain;charset=utf-8{newLine}{newLine}"); outputStream.Write(nameValueCollection[key] + newLine); } var buffer = new byte[4096]; for (fileCount = 0; fileCount < files.Length; fileCount++) { var file = files[fileCount]; currentStreamPosition = file.Stream.Position; outputStream.Write(boundary + newLine); var fileName = file.FileName ?? $"upload{fileCount}"; var fieldName = file.FieldName ?? $"upload{fileCount}"; outputStream.Write($"Content-Disposition: form-data;name=\"{fieldName}\";filename=\"{fileName}\"{newLine}Content-Type: application/octet-stream{newLine}{newLine}"); int byteCount; int bytesWritten = 0; while ((byteCount = file.Stream.Read(buffer, 0, 4096)) > 0) { outputStream.Write(buffer, 0, byteCount); if (OnUploadProgress != null) { bytesWritten += byteCount; OnUploadProgress(bytesWritten, file.Stream.Length); } } outputStream.Write(newLine); if (fileCount == files.Length - 1) outputStream.Write(boundary + "--"); } } return webRequest; }; try { var webRequest = createWebRequest(); var webResponse = PclExport.Instance.GetResponse(webRequest); return HandleResponse(webResponse); } catch (Exception ex) { TResponse response; // restore original position before retry files[fileCount - 1].Stream.Seek(currentStreamPosition, SeekOrigin.Begin); if (!HandleResponseException( ex, request, requestUri, createWebRequest, c => PclExport.Instance.GetResponse(c), out response)) { throw; } return response; } } public virtual TResponse PostFileWithRequest(Stream fileToUpload, string fileName, object request, string fieldName = "upload") { return PostFileWithRequest(ResolveTypedUrl(HttpMethods.Post, request), fileToUpload, fileName, request, fieldName); } public virtual TResponse PostFileWithRequest(string relativeOrAbsoluteUrl, Stream fileToUpload, string fileName, object request, string fieldName = "upload") { var requestUri = ResolveUrl(HttpMethods.Post, relativeOrAbsoluteUrl); var currentStreamPosition = fileToUpload.Position; Func createWebRequest = () => { var webRequest = PrepareWebRequest(HttpMethods.Post, requestUri, null, null); var queryString = QueryStringSerializer.SerializeToString(request); var nameValueCollection = PclExportClient.Instance.ParseQueryString(queryString); var boundary = "----------------------------" + Guid.NewGuid().ToString("N"); webRequest.ContentType = "multipart/form-data; boundary=" + boundary; boundary = "--" + boundary; var newLine = "\r\n"; using (var outputStream = PclExport.Instance.GetRequestStream(webRequest)) { foreach (var key in nameValueCollection.AllKeys) { outputStream.Write(boundary + newLine); outputStream.Write($"Content-Disposition: form-data;name=\"{key}\"{newLine}"); outputStream.Write($"Content-Type: text/plain;charset=utf-8{newLine}{newLine}"); outputStream.Write(nameValueCollection[key] + newLine); } outputStream.Write(boundary + newLine); outputStream.Write($"Content-Disposition: form-data;name=\"{fieldName}\";filename=\"{fileName}\"{newLine}{newLine}"); var buffer = new byte[4096]; int byteCount; int bytesWritten = 0; while ((byteCount = fileToUpload.Read(buffer, 0, 4096)) > 0) { outputStream.Write(buffer, 0, byteCount); if (OnUploadProgress != null) { bytesWritten += byteCount; OnUploadProgress(bytesWritten, fileToUpload.Length); } } outputStream.Write(newLine); outputStream.Write(boundary + "--"); } return webRequest; }; try { var webRequest = createWebRequest(); var webResponse = PclExport.Instance.GetResponse(webRequest); return HandleResponse(webResponse); } catch (Exception ex) { TResponse response; // restore original position before retry fileToUpload.Seek(currentStreamPosition, SeekOrigin.Begin); if (!HandleResponseException( ex, request, requestUri, createWebRequest, c => PclExport.Instance.GetResponse(c), out response)) { throw; } return response; } } public virtual TResponse PostFile(string relativeOrAbsoluteUrl, Stream fileToUpload, string fileName, string mimeType) { var currentStreamPosition = fileToUpload.Position; var requestUri = ResolveUrl(HttpMethods.Post, relativeOrAbsoluteUrl); Func createWebRequest = () => PrepareWebRequest(HttpMethods.Post, requestUri, null, null); try { var webRequest = createWebRequest(); webRequest.UploadFile(fileToUpload, fileName, mimeType); var webResponse = PclExport.Instance.GetResponse(webRequest); return HandleResponse(webResponse); } catch (Exception ex) { TResponse response; // restore original position before retry fileToUpload.Seek(currentStreamPosition, SeekOrigin.Begin); if (!HandleResponseException(ex, null, requestUri, createWebRequest, c => { c.UploadFile(fileToUpload, fileName, mimeType); return PclExport.Instance.GetResponse(c); }, out response)) { throw; } return response; } } private TResponse HandleResponse(WebResponse webResponse) { ApplyWebResponseFilters(webResponse); var response = GetResponse(webResponse); DisposeIfRequired(webResponse); return response; } private static void DisposeIfRequired(WebResponse webResponse) { if (typeof(TResponse) == typeof(HttpWebResponse) && webResponse is HttpWebResponse) return; if (typeof(TResponse) == typeof(Stream)) return; using (webResponse) { } } protected TResponse GetResponse(WebResponse webResponse) { #if NETSTANDARD1_1 || NETSTANDARD1_6 var compressionType = webResponse.Headers[HttpHeaders.ContentEncoding]; #endif //Callee Needs to dispose of response manually if (typeof(TResponse) == typeof(HttpWebResponse) && webResponse is HttpWebResponse) { return (TResponse)Convert.ChangeType(webResponse, typeof(TResponse), null); } if (typeof(TResponse) == typeof(Stream)) { #if NETSTANDARD1_1 || NETSTANDARD1_6 return (TResponse)(object)webResponse.GetResponseStream().Decompress(compressionType); #else return (TResponse)(object)webResponse.GetResponseStream(); #endif } #if NETSTANDARD1_1 || NETSTANDARD1_6 using (var responseStream = webResponse.GetResponseStream().Decompress(compressionType)) #else using (var responseStream = webResponse.GetResponseStream()) #endif { if (typeof(TResponse) == typeof(string)) { using (var reader = new StreamReader(responseStream)) { return (TResponse)(object)reader.ReadToEnd(); } } if (typeof(TResponse) == typeof(byte[])) { return (TResponse)(object)responseStream.ReadFully(); } var response = DeserializeFromStream(responseStream); return response; } } public void Dispose() { } } public static partial class ServiceClientExtensions { #if !(NETFX_CORE || SL5 || PCL || NETSTANDARD1_1) public static TResponse PostFile(this IRestClient client, string relativeOrAbsoluteUrl, FileInfo fileToUpload, string mimeType) { using (FileStream fileStream = fileToUpload.OpenRead()) { return client.PostFile(relativeOrAbsoluteUrl, fileStream, fileToUpload.Name, mimeType); } } public static TResponse PostFileWithRequest(this IRestClient client, FileInfo fileToUpload, object request, string fieldName = "upload") { return client.PostFileWithRequest(request.ToPostUrl(), fileToUpload, request, fieldName); } public static TResponse PostFileWithRequest(this IRestClient client, string relativeOrAbsoluteUrl, FileInfo fileToUpload, object request, string fieldName = "upload") { using (FileStream fileStream = fileToUpload.OpenRead()) { return client.PostFileWithRequest(relativeOrAbsoluteUrl, fileStream, fileToUpload.Name, request, fieldName); } } #endif public static void PopulateRequestMetadata(this IHasSessionId client, object request) { if (client.SessionId != null) { var hasSession = request as IHasSessionId; if (hasSession != null && hasSession.SessionId == null) hasSession.SessionId = client.SessionId; } var clientVersion = client as IHasVersion; if (clientVersion != null && clientVersion.Version > 0) { var hasVersion = request as IHasVersion; if (hasVersion != null && hasVersion.Version <= 0) hasVersion.Version = clientVersion.Version; } } public static Dictionary ToDictionary(this CookieContainer cookies, string baseUri) { var to = new Dictionary(); if (cookies == null) return to; foreach (Cookie cookie in cookies.GetCookies(new Uri(baseUri))) { to[cookie.Name] = cookie.Value; } return to; } [Obsolete("Use: using (client.Get(request) { }")] public static HttpWebResponse Get(this IRestClient client, object request) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Get(request); } [Obsolete("Use: using (client.Delete(request) { }")] public static HttpWebResponse Delete(this IRestClient client, object request) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Delete(request); } [Obsolete("Use: using (client.Post(request) { }")] public static HttpWebResponse Post(this IRestClient client, object request) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Post(request); } [Obsolete("Use: using (client.Put(request) { }")] public static HttpWebResponse Put(this IRestClient client, object request) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Put(request); } [Obsolete("Use: using (client.Patch(request) { }")] public static HttpWebResponse Patch(this IRestClient client, object request) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Patch(request); } [Obsolete("Use: using (client.CustomMethod(httpVerb, request) { }")] public static HttpWebResponse CustomMethod(this IRestClient client, string httpVerb, object requestDto) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.CustomMethod(httpVerb, requestDto); } [Obsolete("Use: using (client.Head(request) { }")] public static HttpWebResponse Head(this IRestClient client, IReturn requestDto) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Head(requestDto); } [Obsolete("Use: using (client.Head(request) { }")] public static HttpWebResponse Head(this IRestClient client, object requestDto) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Head(requestDto); } [Obsolete("Use: using (client.Head(relativeOrAbsoluteUrl) { }")] public static HttpWebResponse Head(this IRestClient client, string relativeOrAbsoluteUrl) { var c = client as ServiceClientBase; if (c == null) throw new NotSupportedException(); return c.Head(relativeOrAbsoluteUrl); } public static void SetCookie(this IServiceClient client, Uri baseUri, string name, string value, DateTime? expiresAt = null, string path = "/", bool? httpOnly = null, bool? secure = null) { var hasCookies = client as IHasCookieContainer; if (hasCookies == null) throw new NotSupportedException("Client does not implement IHasCookieContainer"); hasCookies.CookieContainer.SetCookie(baseUri, name, value, expiresAt, path, httpOnly, secure); } public static TResponse PostBody(this IServiceClient client, IReturn toRequest, object requestBody) => client.Send(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PostBody(this IServiceClient client, IReturn toRequest, string requestBody) => client.Send(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PostBody(this IServiceClient client, IReturn toRequest, byte[] requestBody) => client.Send(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PostBody(this IServiceClient client, IReturn toRequest, Stream requestBody) => client.Send(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PutBody(this IServiceClient client, IReturn toRequest, object requestBody) => client.Send(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PutBody(this IServiceClient client, IReturn toRequest, string requestBody) => client.Send(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PutBody(this IServiceClient client, IReturn toRequest, byte[] requestBody) => client.Send(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PutBody(this IServiceClient client, IReturn toRequest, Stream requestBody) => client.Send(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PatchBody(this IServiceClient client, IReturn toRequest, object requestBody) => client.Send(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PatchBody(this IServiceClient client, IReturn toRequest, string requestBody) => client.Send(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PatchBody(this IServiceClient client, IReturn toRequest, byte[] requestBody) => client.Send(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static TResponse PatchBody(this IServiceClient client, IReturn toRequest, Stream requestBody) => client.Send(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody); public static Task PostBodyAsync(this IServiceClient client, IReturn toRequest, object requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PostBodyAsync(this IServiceClient client, IReturn toRequest, string requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PostBodyAsync(this IServiceClient client, IReturn toRequest, byte[] requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PostBodyAsync(this IServiceClient client, IReturn toRequest, Stream requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Post, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PutBodyAsync(this IServiceClient client, IReturn toRequest, object requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PutBodyAsync(this IServiceClient client, IReturn toRequest, string requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PutBodyAsync(this IServiceClient client, IReturn toRequest, byte[] requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PutBodyAsync(this IServiceClient client, IReturn toRequest, Stream requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Put, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PatchBodyAsync(this IServiceClient client, IReturn toRequest, object requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PatchBodyAsync(this IServiceClient client, IReturn toRequest, string requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PatchBodyAsync(this IServiceClient client, IReturn toRequest, byte[] requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static Task PatchBodyAsync(this IServiceClient client, IReturn toRequest, Stream requestBody, CancellationToken token = default(CancellationToken)) => client.SendAsync(HttpMethods.Patch, ((IServiceClientMeta)client).ResolveTypedUrl(HttpMethods.Get, toRequest), requestBody, token); public static void SetCookie(this CookieContainer cookieContainer, Uri baseUri, string name, string value, DateTime? expiresAt, string path = "/", bool? httpOnly = null, bool? secure = null) { var cookie = new Cookie(name, value, path); if (expiresAt != null) cookie.Expires = expiresAt.Value; if (path != null) cookie.Path = path; if (httpOnly != null) cookie.HttpOnly = httpOnly.Value; if (secure != null) cookie.Secure = secure.Value; cookieContainer.Add(baseUri, cookie); } public static string GetSessionId(this IServiceClient client) { string sessionId; client.GetCookieValues().TryGetValue("ss-id", out sessionId); return sessionId; } public static string GetPermanentSessionId(this IServiceClient client) { string sessionId; client.GetCookieValues().TryGetValue("ss-pid", out sessionId); return sessionId; } public static void SetSessionId(this IServiceClient client, string sessionId) { if (sessionId == null) return; client.SetCookie("ss-id", sessionId); } public static void SetPermanentSessionId(this IServiceClient client, string sessionId) { if (sessionId == null) return; client.SetCookie("ss-pid", sessionId, expiresIn: TimeSpan.FromDays(365 * 20)); } public static string GetTokenCookie(this IServiceClient client) { string token; client.GetCookieValues().TryGetValue("ss-tok", out token); return token; } public static string GetTokenCookie(this CookieContainer cookies, string baseUri) { string token; cookies.ToDictionary(baseUri).TryGetValue("ss-tok", out token); return token; } public static void SetTokenCookie(this IServiceClient client, string token) { if (token == null) return; client.SetCookie("ss-tok", token, expiresIn: TimeSpan.FromDays(365 * 20)); } public static void SetTokenCookie(this CookieContainer cookies, string baseUri, string token) { if (token == null) return; cookies.SetCookie(new Uri(baseUri), "ss-tok", token, expiresAt: DateTime.UtcNow.Add(TimeSpan.FromDays(365 * 20))); } public static void SetUserAgent(this HttpWebRequest req, string userAgent) { PclExport.Instance.SetUserAgent(req, userAgent); } } public interface IHasCookieContainer { CookieContainer CookieContainer { get; } } public interface IServiceClientMeta { string Format { get; } string BaseUri { get; set; } string SyncReplyBaseUri { get; } string AsyncOneWayBaseUri { get; } string UserName { get; } string Password { get; } bool AlwaysSendBasicAuthHeader { get; } int Version { get; } string SessionId { get; } string ResolveTypedUrl(string httpMethod, object requestDto); string ResolveUrl(string httpMethod, string relativeOrAbsoluteUrl); } public delegate string UrlResolverDelegate(IServiceClientMeta client, string httpMethod, string relativeOrAbsoluteUrl); public delegate string TypedUrlResolverDelegate(IServiceClientMeta client, string httpMethod, object requestDto); public delegate object ResultsFilterDelegate(Type responseType, string httpMethod, string requestUri, object request); public delegate void ResultsFilterResponseDelegate(WebResponse webResponse, object response, string httpMethod, string requestUri, object request); public delegate object ExceptionFilterDelegate(WebException webEx, WebResponse webResponse, string requestUri, Type responseType); }