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