Mercurial > pub > bltoolkit
comparison Demo/WebServices/Client/WebClient/WebClientBase.cs @ 0:f990fcb411a9
Копия текущей версии из github
| author | cin |
|---|---|
| date | Thu, 27 Mar 2014 21:46:09 +0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:f990fcb411a9 |
|---|---|
| 1 using System; | |
| 2 using System.Diagnostics; | |
| 3 using System.Net; | |
| 4 using System.Reflection; | |
| 5 using System.Web.Services; | |
| 6 using System.Web.Services.Protocols; | |
| 7 | |
| 8 using BLToolkit.Common; | |
| 9 | |
| 10 namespace Demo.WebServices.Client.WebClient | |
| 11 { | |
| 12 [DebuggerStepThrough] | |
| 13 [System.ComponentModel.DesignerCategory("Code")] | |
| 14 [WebClient] | |
| 15 public abstract class WebClientBase: SoapHttpClientProtocol | |
| 16 { | |
| 17 /// <summary> | |
| 18 /// Initializes a new instance of the <see cref="WebClientBase"/> class. | |
| 19 /// </summary> | |
| 20 protected WebClientBase() | |
| 21 { | |
| 22 if (DefaultCredentials == null) | |
| 23 UseDefaultCredentials = true; | |
| 24 else | |
| 25 Credentials = DefaultCredentials; | |
| 26 | |
| 27 // Use custom redirection since we need to repost some data. | |
| 28 // | |
| 29 AllowAutoRedirect = false; | |
| 30 | |
| 31 EnableDecompression = true; | |
| 32 PreAuthenticate = true; | |
| 33 | |
| 34 // Setup appropriate user agent string. | |
| 35 // | |
| 36 var asm = Assembly.GetEntryAssembly(); | |
| 37 if (asm != null) | |
| 38 UserAgent = asm.FullName; | |
| 39 | |
| 40 #if DEBUG | |
| 41 // By default the timeout value is about 2 minutes, | |
| 42 // wich is quite enought in a normal run, | |
| 43 // but not for debugging. | |
| 44 // | |
| 45 Timeout = Int32.MaxValue; | |
| 46 #endif | |
| 47 | |
| 48 if (string.IsNullOrEmpty(BaseUrl)) | |
| 49 return; | |
| 50 | |
| 51 var attr = (WebServiceBindingAttribute)Attribute.GetCustomAttribute(GetType(), typeof(WebServiceBindingAttribute)); | |
| 52 | |
| 53 if (attr == null) | |
| 54 throw new InvalidOperationException("Please specify relative url or mark the avatar with WebServiceBindingAttribute"); | |
| 55 | |
| 56 var ns = attr.Namespace; | |
| 57 | |
| 58 if (string.IsNullOrEmpty(ns)) | |
| 59 throw new InvalidOperationException("Please specify namespace in WebServiceBindingAttribute"); | |
| 60 | |
| 61 if (ns.EndsWith("/")) | |
| 62 ns = ns.Substring(0, ns.Length - 1); | |
| 63 | |
| 64 var builder = new UriBuilder(BaseUrl) { Path = new UriBuilder(ns).Path }; | |
| 65 | |
| 66 Url = builder.Uri.AbsoluteUri; | |
| 67 } | |
| 68 | |
| 69 /// <summary> | |
| 70 /// Initializes a new instance of the <see cref="WebClientBase"/> class. | |
| 71 /// </summary> | |
| 72 /// <param name="relativeUrl">Path to web service, relative to <see cref="BaseUrl"/>.</param> | |
| 73 protected WebClientBase(string relativeUrl) : this() | |
| 74 { | |
| 75 Debug.Assert(Url == BaseUrl + relativeUrl, string.Format("Expected url '{0}' got '{1}'", Url, BaseUrl + relativeUrl) ); | |
| 76 Url = BaseUrl + relativeUrl; | |
| 77 } | |
| 78 | |
| 79 public static ICredentials DefaultCredentials { get; set; } | |
| 80 | |
| 81 /// <summary> | |
| 82 /// Customizable url path. | |
| 83 /// </summary> | |
| 84 public static string BaseUrl { get; set; } | |
| 85 | |
| 86 /// <summary> | |
| 87 /// Returns <see langword="true"/>, program runs in offline mode. | |
| 88 /// </summary> | |
| 89 public static bool OffLine | |
| 90 { | |
| 91 get { return string.IsNullOrEmpty(BaseUrl); } | |
| 92 } | |
| 93 | |
| 94 #region Invoke | |
| 95 | |
| 96 private object[] InvokeInternal(string methodName, object[] parameters) | |
| 97 { | |
| 98 object[] ret = null; | |
| 99 | |
| 100 for (;;) | |
| 101 { | |
| 102 try | |
| 103 { | |
| 104 #if DEBUG | |
| 105 var sw = Stopwatch.StartNew(); | |
| 106 #endif | |
| 107 ret = base.Invoke(methodName, parameters); | |
| 108 #if DEBUG | |
| 109 Debug.WriteLineIf(TS.TraceVerbose, | |
| 110 string.Format("Sync call {0}/{1} = {2} msec.", | |
| 111 Url, methodName, sw.ElapsedMilliseconds), TS.DisplayName); | |
| 112 #endif | |
| 113 } | |
| 114 catch (Exception ex) | |
| 115 { | |
| 116 if (ex is WebException) | |
| 117 { | |
| 118 var webException = (WebException) ex; | |
| 119 | |
| 120 if (webException.Status == WebExceptionStatus.RequestCanceled) | |
| 121 { | |
| 122 OnWebOperationCancelled(methodName, parameters); | |
| 123 break; | |
| 124 } | |
| 125 | |
| 126 // Internal redirection | |
| 127 // | |
| 128 if (webException.Status == WebExceptionStatus.ReceiveFailure) | |
| 129 continue; | |
| 130 } | |
| 131 | |
| 132 if (OnWebOperationException(methodName, parameters, ex)) | |
| 133 continue; | |
| 134 } | |
| 135 | |
| 136 break; | |
| 137 } | |
| 138 | |
| 139 return AcceptChanges(ret); | |
| 140 } | |
| 141 | |
| 142 /// <summary> | |
| 143 /// Invokes a web method synchronously. | |
| 144 /// </summary> | |
| 145 /// <param name="methodName">Web method name.</param> | |
| 146 /// <param name="parameters">Web method parameters.</param> | |
| 147 /// <returns>Web method return value or values on success, | |
| 148 /// a null reference otherwise.</returns> | |
| 149 public new object[] Invoke(string methodName, params object[] parameters) | |
| 150 { | |
| 151 return InvokeInternal(methodName, parameters) ?? new object[]{ null }; | |
| 152 } | |
| 153 | |
| 154 /// <summary> | |
| 155 /// Invokes a web method synchronously. | |
| 156 /// </summary> | |
| 157 /// <param name="methodName">Web method name.</param> | |
| 158 /// <param name="parameters">Web method parameters.</param> | |
| 159 /// <returns>Web method return value or default(T) if the call fails.</returns> | |
| 160 public T Invoke<T>(string methodName, params object[] parameters) | |
| 161 { | |
| 162 var ret = InvokeInternal(methodName, parameters); | |
| 163 | |
| 164 return ret != null && ret.Length != 0? (T)ret[0]: default(T); | |
| 165 } | |
| 166 | |
| 167 /// <summary> | |
| 168 /// Invokes a web method asynchronously. | |
| 169 /// </summary> | |
| 170 /// <param name="methodName">Web method name.</param> | |
| 171 /// <param name="asyncCallState">Call state handle. | |
| 172 /// Upon return, may be used to cancel the call</param> | |
| 173 /// <param name="parameters">Web method parameters.</param> | |
| 174 /// <param name="callback">Callback method to process the result.</param> | |
| 175 /// <param name="exceptionHandler">Fail handler.</param> | |
| 176 /// <seealso cref="CancelAsync(AsyncCallState)"/> | |
| 177 public void InvokeAsync( | |
| 178 string methodName, | |
| 179 AsyncCallState asyncCallState, | |
| 180 Action<Exception> exceptionHandler, | |
| 181 Delegate callback, | |
| 182 params object[] parameters) | |
| 183 { | |
| 184 #if DEBUG | |
| 185 var sw = Stopwatch.StartNew(); | |
| 186 #endif | |
| 187 | |
| 188 if (asyncCallState != null) | |
| 189 { | |
| 190 #if DEBUG | |
| 191 Debug.WriteLineIf(TS.TraceVerbose && asyncCallState.PendingCall != null, | |
| 192 string.Format("Cancelling async call {0}/{1}", | |
| 193 Url, methodName), TS.DisplayName); | |
| 194 #endif | |
| 195 CancelAsync(asyncCallState); | |
| 196 } | |
| 197 | |
| 198 var exceptionCallback = exceptionHandler ?? delegate(Exception ex) | |
| 199 { | |
| 200 if (ex is WebException) | |
| 201 { | |
| 202 var webException = (WebException)ex; | |
| 203 | |
| 204 // Request cancelled. | |
| 205 // | |
| 206 if (webException.Status == WebExceptionStatus.RequestCanceled) | |
| 207 { | |
| 208 OnWebOperationCancelled(methodName, parameters); | |
| 209 return; | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 // Check for retry. | |
| 214 // | |
| 215 if (OnWebOperationException(methodName, parameters, ex)) | |
| 216 { | |
| 217 InvokeAsync(methodName, asyncCallState, exceptionHandler, callback, parameters); | |
| 218 } | |
| 219 }; | |
| 220 | |
| 221 System.Threading.SendOrPostCallback sendCallback = delegate(object obj) | |
| 222 { | |
| 223 #if DEBUG | |
| 224 Debug.WriteLineIf(TS.TraceVerbose, | |
| 225 string.Format("Async call {0}/{1} = {2} msec.", | |
| 226 Url, methodName, sw.ElapsedMilliseconds), TS.DisplayName); | |
| 227 #endif | |
| 228 | |
| 229 var ea = (InvokeCompletedEventArgs)obj; | |
| 230 | |
| 231 if (ea.Error != null) | |
| 232 { | |
| 233 // Internal redirection | |
| 234 // | |
| 235 if (ea.Error is WebException && ((WebException)ea.Error).Status == WebExceptionStatus.ReceiveFailure) | |
| 236 { | |
| 237 InvokeAsync(methodName, asyncCallState, exceptionHandler, callback, parameters); | |
| 238 } | |
| 239 else | |
| 240 { | |
| 241 exceptionCallback(ea.Error); | |
| 242 } | |
| 243 } | |
| 244 else if (ea.Cancelled || (asyncCallState != null && ea.UserState != asyncCallState.PendingCall)) | |
| 245 { | |
| 246 exceptionCallback(new WebException(methodName, WebExceptionStatus.RequestCanceled)); | |
| 247 } | |
| 248 else | |
| 249 { | |
| 250 callback.DynamicInvoke(AcceptChanges(ea.Results)); | |
| 251 } | |
| 252 | |
| 253 if (asyncCallState != null && ea.UserState == asyncCallState.PendingCall) | |
| 254 asyncCallState.PendingCall = null; | |
| 255 }; | |
| 256 | |
| 257 object cookie = new CompoundValue(methodName, parameters); | |
| 258 | |
| 259 if (asyncCallState!= null) | |
| 260 asyncCallState.PendingCall = cookie; | |
| 261 | |
| 262 InvokeAsync(methodName, parameters, sendCallback, cookie); | |
| 263 } | |
| 264 | |
| 265 /// <summary> | |
| 266 /// Invokes a web method asynchronously. | |
| 267 /// </summary> | |
| 268 /// <param name="methodName">Web method name.</param> | |
| 269 /// <param name="asyncCallState">Call state handle. | |
| 270 /// Upon return, may be used to cancel the call</param> | |
| 271 /// <param name="parameters">Web method parameters.</param> | |
| 272 /// <param name="callback">Callback method to process the result.</param> | |
| 273 /// <seealso cref="CancelAsync(AsyncCallState)"/> | |
| 274 public void InvokeAsync( | |
| 275 string methodName, | |
| 276 AsyncCallState asyncCallState, | |
| 277 Delegate callback, | |
| 278 params object[] parameters) | |
| 279 { | |
| 280 InvokeAsync(methodName, asyncCallState, null, callback, parameters); | |
| 281 } | |
| 282 | |
| 283 private static void AcceptChanges(object obj) | |
| 284 { | |
| 285 if (obj == null || obj is IConvertible) | |
| 286 { | |
| 287 // | |
| 288 // Do nothing on bool, int, string, etc. | |
| 289 // | |
| 290 } | |
| 291 else if (obj is BLToolkit.EditableObjects.IEditable) | |
| 292 ((BLToolkit.EditableObjects.IEditable)obj).AcceptChanges(); | |
| 293 else if (obj is System.Collections.IDictionary) | |
| 294 { | |
| 295 foreach (System.Collections.DictionaryEntry pair in (System.Collections.IDictionary)obj) | |
| 296 { | |
| 297 AcceptChanges(pair.Key); | |
| 298 AcceptChanges(pair.Value); | |
| 299 } | |
| 300 } | |
| 301 else if (obj is System.Collections.IEnumerable) | |
| 302 { | |
| 303 foreach (var elm in (System.Collections.IEnumerable)obj) | |
| 304 AcceptChanges(elm); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 private static object[] AcceptChanges(object[] array) | |
| 309 { | |
| 310 if (array != null) | |
| 311 Array.ForEach(array, AcceptChanges); | |
| 312 | |
| 313 return array; | |
| 314 } | |
| 315 | |
| 316 /// <summary> | |
| 317 ///.Cancel an asynchronous call if it is not completed already. | |
| 318 /// </summary> | |
| 319 /// <param name="asyncCallState">Async call state.</param> | |
| 320 public void CancelAsync(AsyncCallState asyncCallState) | |
| 321 { | |
| 322 if (asyncCallState.PendingCall == null) | |
| 323 return; | |
| 324 | |
| 325 CancelAsync(asyncCallState.PendingCall); | |
| 326 asyncCallState.PendingCall = null; | |
| 327 } | |
| 328 | |
| 329 #endregion | |
| 330 | |
| 331 #region Events | |
| 332 | |
| 333 private static readonly object EventWebOperationCancelled = new object(); | |
| 334 public event EventHandler<WebOperationCancelledEventArgs> WebOperationCancelled | |
| 335 { | |
| 336 add { Events.AddHandler (EventWebOperationCancelled, value); } | |
| 337 remove { Events.RemoveHandler(EventWebOperationCancelled, value); } | |
| 338 } | |
| 339 | |
| 340 public static event EventHandler<WebOperationCancelledEventArgs> WebOperationCancelledDefaultHandler; | |
| 341 | |
| 342 protected virtual void OnWebOperationCancelled(string methodName, params object[] parameters) | |
| 343 { | |
| 344 Debug.WriteLineIf(TS.TraceInfo, string.Format("OnWebOperationCancelled; op={0}/{1}", Url, methodName)); | |
| 345 var handler = (EventHandler<WebOperationCancelledEventArgs>)Events[EventWebOperationCancelled] ?? WebOperationCancelledDefaultHandler; | |
| 346 if (handler != null) | |
| 347 { | |
| 348 var ea = new WebOperationCancelledEventArgs(Url, methodName, parameters); | |
| 349 handler(this, ea); | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 private static readonly object EventWebOperationException = new object(); | |
| 354 public event EventHandler<WebOperationExceptionEventArgs> WebOperationException | |
| 355 { | |
| 356 add { Events.AddHandler (EventWebOperationException, value); } | |
| 357 remove { Events.RemoveHandler(EventWebOperationException, value); } | |
| 358 } | |
| 359 public static event EventHandler<WebOperationExceptionEventArgs> WebOperationExceptionDefaultHandler; | |
| 360 | |
| 361 protected virtual bool OnWebOperationException(string methodName, object[] parameters, Exception ex) | |
| 362 { | |
| 363 Debug.WriteLineIf(TS.TraceError, string.Format("OnWebOperationException; op={0}/{1}; ex={2}", Url, methodName, ex)); | |
| 364 var handler = (EventHandler<WebOperationExceptionEventArgs>)Events[EventWebOperationException] ?? WebOperationExceptionDefaultHandler; | |
| 365 | |
| 366 if (handler != null) | |
| 367 { | |
| 368 var ea = new WebOperationExceptionEventArgs(Url, methodName, parameters, ex); | |
| 369 handler(this, ea); | |
| 370 return ea.Retry; | |
| 371 } | |
| 372 | |
| 373 throw new TargetInvocationException(methodName, ex); | |
| 374 } | |
| 375 | |
| 376 #endregion | |
| 377 | |
| 378 #region Cookies | |
| 379 | |
| 380 private string _cookie; | |
| 381 | |
| 382 /// <summary> | |
| 383 /// Creates a <see cref="T:System.Net.WebRequest"/> for the specified uri. | |
| 384 /// </summary> | |
| 385 /// <returns> The <see cref="T:System.Net.WebRequest"/>. </returns> | |
| 386 /// <param name="uri">The <see cref="T:System.Uri"></see> to use when creating the <see cref="T:System.Net.WebRequest"></see>. </param> | |
| 387 /// <exception cref="T:System.InvalidOperationException">The uri parameter is null. </exception> | |
| 388 protected override WebRequest GetWebRequest(Uri uri) | |
| 389 { | |
| 390 var webRequest = base.GetWebRequest(uri); | |
| 391 PrepareRequest(webRequest); | |
| 392 return webRequest; | |
| 393 } | |
| 394 | |
| 395 /// <summary> | |
| 396 /// Returns a response from a synchronous request to an XML Web service method. | |
| 397 /// </summary> | |
| 398 /// <returns> The <see cref="T:System.Net.WebResponse"/>. </returns> | |
| 399 /// <param name="request">The <see cref="T:System.Net.WebRequest"/> | |
| 400 /// from which to get the response. </param> | |
| 401 protected override WebResponse GetWebResponse(WebRequest request) | |
| 402 { | |
| 403 var response = (HttpWebResponse)base.GetWebResponse(request); | |
| 404 ProcessResponse(response); | |
| 405 return response; | |
| 406 } | |
| 407 | |
| 408 /// <summary> | |
| 409 /// Returns a response from an asynchronous request to an XML Web service method. | |
| 410 /// </summary> | |
| 411 /// <returns> The <see cref="T:System.Net.WebResponse"/>.</returns> | |
| 412 /// <param name="result">The <see cref="T:System.IAsyncResult"/> to pass to | |
| 413 /// <see cref="M:System.Net.HttpWebRequest.EndGetResponse(System.IAsyncResult)"/> when the response has completed. </param> | |
| 414 /// <param name="request">The <see cref="T:System.Net.WebRequest"/> from which to get the response. </param> | |
| 415 protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) | |
| 416 { | |
| 417 var response = (HttpWebResponse)base.GetWebResponse(request, result); | |
| 418 ProcessResponse(response); | |
| 419 return response; | |
| 420 } | |
| 421 | |
| 422 private void ProcessResponse(HttpWebResponse response) | |
| 423 { | |
| 424 if (response.StatusCode == HttpStatusCode.MovedPermanently) | |
| 425 { | |
| 426 var redirectedLocation = response.Headers["Location"]; | |
| 427 Url = new Uri(new Uri(Url), redirectedLocation).AbsoluteUri; | |
| 428 throw new WebException(redirectedLocation, WebExceptionStatus.ReceiveFailure); | |
| 429 } | |
| 430 | |
| 431 var cookies = response.Headers.GetValues("Set-Cookie"); | |
| 432 | |
| 433 if (cookies == null) | |
| 434 return; | |
| 435 | |
| 436 foreach (var cookie in cookies) | |
| 437 { | |
| 438 if (cookie.StartsWith("ASP.NET_SessionId=", StringComparison.Ordinal)) | |
| 439 { | |
| 440 _cookie = cookie; | |
| 441 break; | |
| 442 } | |
| 443 } | |
| 444 } | |
| 445 | |
| 446 private void PrepareRequest(WebRequest request) | |
| 447 { | |
| 448 if (!string.IsNullOrEmpty(_cookie)) | |
| 449 request.Headers.Add("Cookie", _cookie); | |
| 450 } | |
| 451 | |
| 452 #endregion | |
| 453 | |
| 454 #region Debug | |
| 455 | |
| 456 private static TraceSwitch _ts; | |
| 457 internal static TraceSwitch TS | |
| 458 { | |
| 459 get { return _ts ?? (_ts = new TraceSwitch("WebServiceClient", "Web service client trace switch")); } | |
| 460 } | |
| 461 | |
| 462 #endregion | |
| 463 | |
| 464 } | |
| 465 } |
