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 }