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
+
+	}
+}