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