Mercurial > pub > bltoolkit
diff Demo/WebServices/Client/WebClient/WebClientBase.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Demo/WebServices/Client/WebClient/WebClientBase.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,465 @@ +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 + + } +}