Mercurial > pub > bltoolkit
view Demo/WebServices/Client/WebClient/WebClientBase.cs @ 9:1e85f66cf767 default tip
update bltoolkit
author | nickolay |
---|---|
date | Thu, 05 Apr 2018 20:53:26 +0300 |
parents | f990fcb411a9 |
children |
line wrap: on
line source
using System; using System.Diagnostics; using System.Net; using System.Reflection; using System.Web.Services; using System.Web.Services.Protocols; using BLToolkit.Common; namespace Demo.WebServices.Client.WebClient { [DebuggerStepThrough] [System.ComponentModel.DesignerCategory("Code")] [WebClient] public abstract class WebClientBase: SoapHttpClientProtocol { /// <summary> /// Initializes a new instance of the <see cref="WebClientBase"/> class. /// </summary> protected WebClientBase() { if (DefaultCredentials == null) UseDefaultCredentials = true; else Credentials = DefaultCredentials; // Use custom redirection since we need to repost some data. // AllowAutoRedirect = false; EnableDecompression = true; PreAuthenticate = true; // Setup appropriate user agent string. // var asm = Assembly.GetEntryAssembly(); if (asm != null) UserAgent = asm.FullName; #if DEBUG // By default the timeout value is about 2 minutes, // wich is quite enought in a normal run, // but not for debugging. // Timeout = Int32.MaxValue; #endif if (string.IsNullOrEmpty(BaseUrl)) return; var attr = (WebServiceBindingAttribute)Attribute.GetCustomAttribute(GetType(), typeof(WebServiceBindingAttribute)); if (attr == null) throw new InvalidOperationException("Please specify relative url or mark the avatar with WebServiceBindingAttribute"); var ns = attr.Namespace; if (string.IsNullOrEmpty(ns)) throw new InvalidOperationException("Please specify namespace in WebServiceBindingAttribute"); if (ns.EndsWith("/")) ns = ns.Substring(0, ns.Length - 1); var builder = new UriBuilder(BaseUrl) { Path = new UriBuilder(ns).Path }; Url = builder.Uri.AbsoluteUri; } /// <summary> /// Initializes a new instance of the <see cref="WebClientBase"/> class. /// </summary> /// <param name="relativeUrl">Path to web service, relative to <see cref="BaseUrl"/>.</param> protected WebClientBase(string relativeUrl) : this() { Debug.Assert(Url == BaseUrl + relativeUrl, string.Format("Expected url '{0}' got '{1}'", Url, BaseUrl + relativeUrl) ); Url = BaseUrl + relativeUrl; } public static ICredentials DefaultCredentials { get; set; } /// <summary> /// Customizable url path. /// </summary> public static string BaseUrl { get; set; } /// <summary> /// Returns <see langword="true"/>, program runs in offline mode. /// </summary> public static bool OffLine { get { return string.IsNullOrEmpty(BaseUrl); } } #region Invoke private object[] InvokeInternal(string methodName, object[] parameters) { object[] ret = null; for (;;) { try { #if DEBUG var sw = Stopwatch.StartNew(); #endif ret = base.Invoke(methodName, parameters); #if DEBUG Debug.WriteLineIf(TS.TraceVerbose, string.Format("Sync call {0}/{1} = {2} msec.", Url, methodName, sw.ElapsedMilliseconds), TS.DisplayName); #endif } catch (Exception ex) { if (ex is WebException) { var webException = (WebException) ex; if (webException.Status == WebExceptionStatus.RequestCanceled) { OnWebOperationCancelled(methodName, parameters); break; } // Internal redirection // if (webException.Status == WebExceptionStatus.ReceiveFailure) continue; } if (OnWebOperationException(methodName, parameters, ex)) continue; } break; } return AcceptChanges(ret); } /// <summary> /// Invokes a web method synchronously. /// </summary> /// <param name="methodName">Web method name.</param> /// <param name="parameters">Web method parameters.</param> /// <returns>Web method return value or values on success, /// a null reference otherwise.</returns> public new object[] Invoke(string methodName, params object[] parameters) { return InvokeInternal(methodName, parameters) ?? new object[]{ null }; } /// <summary> /// Invokes a web method synchronously. /// </summary> /// <param name="methodName">Web method name.</param> /// <param name="parameters">Web method parameters.</param> /// <returns>Web method return value or default(T) if the call fails.</returns> public T Invoke<T>(string methodName, params object[] parameters) { var ret = InvokeInternal(methodName, parameters); return ret != null && ret.Length != 0? (T)ret[0]: default(T); } /// <summary> /// Invokes a web method asynchronously. /// </summary> /// <param name="methodName">Web method name.</param> /// <param name="asyncCallState">Call state handle. /// Upon return, may be used to cancel the call</param> /// <param name="parameters">Web method parameters.</param> /// <param name="callback">Callback method to process the result.</param> /// <param name="exceptionHandler">Fail handler.</param> /// <seealso cref="CancelAsync(AsyncCallState)"/> public void InvokeAsync( string methodName, AsyncCallState asyncCallState, Action<Exception> exceptionHandler, Delegate callback, params object[] parameters) { #if DEBUG var sw = Stopwatch.StartNew(); #endif if (asyncCallState != null) { #if DEBUG Debug.WriteLineIf(TS.TraceVerbose && asyncCallState.PendingCall != null, string.Format("Cancelling async call {0}/{1}", Url, methodName), TS.DisplayName); #endif CancelAsync(asyncCallState); } var exceptionCallback = exceptionHandler ?? delegate(Exception ex) { if (ex is WebException) { var webException = (WebException)ex; // Request cancelled. // if (webException.Status == WebExceptionStatus.RequestCanceled) { OnWebOperationCancelled(methodName, parameters); return; } } // Check for retry. // if (OnWebOperationException(methodName, parameters, ex)) { InvokeAsync(methodName, asyncCallState, exceptionHandler, callback, parameters); } }; System.Threading.SendOrPostCallback sendCallback = delegate(object obj) { #if DEBUG Debug.WriteLineIf(TS.TraceVerbose, string.Format("Async call {0}/{1} = {2} msec.", Url, methodName, sw.ElapsedMilliseconds), TS.DisplayName); #endif var ea = (InvokeCompletedEventArgs)obj; if (ea.Error != null) { // Internal redirection // if (ea.Error is WebException && ((WebException)ea.Error).Status == WebExceptionStatus.ReceiveFailure) { InvokeAsync(methodName, asyncCallState, exceptionHandler, callback, parameters); } else { exceptionCallback(ea.Error); } } else if (ea.Cancelled || (asyncCallState != null && ea.UserState != asyncCallState.PendingCall)) { exceptionCallback(new WebException(methodName, WebExceptionStatus.RequestCanceled)); } else { callback.DynamicInvoke(AcceptChanges(ea.Results)); } if (asyncCallState != null && ea.UserState == asyncCallState.PendingCall) asyncCallState.PendingCall = null; }; object cookie = new CompoundValue(methodName, parameters); if (asyncCallState!= null) asyncCallState.PendingCall = cookie; InvokeAsync(methodName, parameters, sendCallback, cookie); } /// <summary> /// Invokes a web method asynchronously. /// </summary> /// <param name="methodName">Web method name.</param> /// <param name="asyncCallState">Call state handle. /// Upon return, may be used to cancel the call</param> /// <param name="parameters">Web method parameters.</param> /// <param name="callback">Callback method to process the result.</param> /// <seealso cref="CancelAsync(AsyncCallState)"/> public void InvokeAsync( string methodName, AsyncCallState asyncCallState, Delegate callback, params object[] parameters) { InvokeAsync(methodName, asyncCallState, null, callback, parameters); } private static void AcceptChanges(object obj) { if (obj == null || obj is IConvertible) { // // Do nothing on bool, int, string, etc. // } else if (obj is BLToolkit.EditableObjects.IEditable) ((BLToolkit.EditableObjects.IEditable)obj).AcceptChanges(); else if (obj is System.Collections.IDictionary) { foreach (System.Collections.DictionaryEntry pair in (System.Collections.IDictionary)obj) { AcceptChanges(pair.Key); AcceptChanges(pair.Value); } } else if (obj is System.Collections.IEnumerable) { foreach (var elm in (System.Collections.IEnumerable)obj) AcceptChanges(elm); } } private static object[] AcceptChanges(object[] array) { if (array != null) Array.ForEach(array, AcceptChanges); return array; } /// <summary> ///.Cancel an asynchronous call if it is not completed already. /// </summary> /// <param name="asyncCallState">Async call state.</param> public void CancelAsync(AsyncCallState asyncCallState) { if (asyncCallState.PendingCall == null) return; CancelAsync(asyncCallState.PendingCall); asyncCallState.PendingCall = null; } #endregion #region Events private static readonly object EventWebOperationCancelled = new object(); public event EventHandler<WebOperationCancelledEventArgs> WebOperationCancelled { add { Events.AddHandler (EventWebOperationCancelled, value); } remove { Events.RemoveHandler(EventWebOperationCancelled, value); } } public static event EventHandler<WebOperationCancelledEventArgs> WebOperationCancelledDefaultHandler; protected virtual void OnWebOperationCancelled(string methodName, params object[] parameters) { Debug.WriteLineIf(TS.TraceInfo, string.Format("OnWebOperationCancelled; op={0}/{1}", Url, methodName)); var handler = (EventHandler<WebOperationCancelledEventArgs>)Events[EventWebOperationCancelled] ?? WebOperationCancelledDefaultHandler; if (handler != null) { var ea = new WebOperationCancelledEventArgs(Url, methodName, parameters); handler(this, ea); } } private static readonly object EventWebOperationException = new object(); public event EventHandler<WebOperationExceptionEventArgs> WebOperationException { add { Events.AddHandler (EventWebOperationException, value); } remove { Events.RemoveHandler(EventWebOperationException, value); } } public static event EventHandler<WebOperationExceptionEventArgs> WebOperationExceptionDefaultHandler; protected virtual bool OnWebOperationException(string methodName, object[] parameters, Exception ex) { Debug.WriteLineIf(TS.TraceError, string.Format("OnWebOperationException; op={0}/{1}; ex={2}", Url, methodName, ex)); var handler = (EventHandler<WebOperationExceptionEventArgs>)Events[EventWebOperationException] ?? WebOperationExceptionDefaultHandler; if (handler != null) { var ea = new WebOperationExceptionEventArgs(Url, methodName, parameters, ex); handler(this, ea); return ea.Retry; } throw new TargetInvocationException(methodName, ex); } #endregion #region Cookies private string _cookie; /// <summary> /// Creates a <see cref="T:System.Net.WebRequest"/> for the specified uri. /// </summary> /// <returns> The <see cref="T:System.Net.WebRequest"/>. </returns> /// <param name="uri">The <see cref="T:System.Uri"></see> to use when creating the <see cref="T:System.Net.WebRequest"></see>. </param> /// <exception cref="T:System.InvalidOperationException">The uri parameter is null. </exception> protected override WebRequest GetWebRequest(Uri uri) { var webRequest = base.GetWebRequest(uri); PrepareRequest(webRequest); return webRequest; } /// <summary> /// Returns a response from a synchronous request to an XML Web service method. /// </summary> /// <returns> The <see cref="T:System.Net.WebResponse"/>. </returns> /// <param name="request">The <see cref="T:System.Net.WebRequest"/> /// from which to get the response. </param> protected override WebResponse GetWebResponse(WebRequest request) { var response = (HttpWebResponse)base.GetWebResponse(request); ProcessResponse(response); return response; } /// <summary> /// Returns a response from an asynchronous request to an XML Web service method. /// </summary> /// <returns> The <see cref="T:System.Net.WebResponse"/>.</returns> /// <param name="result">The <see cref="T:System.IAsyncResult"/> to pass to /// <see cref="M:System.Net.HttpWebRequest.EndGetResponse(System.IAsyncResult)"/> when the response has completed. </param> /// <param name="request">The <see cref="T:System.Net.WebRequest"/> from which to get the response. </param> protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) { var response = (HttpWebResponse)base.GetWebResponse(request, result); ProcessResponse(response); return response; } private void ProcessResponse(HttpWebResponse response) { if (response.StatusCode == HttpStatusCode.MovedPermanently) { var redirectedLocation = response.Headers["Location"]; Url = new Uri(new Uri(Url), redirectedLocation).AbsoluteUri; throw new WebException(redirectedLocation, WebExceptionStatus.ReceiveFailure); } var cookies = response.Headers.GetValues("Set-Cookie"); if (cookies == null) return; foreach (var cookie in cookies) { if (cookie.StartsWith("ASP.NET_SessionId=", StringComparison.Ordinal)) { _cookie = cookie; break; } } } private void PrepareRequest(WebRequest request) { if (!string.IsNullOrEmpty(_cookie)) request.Headers.Add("Cookie", _cookie); } #endregion #region Debug private static TraceSwitch _ts; internal static TraceSwitch TS { get { return _ts ?? (_ts = new TraceSwitch("WebServiceClient", "Web service client trace switch")); } } #endregion } }