Statistics
| Branch: | Revision:

root / trunk / hammock / src / net35 / Hammock / Web / WebQuery.cs @ 0eea575a

History | View | Annotate | Download (70.2 kB)

1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Linq;
6
using System.Net;
7
using System.Reflection;
8
using System.Text;
9
using Hammock.Attributes.Specialized;
10
using Hammock.Caching;
11
using Hammock.Extensions;
12
using Hammock.Validation;
13
using Hammock.Web.Mocks;
14

    
15
#if SILVERLIGHT
16
using Hammock.Silverlight.Compat;
17
using System.IO.IsolatedStorage;
18
#endif
19

    
20
#if SILVERLIGHT && !WindowsPhone
21
using System.Windows.Browser;
22
using System.Net.Browser;
23
#endif
24

    
25
namespace Hammock.Web
26
{
27
    public abstract partial class WebQuery: IDisposable
28
    {
29
        private const string AcceptEncodingHeader = "Accept-Encoding";
30
        private static readonly object _sync = new object();
31
        private readonly WebHeaderCollection _restrictedHeaders = new WebHeaderCollection(0);
32

    
33
        public virtual Encoding Encoding { get; protected internal set; }
34
        public virtual IWebQueryInfo Info { get; protected set; }
35
        public virtual string UserAgent { get; protected internal set; }
36
        public virtual WebHeaderCollection Headers { get; protected set; }
37
        public virtual WebParameterCollection Parameters { get; protected set; }
38
        public virtual WebParameterCollection Cookies { get; protected set; }
39

    
40
        private WebEntity _entity;
41
        protected internal virtual WebEntity Entity
42
        {
43
            get
44
            {
45
                return _entity;
46
            }
47
            set
48
            {
49
                _entity = value;
50
                HasEntity = _entity != null;
51
            }
52
        }
53
        
54
        public virtual WebMethod Method { get; set; }
55
        public virtual string Proxy { get; set; }
56
        public virtual string AuthorizationHeader { get; internal set; }
57
        public DecompressionMethods? DecompressionMethods { get; set; }
58
        public virtual TimeSpan? RequestTimeout { get; set; }
59
        public virtual WebQueryResult Result { get; internal set; }
60
        public virtual object UserState { get; internal set; }
61

    
62
#if SILVERLIGHT
63
        public virtual bool HasElevatedPermissions { get; set; }
64

    
65
        // [DC]: Headers to use when access isn't direct
66
        public virtual string SilverlightUserAgentHeader { get; set; }
67
        public virtual string SilverlightAcceptEncodingHeader { get; set; }        
68
#endif
69
        
70
#if !Silverlight
71
        public virtual ServicePoint ServicePoint { get; set; }
72
        public virtual bool KeepAlive { get; set; }
73
        public virtual bool FollowRedirects { get; internal set; }
74
#endif
75

    
76
        private WebResponse _webResponse;
77
        public virtual WebResponse WebResponse
78
        {
79
            get
80
            {
81
                lock (_sync)
82
                {
83
                    return _webResponse;
84
                }
85
            }
86
            set
87
            {
88
                lock (_sync)
89
                {
90
                    _webResponse = value;
91
                }
92
            }
93
        }
94

    
95
        protected virtual Stream ContentStream { get; set; }
96
        public virtual bool HasEntity { get; set; }
97
        public virtual byte[] PostContent { get; set; }
98

    
99
#if SL3 || SL4
100
        static WebQuery()
101
        {
102
            // [DC]: Opt-in to the networking stack so we can get headers for proxies
103
            WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
104
            WebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
105
        }
106
#endif
107

    
108
        protected WebQuery(bool enableTrace) : this(null, enableTrace)
109
        {
110

    
111
        }
112

    
113
        protected WebQuery(IWebQueryInfo info, bool enableTrace)
114
        {
115
            TraceEnabled = enableTrace;
116
            SetQueryMeta(info);
117
            InitializeResult();
118
        }
119

    
120
        protected bool TraceEnabled { get; private set; }
121

    
122
        private void SetQueryMeta(IWebQueryInfo info)
123
        {
124
            Cookies = new WebParameterCollection(0);
125

    
126
            if(info == null)
127
            {
128
                Headers = new WebHeaderCollection(0);
129
                Parameters = new WebParameterCollection(0);
130
                return;
131
            }
132

    
133
            Info = info;
134
            IEnumerable<PropertyInfo> properties;
135
            IDictionary<string, string> transforms;
136

    
137
            ParseTransforms(out properties, out transforms);
138
            Headers = ParseInfoHeaders(properties, transforms);
139
            Parameters = ParseInfoParameters(properties, transforms);
140
            ParseUserAgent(properties);
141
            ParseWebEntity(properties);
142
        }
143

    
144
        private void ParseTransforms(out IEnumerable<PropertyInfo> properties, 
145
                                     out IDictionary<string, string> transforms)
146
        {
147
            properties = Info.GetType().GetProperties();
148
            transforms = new Dictionary<string, string>(0);
149
            Info.ParseValidationAttributes(properties, transforms);
150
        }
151

    
152
        private void InitializeResult()
153
        {
154
            Result = new WebQueryResult();
155
            QueryRequest += (s, e) => SetRequestResults(e);
156
            QueryResponse += (s, e) => SetResponseResults(e);
157
        }
158

    
159
        private void SetResponseResults(WebQueryResponseEventArgs e)
160
        {
161
            Result.ContentStream = e.Response;
162
            Result.ResponseDate = DateTime.UtcNow;
163
            Result.RequestHttpMethod = Method.ToUpper();
164
            Result.IsMock = WebResponse is MockHttpWebResponse;
165
            Result.TimedOut = TimedOut;
166

    
167
            string version;
168
            int statusCode;
169
            string statusDescription;
170
            System.Net.WebHeaderCollection headers;
171
            string contentType;
172
            long contentLength;
173
            Uri responseUri;
174
            CastWebResponse(
175
                out version, out statusCode, out statusDescription, out headers, 
176
                out contentType, out contentLength, out responseUri
177
                );
178
			
179
#if !MonoTouch
180
            TraceResponse(
181
                responseUri, version, headers, statusCode, statusDescription
182
                );
183
#endif
184

    
185
            Result.WebResponse = WebResponse;
186
            Result.ResponseHttpStatusCode = statusCode;
187
            Result.ResponseHttpStatusDescription = statusDescription;
188
            Result.ResponseType = contentType;
189
            Result.ResponseLength = contentLength;
190
            Result.ResponseUri = responseUri;
191
            Result.Exception = e.Exception;
192
        }
193

    
194
#if !MonoTouch
195
		[Conditional("TRACE")]
196
        private void TraceResponse(Uri uri, string version, System.Net.WebHeaderCollection headers, int statusCode, string statusDescription)
197
        {
198
            if(!TraceEnabled)
199
            {
200
                return;
201
            }
202

    
203
            Trace.WriteLine(
204
                String.Concat("\r\n--RESPONSE:", " ", uri)
205
                );
206
            Trace.WriteLine(
207
                String.Concat(version, " ", statusCode, " ", statusDescription)
208
                );
209
            foreach (var trace in headers.AllKeys.Select(
210
                key => String.Concat(key, ": ", headers[key])))
211
            {
212
                Trace.WriteLine(trace);
213
            }
214
        }
215
#endif
216

    
217
        private void SetRequestResults(WebQueryRequestEventArgs e)
218
        {
219
            Result.RequestDate = DateTime.UtcNow;
220
            Result.RequestUri = new Uri(e.Request);
221
#if !SILVERLIGHT
222
            Result.RequestKeptAlive = KeepAlive;
223
#endif
224
        }
225

    
226
#if !SILVERLIGHT
227
        protected virtual void SetWebProxy(WebRequest request)
228
        {
229
#if !Smartphone && !NETCF
230
            var proxyUriBuilder = new UriBuilder(Proxy);
231
            request.Proxy = new WebProxy(proxyUriBuilder.Host,
232
                                         proxyUriBuilder.Port);
233

    
234
            if (!proxyUriBuilder.UserName.IsNullOrBlank())
235
            {
236
                request.Headers["Proxy-Authorization"] = WebExtensions.ToBasicAuthorizationHeader(proxyUriBuilder.UserName,
237
                                                                                             proxyUriBuilder.Password);
238
            }
239
#else
240
          var uri = new Uri(Proxy);
241
            request.Proxy = new WebProxy(uri.Host, uri.Port);
242
            var userParts = uri.UserInfo.Split(new[] { ':' }).Where(ui => !ui.IsNullOrBlank()).ToArray();
243
            if (userParts.Length == 2)
244
            {
245
                request.Proxy.Credentials = new NetworkCredential(userParts[0], userParts[1]);
246
            }
247
#endif
248
        }
249
#endif
250

    
251
        protected virtual WebRequest BuildPostOrPutWebRequest(PostOrPut method, string url, out byte[] content)
252
        {
253
            return !HasEntity
254
                       ? BuildPostOrPutFormWebRequest(method, url, out content)
255
                       : BuildPostOrPutEntityWebRequest(method, url, out content);
256
        }
257

    
258
        protected virtual Func<string, string> BeforeBuildPostOrPutFormWebRequest()
259
        {
260
            return url => AppendParameters(url).Replace(url + "?", "");
261
        }
262
        
263
        protected virtual WebRequest BuildPostOrPutFormWebRequest(PostOrPut method, string url, out byte[] content)
264
        {
265
            var post = BeforeBuildPostOrPutFormWebRequest().Invoke(url);
266

    
267
            var request = WebRequest.Create(url);
268

    
269
            AuthenticateRequest(request);
270

    
271
            SetMethod(method.ToString(), request);
272

    
273
            // It should be possible to override the content type in the case of AddPostContent
274
            var hasContentType = Headers.AllKeys.Where(
275
                key => key.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase)
276
                ).Count() > 0;
277
            
278
            if(!hasContentType)
279
            {
280
                request.ContentType = "application/x-www-form-urlencoded";
281
            }
282

    
283
            HandleRequestMeta(request);
284
			
285
#if !MonoTouch
286
            TraceRequest(request);
287
#endif
288
            content = BuildPostOrPutContent(request, post);
289

    
290
#if !SILVERLIGHT
291
            request.ContentLength = content.Length;
292
#endif
293
            return request;
294
        }
295

    
296
        protected virtual byte[] BuildPostOrPutContent(WebRequest request, string post)
297
        {
298
            var encoding = Encoding ?? Encoding.UTF8;
299

    
300
            var content = PostContent ?? encoding.GetBytes(post);
301

    
302
#if TRACE
303
            Trace.WriteLineIf(TraceEnabled, string.Concat("\r\n", content));            
304
#endif
305
			return content;
306
        }
307

    
308
        protected virtual Func<string, string> BeforeBuildPostOrPutEntityWebRequest()
309
        {
310
            return AppendParameters;
311
        }
312

    
313
        protected virtual WebRequest BuildPostOrPutEntityWebRequest(PostOrPut method, string url, out byte[] content)
314
        {
315
            url = BeforeBuildPostOrPutEntityWebRequest().Invoke(url);
316

    
317
            var request = WebRequest.Create(url);
318

    
319
            SetMethod(method.ToString(), request);
320
            
321
            AuthenticateRequest(request);
322

    
323
            HandleRequestMeta(request);
324
			
325
#if !MonoTouch
326
            TraceRequest(request);
327
#endif
328

    
329
            if (Entity != null)
330
            {
331
                var entity = Entity.Content;
332

    
333
                var encoding = Entity.ContentEncoding ?? Encoding ?? Encoding.UTF8;
334

    
335
                content = encoding.GetBytes(entity);
336

    
337
                request.ContentType = Entity.ContentType;
338
#if TRACE
339
                Trace.WriteLineIf(TraceEnabled, string.Concat("\r\n", entity));
340
#endif
341
                
342
#if !SILVERLIGHT 
343
                // [DC]: This is set by Silverlight
344
                request.ContentLength = content.Length;
345
#endif
346
            }
347
            else
348
            {
349
                using(var ms = new MemoryStream())
350
                {
351
                    content = ms.ToArray();
352
                }
353
            }
354

    
355
            return request;
356
        }
357

    
358
        protected virtual Func<string, string> BeforeBuildGetDeleteHeadOptionsWebRequest()
359
        {
360
            return AppendParameters;
361
        }
362

    
363
        protected virtual WebRequest BuildGetDeleteHeadOptionsWebRequest(GetDeleteHeadOptions method, string url)
364
        {
365
            url = BeforeBuildGetDeleteHeadOptionsWebRequest().Invoke(url);
366

    
367
            var request = WebRequest.Create(url);
368

    
369
            SetMethod(method.ToString(), request);
370
            
371
            AuthenticateRequest(request);
372

    
373
            HandleRequestMeta(request);
374
			
375
#if !MonoTouch
376
            TraceRequest(request);
377
#endif
378
            return request;
379
        }
380

    
381
        private void SetMethod(string method, WebRequest request)
382
        {
383
            request.Method = method.ToUpper();
384
        }
385

    
386
        private void HandleRequestMeta(WebRequest request)
387
        {
388
            // [DC] LSP violation necessary for "pure" mocks
389
            if (request is HttpWebRequest)
390
            {
391
                SetRequestMeta((HttpWebRequest)request);
392
            }
393
            else
394
            {
395
                AppendHeaders(request);
396
                SetUserAgent(request);
397
            }
398
        }
399

    
400
        protected virtual void SetUserAgent(WebRequest request)
401
        {
402
            if (!UserAgent.IsNullOrBlank())
403
            {
404
#if SILVERLIGHT && !WindowsPhone
405
                // [DC] User-Agent is still restricted in elevated mode
406
                request.Headers[SilverlightUserAgentHeader ?? "X-User-Agent"] = UserAgent;
407
#else
408
                if(request is HttpWebRequest)
409
                {
410
                    ((HttpWebRequest) request).UserAgent = UserAgent;
411
                }
412
                else
413
                {
414
                    request.Headers["User-Agent"] = UserAgent;
415
                }
416
#endif
417
            }
418
        }
419

    
420
        protected virtual void SetRequestMeta(HttpWebRequest request)
421
        {
422
            AppendHeaders(request);
423
            AppendCookies(request);
424

    
425
#if !SILVERLIGHT
426
            if (ServicePoint != null)
427
            {
428
#if !Smartphone  && !NETCF
429
                request.ServicePoint.ConnectionLeaseTimeout = ServicePoint.ConnectionLeaseTimeout;
430
                request.ServicePoint.ReceiveBufferSize = ServicePoint.ReceiveBufferSize;
431
                request.ServicePoint.UseNagleAlgorithm = ServicePoint.UseNagleAlgorithm;
432
                request.ServicePoint.BindIPEndPointDelegate = ServicePoint.BindIPEndPointDelegate;
433
#endif
434
              request.ServicePoint.ConnectionLimit = ServicePoint.ConnectionLimit;
435
                request.ServicePoint.Expect100Continue = ServicePoint.Expect100Continue;
436
                request.ServicePoint.MaxIdleTime = ServicePoint.MaxIdleTime;
437
            }
438
#endif
439

    
440
#if !SILVERLIGHT
441
            if (!Proxy.IsNullOrBlank())
442
            {
443
                SetWebProxy(request);
444
            }
445
            request.AllowAutoRedirect = FollowRedirects;
446
#endif
447

    
448
            SetUserAgent(request);
449

    
450
            if (DecompressionMethods.HasValue)
451
            {
452
                var decompressionMethods = DecompressionMethods.Value;
453

    
454
#if !SILVERLIGHT && !WindowsPhone
455
                request.AutomaticDecompression = decompressionMethods;
456
#else
457

    
458
#if !WindowsPhone
459
                if (HasElevatedPermissions)
460
                {
461
#endif
462
                switch (decompressionMethods)
463
                {
464
                    case Silverlight.Compat.DecompressionMethods.GZip:
465
                        request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "gzip";
466
                        break;
467
                    case Silverlight.Compat.DecompressionMethods.Deflate:
468
                        request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "deflate";
469
                        break;
470
                    case Silverlight.Compat.DecompressionMethods.GZip | Silverlight.Compat.DecompressionMethods.Deflate:
471
                        request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "gzip,deflate";
472
                        break;
473
                }
474

    
475
#if !WindowsPhone
476
                }
477
                else
478
                {
479
                    switch (decompressionMethods)
480
                    {
481
                        case Silverlight.Compat.DecompressionMethods.GZip:
482
                            request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "gzip";
483
                            break;
484
                        case Silverlight.Compat.DecompressionMethods.Deflate:
485
                            request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "deflate";
486
                            break;
487
                        case Silverlight.Compat.DecompressionMethods.GZip | Silverlight.Compat.DecompressionMethods.Deflate:
488
                            request.Headers[SilverlightAcceptEncodingHeader ?? "X-Accept-Encoding"] = "gzip,deflate";
489
                            break;
490
                    }
491
                }
492
#endif
493

    
494
#endif
495
            }
496
#if !SILVERLIGHT
497
            if (RequestTimeout.HasValue)
498
            {
499
                // [DC] Need to synchronize these as Timeout is ignored in async requests
500
                request.Timeout = (int)RequestTimeout.Value.TotalMilliseconds;
501
                request.ReadWriteTimeout = (int)RequestTimeout.Value.TotalMilliseconds;
502
            }
503

    
504
            if (KeepAlive)
505
            {
506
                request.KeepAlive = true;
507
            }
508
#endif
509
        }
510

    
511
        private void AppendCookies(HttpWebRequest request)
512
        {
513
#if !NETCF
514
            request.CookieContainer = new CookieContainer();
515

    
516
            foreach(var cookie in Cookies.OfType<HttpCookieParameter>())
517
            {
518
                var value = new Cookie(cookie.Name, cookie.Value);
519
                if(cookie.Domain != null)
520
                {
521
                    request.CookieContainer.Add(cookie.Domain, value);        
522
                }
523
#if !SILVERLIGHT
524
                else
525
                {
526
                    request.CookieContainer.Add(value);
527
                }
528
#endif
529
            }
530
#endif
531
        }
532

    
533
        protected virtual void AppendHeaders(WebRequest request)
534
        {
535
            if (!(request is HttpWebRequest) &&
536
                !(request is MockHttpWebRequest))
537
            {
538
                return;
539
            }
540

    
541
            // [DC]: Combine all duplicate headers into CSV
542
            var headers = new Dictionary<string, string>(0);
543
            foreach(var header in Headers)
544
            {
545
                string value;
546
                if(headers.ContainsKey(header.Name))
547
                {
548
                    value = String.Concat(headers[header.Name], ",", header.Value);
549
                    headers.Remove(header.Name);
550
                }
551
                else
552
                {
553
                    value = header.Value;
554
                }
555

    
556
                headers.Add(header.Name, value);
557
            }
558

    
559
            foreach (var header in headers)
560
            {
561
                if (_restrictedHeaderActions.ContainsKey(header.Key))
562
                {
563
                    if(request is HttpWebRequest)
564
                    {
565
#if SILVERLIGHT
566
                    if(header.Key.EqualsIgnoreCase("User-Agent"))
567
                    {
568
                        // [DC]: User-Agent is still restricted in elevated mode
569
                        request.Headers[SilverlightUserAgentHeader ?? "X-User-Agent"] = UserAgent;
570
                        continue;
571
                    }
572
#endif
573
                        _restrictedHeaderActions[header.Key].Invoke((HttpWebRequest) request, header.Value);
574
                        _restrictedHeaders.Add(header.Key, header.Value);
575
                    }
576
                    if(request is MockHttpWebRequest)
577
                    {
578
                        AddHeader(header, request);
579
                    }
580
                }
581
                else
582
                {
583
                    AddHeader(header, request);
584
                }
585
            }
586
        }
587
		
588
#if !MonoTouch
589
        [Conditional("TRACE")]
590
        private void TraceHeaders(WebRequest request)
591
        {
592
            if (!TraceEnabled)
593
            {
594
                return;
595
            }
596

    
597
            var restricted = _restrictedHeaders.AllKeys.Select(key => String.Concat(key, ": ", request.Headers[key]));
598
            var remaining = request.Headers.AllKeys.Except(_restrictedHeaders.AllKeys).Select(key => String.Concat(key, ": ", request.Headers[key]));
599
            var all = restricted.ToList();
600
            all.AddRange(remaining);
601
            all.Sort();
602

    
603
            foreach (var trace in all)
604
            {
605
                Trace.WriteLine(trace);
606
            }
607
        }
608
#endif
609

    
610
        private static void AddHeader(KeyValuePair<string, string> header, WebRequest request)
611
        {
612
#if !SILVERLIGHT
613
            request.Headers.Add(header.Key, header.Value);
614
#else
615
            request.Headers[header.Key] = header.Value;
616
#endif
617
        }
618

    
619
#if !SILVERLIGHT
620
        private readonly IDictionary<string, Action<HttpWebRequest, string>> _restrictedHeaderActions
621
            = new Dictionary<string, Action<HttpWebRequest, string>>(StringComparer.OrdinalIgnoreCase)
622
                  {
623
                      {"Accept", (r, v) => r.Accept = v},
624
                      {"Connection", (r, v) => r.Connection = v},
625
                      {"Content-Length", (r, v) => r.ContentLength = Convert.ToInt64(v)},
626
                      {"Content-Type", (r, v) => r.ContentType = v},
627
                      {"Expect", (r, v) => r.Expect = v},
628
                      {"Date", (r, v) => { /* Set by system */ }},
629
                      {"Host", (r, v) => { /* Set by system */ }},
630
                      {"If-Modified-Since", (r, v) => r.IfModifiedSince = Convert.ToDateTime(v)},
631
                      {"Range", (r, v) => { throw new NotSupportedException( /* r.AddRange() */); }},
632
                      {"Referer", (r, v) => r.Referer = v},
633
                      {"Transfer-Encoding", (r, v) => { r.TransferEncoding = v; r.SendChunked = true; }},
634
                      {"User-Agent", (r, v) => r.UserAgent = v }
635
                  };
636
#else
637
        private readonly IDictionary<string, Action<HttpWebRequest, string>> _restrictedHeaderActions
638
            = new Dictionary<string, Action<HttpWebRequest, string>>(StringComparer.OrdinalIgnoreCase) {
639
                      { "Accept",            (r, v) => r.Accept = v },
640
                      { "Connection",        (r, v) => { /* Set by Silverlight */ }},           
641
                      { "Content-Length",    (r, v) => { /* Set by Silverlight */ }},
642
                      { "Content-Type",      (r, v) => r.ContentType = v },
643
                      { "Expect",            (r, v) => { /* Set by Silverlight */ }},
644
                      { "Date",              (r, v) => { /* Set by Silverlight */ }},
645
                      { "Host",              (r, v) => { /* Set by Silverlight */ }},
646
                      { "If-Modified-Since", (r, v) => { /* Not supported */ }},
647
                      { "Range",             (r, v) => { /* Not supported */ }},
648
                      { "Referer",           (r, v) => { /* Not supported */ }},
649
                      { "Transfer-Encoding", (r, v) => { /* Not supported */ }},
650
                      { "User-Agent",        (r, v) => { /* Not supported here */  }}             
651
                  };
652
#endif
653

    
654
        protected virtual string AppendParameters(string url)
655
        {
656
            var count = 0;
657

    
658
            var parameters = Parameters.Where(
659
                parameter => !(parameter is HttpPostParameter) || Method == WebMethod.Post).Where(
660
                parameter => !string.IsNullOrEmpty(parameter.Name) && !string.IsNullOrEmpty(parameter.Value)
661
                );
662

    
663
            foreach (var parameter in parameters)
664
            {
665
                // GET parameters in URL
666
                url = url.Then(count > 0 || url.Contains("?") ? "&" : "?");
667
                url = url.Then("{0}={1}".FormatWith(parameter.Name, parameter.Value.UrlEncode()));
668
                count++;
669
            }
670

    
671
            return url;
672
        }
673

    
674
        // [DC] Headers don't need to be unique, this should change
675
        protected virtual WebHeaderCollection ParseInfoHeaders(IEnumerable<PropertyInfo> properties,
676
                                                               IDictionary<string, string> transforms)
677
        {
678
            var headers = new Dictionary<string, string>();
679
            
680
            Info.ParseNamedAttributes<HeaderAttribute>(properties, transforms, headers);
681

    
682
            var collection = new WebHeaderCollection();
683
            headers.ForEach(p => collection.Add(new WebHeader(p.Key, p.Value)));
684

    
685
            return collection;
686
        }
687

    
688
        protected virtual WebParameterCollection ParseInfoParameters(IEnumerable<PropertyInfo> properties,
689
                                                                     IDictionary<string, string> transforms)
690
        {
691
            var parameters = new Dictionary<string, string>();
692
            
693
            Info.ParseNamedAttributes<ParameterAttribute>(properties, transforms, parameters);
694

    
695
            var collection = new WebParameterCollection();
696
            parameters.ForEach(p => collection.Add(new WebParameter(p.Key, p.Value)));
697

    
698
            return collection;
699
        }
700

    
701
        protected virtual WebParameterCollection ParseInfoParameters()
702
        {
703
            IEnumerable<PropertyInfo> properties;
704
            IDictionary<string, string> transforms;
705
            ParseTransforms(out properties, out transforms);
706
            return ParseInfoParameters(properties, transforms);
707
        }
708

    
709
        private void ParseUserAgent(IEnumerable<PropertyInfo> properties)
710
        {
711
            var count = 0;
712
            foreach (var property in properties)
713
            {
714
                var attributes = property.GetCustomAttributes<UserAgentAttribute>(true);
715
                count += attributes.Count();
716
                if (count > 1)
717
                {
718
                    throw new ArgumentException("Cannot declare more than one user agent per query");
719
                }
720

    
721
                if (count < 1)
722
                {
723
                    continue;
724
                }
725

    
726
                if (!UserAgent.IsNullOrBlank())
727
                {
728
                    continue;
729
                }
730

    
731
                var value = property.GetValue(Info, null);
732
                UserAgent = value != null ? value.ToString() : null;
733
            }
734
        }
735

    
736
        private void ParseWebEntity(IEnumerable<PropertyInfo> properties)
737
        {
738
            if (Entity != null)
739
            {
740
                // Already set by client or request
741
                return;
742
            }
743

    
744
            var count = 0;
745
            foreach (var property in properties)
746
            {
747
                var attributes = property.GetCustomAttributes<EntityAttribute>(true);
748
                count += attributes.Count();
749
                if (count > 1)
750
                {
751
                    throw new ValidationException("Cannot declare more than one entity per query");
752
                }
753

    
754
                if (count < 1)
755
                {
756
                    continue;
757
                }
758

    
759
                if (Entity != null)
760
                {
761
                    // Already set in this pass
762
                    continue;
763
                }
764

    
765
                var value = property.GetValue(Info, null);
766

    
767
                var content = value != null ? value.ToString() : null;
768
                var contentEncoding = attributes.Single().ContentEncoding;
769
                var contentType = attributes.Single().ContentType;
770

    
771
                Entity = new WebEntity
772
                {
773
                    Content = content,
774
                    ContentEncoding = contentEncoding,
775
                    ContentType = contentType
776
                };
777
            }
778
        }
779

    
780
        protected void HandleWebException(WebException exception)
781
        {
782
            if (!(exception.Response is HttpWebResponse))
783
            {
784
                return;
785
            }
786

    
787
            var response = exception.Response;
788
#if SILVERLIGHT
789
            if (DecompressionMethods == Silverlight.Compat.DecompressionMethods.GZip ||
790
                DecompressionMethods == Silverlight.Compat.DecompressionMethods.Deflate ||
791
                DecompressionMethods == (Silverlight.Compat.DecompressionMethods.GZip | Silverlight.Compat.DecompressionMethods.Deflate)
792
                )
793
            {
794
                response = new GzipHttpWebResponse((HttpWebResponse)response);
795
            }
796
#endif
797
            WebResponse = response;
798
            var stream = WebResponse.GetResponseStream();
799

    
800
            if (stream == null)
801
            {
802
                return;
803
            }
804

    
805
            var args = new WebQueryResponseEventArgs(stream, exception);
806
            OnQueryResponse(args);
807
        }
808

    
809
        protected abstract void SetAuthorizationHeader(WebRequest request, string header);
810

    
811
        protected abstract void AuthenticateRequest(WebRequest request);
812

    
813
        public abstract string GetAuthorizationContent();
814

    
815
        private static string CreateCacheKey(string prefix, string url)
816
        {
817
            return !prefix.IsNullOrBlank() ? "{0}_{1}".FormatWith(prefix, url) : url;
818
        }
819

    
820
        protected virtual void ExecuteWithCache(ICache cache,
821
                                                string url,
822
                                                string key,
823
                                                Action<ICache, string> cacheScheme)
824
        {
825
            var fetch = cache.Get<Stream>(CreateCacheKey(key, url));
826
            if (fetch != null)
827
            {
828
                // [DC]: In order to build results, an event must still raise
829
                var responseArgs = new WebQueryResponseEventArgs(fetch);
830
                OnQueryResponse(responseArgs);
831
            }
832
            else
833
            {
834
                cacheScheme.Invoke(cache, url);
835
            }
836
        }
837

    
838
        protected virtual void ExecuteWithCacheAndAbsoluteExpiration(ICache cache,
839
                                                                       string url,
840
                                                                       string key,
841
                                                                       DateTime expiry,
842
                                                                       Action<ICache, string, DateTime> cacheScheme)
843
        {
844
            var fetch = cache.Get<Stream>(CreateCacheKey(key, url));
845
            if (fetch != null)
846
            {
847
                // [DC]: In order to build results, an event must still raise
848
                var responseArgs = new WebQueryResponseEventArgs(fetch);
849
                OnQueryResponse(responseArgs);
850
            }
851
            else
852
            {
853
                cacheScheme.Invoke(cache, url, expiry);
854
            }
855
        }
856

    
857
        protected virtual void ExecuteWithCacheAndSlidingExpiration(ICache cache,
858
                                                                      string url,
859
                                                                      string key,
860
                                                                      TimeSpan expiry,
861
                                                                      Action<ICache, string, TimeSpan> cacheScheme)
862
        {
863
            var fetch = cache.Get<Stream>(CreateCacheKey(key, url));
864
            if (fetch != null)
865
            {
866
                // [DC]: In order to build results, an event must still raise
867
                var responseArgs = new WebQueryResponseEventArgs(fetch);
868
                OnQueryResponse(responseArgs);
869
            }
870
            else
871
            {
872
                cacheScheme.Invoke(cache, url, expiry);    
873
            }
874
        }
875

    
876
#if !SILVERLIGHT
877
        protected virtual void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, string url, string key, ICache cache, out WebException exception)
878
        {
879
            WebException ex = null;
880
            ExecuteWithCache(cache, url, key, (c, u) => ExecuteGetDeleteHeadOptions(method, cache, url, key, out ex));
881
            exception = ex;
882
        }
883

    
884
        protected virtual void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, 
885
                                                             string url, 
886
                                                             string key, 
887
                                                             ICache cache, 
888
                                                             DateTime absoluteExpiration, 
889
                                                             out WebException exception)
890
        {
891
            WebException ex = null; 
892
            ExecuteWithCacheAndAbsoluteExpiration(cache, url, key, absoluteExpiration,
893
                                                            (c, u, e) =>
894
                                                            ExecuteGetDeleteHeadOptions(method, cache, url, key, absoluteExpiration, out ex));
895
            exception = ex;
896
        }
897

    
898
        protected virtual void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, 
899
                                                    string url, 
900
                                                    string key, 
901
                                                    ICache cache, 
902
                                                    TimeSpan slidingExpiration, 
903
                                                    out WebException exception)
904
        {
905
            WebException ex = null; 
906
            ExecuteWithCacheAndSlidingExpiration(cache, url, key, slidingExpiration,
907
                                                           (c, u, e) =>
908
                                                           ExecuteGetDeleteHeadOptions(method, cache, url, key, slidingExpiration, out ex));
909
            exception = ex;
910
        }
911

    
912
        private void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method,
913
                                                 ICache cache, 
914
                                                 string url, 
915
                                                 string key, 
916
                                                 out WebException exception)
917
        {
918
            ExecuteGetDeleteHeadOptions(method, url, out exception);
919
            if (exception == null)
920
            {
921
                cache.Insert(CreateCacheKey(key, url), ContentStream);
922
            }
923
        }
924

    
925
        private void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, 
926
                                                 ICache cache, 
927
                                                 string url, 
928
                                                 string key,
929
                                                 DateTime absoluteExpiration, 
930
                                                 out WebException exception)
931
        {
932
            ExecuteGetDeleteHeadOptions(method, url, out exception);
933
            if (exception == null)
934
            {
935
                cache.Insert(CreateCacheKey(key, url), ContentStream, absoluteExpiration);
936
            }
937
        }
938

    
939
        private void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, ICache cache, string url, string key,
940
                                                   TimeSpan slidingExpiration, out WebException exception)
941
        {
942
            ExecuteGetDeleteHeadOptions(method, url, out exception);
943
            if (exception == null)
944
            {
945
                cache.Insert(CreateCacheKey(key, url), ContentStream, slidingExpiration);
946
            }
947
        }
948
#endif  
949

    
950
        public virtual event EventHandler<WebQueryRequestEventArgs> QueryRequest;
951
        public virtual void OnQueryRequest(WebQueryRequestEventArgs args)
952
        {
953
            var handler = QueryRequest;
954
            if (handler != null)
955
            {
956
                handler(this, args);
957
            }
958
        }
959

    
960
        public virtual event EventHandler<WebQueryResponseEventArgs> QueryResponse;
961
        public virtual void OnQueryResponse(WebQueryResponseEventArgs args)
962
        {
963
            var handler = QueryResponse;
964
            if (handler != null)
965
            {
966
                handler(this, args);
967
            }
968
        }
969

    
970
        internal virtual event EventHandler<PostProgressEventArgs> PostProgress;
971
        internal virtual void OnPostProgress(PostProgressEventArgs args)
972
        {
973
            var handler = PostProgress;
974
            if (handler != null)
975
            {
976
                handler(this, args);
977
            }
978
        }
979
#if !SILVERLIGHT
980
        protected virtual void ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions method, string url, out WebException exception)
981
        {
982
            WebResponse = null;
983
            var request = BuildGetDeleteHeadOptionsWebRequest(method, url);
984
            
985
            var requestArgs = new WebQueryRequestEventArgs(url);
986
            OnQueryRequest(requestArgs);
987

    
988
            ExecuteGetDeleteHeadOptions(request, out exception);
989
        }
990

    
991
        private void ExecuteGetDeleteHeadOptions(WebRequest request, out WebException exception)
992
        {
993
            try
994
            {
995
                // [DC] Avoid disposing until no longer needed to build results
996
                var response = request.GetResponse();
997
                WebResponse = response;
998

    
999
                if (response != null)
1000
                {
1001
                    ContentStream = response.GetResponseStream();
1002
                    if (ContentStream != null)
1003
                    {
1004
                        var args = new WebQueryResponseEventArgs(ContentStream);
1005
                        OnQueryResponse(args);
1006
                    }
1007
                }
1008

    
1009
                exception = null;
1010
            }
1011
            catch (WebException ex)
1012
            {
1013
                exception = ex;
1014
                HandleWebException(ex);
1015
            }
1016
        }
1017
        
1018
#endif
1019
        protected virtual WebRequest BuildMultiPartFormRequest(PostOrPut method, string url, IEnumerable<HttpPostParameter> parameters, out string boundary)
1020
        {
1021
            url = AppendParameters(url);
1022

    
1023
            boundary = Guid.NewGuid().ToString();
1024
            var request = WebRequest.Create(url);
1025
            AuthenticateRequest(request);
1026

    
1027
            request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
1028
            request.Method = method == PostOrPut.Post ? "POST" : "PUT";
1029
            
1030
            HandleRequestMeta(request);
1031
            
1032
#if !MonoTouch
1033
			TraceRequest(request);
1034
#endif
1035
			
1036
            return request;
1037
        }
1038
		
1039
#if !MonoTouch
1040
        [Conditional("TRACE")]
1041
        protected void TraceRequest(WebRequest request)
1042
        {
1043
            if (!TraceEnabled)
1044
            {
1045
                return;
1046
            }
1047

    
1048
            var version = request is HttpWebRequest ?
1049
#if SILVERLIGHT
1050
                "HTTP/v1.1" :
1051
#else
1052
                string.Concat("HTTP/", ((HttpWebRequest)request).ProtocolVersion) :
1053
#endif
1054
 "HTTP/v1.1";
1055
			
1056
            Trace.WriteLine(
1057
                String.Concat("--REQUEST: ", request.RequestUri.Scheme, "://", request.RequestUri.Host)
1058
                );
1059
            var pathAndQuery = String.Concat(
1060
                request.RequestUri.AbsolutePath, string.IsNullOrEmpty(request.RequestUri.Query)
1061
                                                     ? ""
1062
                                                     : string.Concat(request.RequestUri.Query)
1063
                );
1064
            Trace.WriteLine(
1065
                String.Concat(request.Method, " ", pathAndQuery, " ", version
1066
                ));
1067

    
1068
            TraceHeaders(request);
1069
        }
1070
#endif
1071

    
1072
#if !SILVERLIGHT
1073
        protected virtual void ExecutePostOrPut(PostOrPut method, string url, out WebException exception)
1074
        {
1075
            WebResponse = null;
1076
            exception = null;
1077
            byte[] post;
1078
            var request = BuildPostOrPutWebRequest(method, url, out post);
1079

    
1080
            var requestArgs = new WebQueryRequestEventArgs(url);
1081
            OnQueryRequest(requestArgs);
1082

    
1083
            try
1084
            {
1085
                using (var stream = request.GetRequestStream())
1086
                {
1087
                    stream.Write(post, 0, post.Length);
1088
                    stream.Close();
1089

    
1090
#if TRACE
1091
                    var encoding = Encoding ?? new UTF8Encoding();
1092
#if NETCF
1093
                    Trace.WriteLineIf(TraceEnabled, encoding.GetString(post, 0, post.Length));
1094
#else
1095
                    Trace.WriteLineIf(TraceEnabled, encoding.GetString(post));
1096
#endif
1097
#endif
1098

    
1099
                    // [DC] Avoid disposing until no longer needed to build results
1100
                    var response = request.GetResponse();
1101
                    WebResponse = response;
1102

    
1103
                    if (response != null)
1104
                    {
1105
                        ContentStream = response.GetResponseStream();
1106
                        if (ContentStream != null)
1107
                        {
1108
                            var args = new WebQueryResponseEventArgs(ContentStream);
1109
                            OnQueryResponse(args);
1110
                        }
1111
                    }
1112
                }
1113
            }
1114
            catch (WebException ex)
1115
            {
1116
                exception = ex; 
1117
                HandleWebException(ex);
1118
            }
1119
        }
1120

    
1121
        protected virtual void ExecutePostOrPut(PostOrPut method, 
1122
                                                string url, 
1123
                                                IEnumerable<HttpPostParameter> parameters,
1124
                                                out WebException exception)
1125
        {
1126
            WebResponse = null;
1127

    
1128
            string boundary;
1129
            var request = BuildMultiPartFormRequest(method, url, parameters, out boundary);
1130

    
1131
#if !Smartphone
1132
            var encoding = Encoding ?? Encoding.GetEncoding("ISO-8859-1");
1133
#else
1134
            var encoding = Encoding ?? Encoding.GetEncoding(1252);
1135
#endif
1136
            var expected = WriteMultiPartImpl(
1137
                false /* write */, parameters, boundary, encoding, null
1138
                );
1139

    
1140
            request.ContentLength = expected;
1141

    
1142
            try
1143
            {
1144
                using (var requestStream = request.GetRequestStream())
1145
                {
1146
#if DEBUG
1147
					var actual = WriteMultiPartImpl(
1148
                        true /* write */, parameters, boundary, encoding, requestStream
1149
                        );
1150
					
1151
                    Debug.Assert(expected == actual, string.Format("Expected {0} bytes but wrote {1}!", expected, actual));
1152
#else
1153
				WriteMultiPartImpl(
1154
                        true /* write */, parameters, boundary, encoding, requestStream
1155
                        );
1156
#endif
1157

    
1158
                    // [DC] Avoid disposing until no longer needed to build results
1159
                    var response = request.GetResponse();
1160
                    WebResponse = response;
1161

    
1162
                    if (response != null)
1163
                    {
1164
                        ContentStream = response.GetResponseStream();
1165
                        if (ContentStream != null)
1166
                        {
1167
                            var args = new WebQueryResponseEventArgs(ContentStream);
1168
                            OnQueryResponse(args);
1169
                        }
1170
                    }
1171

    
1172
                    exception = null;
1173
                }
1174
            }
1175
            catch (WebException ex)
1176
            {
1177
                exception = ex;
1178
                HandleWebException(ex);
1179
            }
1180
        }
1181
#endif
1182

    
1183
        private static int Write(bool write, Encoding encoding, Stream requestStream, string input)
1184
        {
1185
            var dataBytes = encoding.GetBytes(input);
1186
            if(write)
1187
            {
1188
                requestStream.Write(dataBytes, 0, dataBytes.Length);
1189
            }
1190
            return dataBytes.Length;
1191
        }
1192

    
1193
        private static int WriteLine(bool write, Encoding encoding, Stream requestStream, string input)
1194
        {
1195
            var sb = new StringBuilder();
1196
            sb.AppendLine(input);
1197

    
1198
            var dataBytes = encoding.GetBytes(sb.ToString());
1199
            if (write)
1200
            {
1201
                requestStream.Write(dataBytes, 0, dataBytes.Length);
1202
            }
1203
            return dataBytes.Length;
1204
        }
1205

    
1206
        private long WriteMultiPartImpl(bool write, IEnumerable<HttpPostParameter> parameters, string boundary, Encoding encoding, Stream requestStream)
1207
        {
1208
            Stream fs = null;
1209
            var header = string.Format("--{0}", boundary);
1210
            var footer = string.Format("--{0}--", boundary);
1211
            long written = 0;
1212

    
1213
            foreach (var parameter in parameters)
1214
            {
1215
                written += WriteLine(write, encoding, requestStream, header);
1216
#if TRACE
1217
                if(write)
1218
                {
1219
                    Trace.WriteLineIf(TraceEnabled, header);
1220
                }
1221
#endif
1222
                switch (parameter.Type)
1223
                {
1224
                    case HttpPostParameterType.File:
1225
                        {
1226
                            var disposition = parameter.ContentDisposition ?? "form-data";
1227
                            var fileMask = "Content-Disposition: " + disposition + "; name=\"{0}\"; filename=\"{1}\"";
1228
                            var fileHeader = fileMask.FormatWith(parameter.Name, parameter.FileName);
1229
                            var fileLine = "Content-Type: {0}".FormatWith(parameter.ContentType.ToLower());
1230

    
1231
                            written += WriteLine(write, encoding, requestStream, fileHeader);
1232
                            written += WriteLine(write, encoding, requestStream, fileLine);
1233
                            written += WriteLine(write, encoding, requestStream, "");
1234
#if TRACE
1235
                            if (write)
1236
                            {
1237
                                Trace.WriteLineIf(TraceEnabled, fileHeader);
1238
                                Trace.WriteLineIf(TraceEnabled, fileLine);
1239
                                Trace.WriteLineIf(TraceEnabled, "");
1240
                                Trace.WriteLineIf(TraceEnabled, "[FILE DATA]");
1241
                            }
1242
#endif
1243

    
1244
#if !SILVERLIGHT
1245
                            fs = parameter.FileStream ?? new FileStream(parameter.FilePath, FileMode.Open, FileAccess.Read);
1246
#else
1247
                            if (parameter.FileStream == null)
1248
                            {
1249
                                var store = IsolatedStorageFile.GetUserStoreForApplication();
1250
                                var stream = store.OpenFile(parameter.FilePath, FileMode.Open, FileAccess.Read);
1251
                                parameter.FileStream = stream;
1252
                            }
1253

    
1254
                            fs = parameter.FileStream; // <-- WP7 requires a stream
1255
#endif
1256
                            {
1257
                                if(!write)
1258
                                {
1259
                                    written += fs.Length;
1260
                                }
1261
                                else
1262
                                {
1263
                                    var fileWritten = default(long);
1264
                                    using (var br = new BinaryReader(fs))
1265
                                    {
1266
                                        while (fileWritten < fs.Length)
1267
                                        {
1268
                                            var buffer = br.ReadBytes(8192);
1269
                                            requestStream.Write(buffer, 0, buffer.Length);
1270
                                            written += buffer.Length;
1271
                                            fileWritten += buffer.Length;
1272

    
1273
                                            var args = new PostProgressEventArgs
1274
                                            {
1275
                                                FileName = parameter.FileName,
1276
                                                BytesWritten = fileWritten,
1277
                                                TotalBytes = fs.Length
1278
                                            };
1279
                                            OnPostProgress(args);
1280
                                        }
1281
                                    }
1282
                                }
1283
                            }
1284
                            written += WriteLine(write, encoding, requestStream, "");
1285
                            break;
1286
                        }
1287
                    case HttpPostParameterType.Field:
1288
                        {
1289
                            var fieldLine = "Content-Disposition: form-data; name=\"{0}\"".FormatWith(parameter.Name);
1290
                            
1291
                            written += WriteLine(write, encoding, requestStream, fieldLine);
1292
                            written += WriteLine(write, encoding, requestStream, "");
1293
                            written += WriteLine(write, encoding, requestStream, parameter.Value);
1294
#if TRACE
1295
                            if(write)
1296
                            {
1297
                                Trace.WriteLineIf(TraceEnabled, fieldLine);
1298
                                Trace.WriteLineIf(TraceEnabled, "");
1299
                                Trace.WriteLineIf(TraceEnabled, parameter.Value);
1300
                            }
1301
#endif
1302
                            break;
1303
                        }
1304
                }
1305
            }
1306

    
1307
            written += Write(write, encoding, requestStream, footer);
1308
#if TRACE
1309
            if(write)
1310
            {
1311
                Trace.WriteLineIf(TraceEnabled, footer);
1312
            }
1313
#endif
1314
            if(write)
1315
            {
1316
                requestStream.Flush();
1317
                requestStream.Close();
1318
                if (fs != null)
1319
                {
1320
                    fs.Dispose();
1321
                }
1322
            }
1323

    
1324
            return written;
1325
        }
1326

    
1327
#if !SILVERLIGHT
1328
        public virtual void Request(string url, out WebException exception)
1329
        {
1330
            switch (Method)
1331
            {
1332
                case WebMethod.Get:
1333
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Get, url, out exception);
1334
                    break;
1335
                case WebMethod.Put:
1336
                    ExecutePostOrPut(PostOrPut.Put, url, out exception);
1337
                    break;
1338
                case WebMethod.Post:
1339
                    ExecutePostOrPut(PostOrPut.Post, url, out exception);
1340
                    break;
1341
                case WebMethod.Delete:
1342
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Delete, url, out exception);
1343
                    break;
1344
                case WebMethod.Head:
1345
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Head, url, out exception);
1346
                    break;
1347
                case WebMethod.Options:
1348
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Options, url, out exception);
1349
                    break;
1350
                default:
1351
                    throw new NotSupportedException("Unsupported web method");
1352
            }
1353
        }
1354

    
1355
        public virtual void Request(string url, string key, ICache cache, out WebException exception)
1356
        {
1357
            switch (Method)
1358
            {
1359
                case WebMethod.Get:
1360
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Get, url, key, cache, out exception);
1361
                    break;
1362
                case WebMethod.Put:
1363
                    ExecutePostOrPut(PostOrPut.Put, url, key, cache, out exception);
1364
                    break;
1365
                case WebMethod.Post: 
1366
                    ExecutePostOrPut(PostOrPut.Post, url, key, cache, out exception);
1367
                    break;
1368
                case WebMethod.Delete:
1369
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Delete, url, key, cache, out exception);
1370
                    break;
1371
                case WebMethod.Head:
1372
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Head, url, key, cache,  out exception);
1373
                    break;
1374
                case WebMethod.Options:
1375
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Options, url, key, cache, out exception);
1376
                    break;
1377
                default:
1378
                    throw new NotSupportedException("Unsupported web method");
1379
            }
1380
        }
1381

    
1382
        public virtual void Request(string url, string key, ICache cache, DateTime absoluteExpiration, out WebException exception)
1383
        {
1384
            switch (Method)
1385
            {
1386
                case WebMethod.Get:
1387
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Get, url, key, cache, absoluteExpiration, out exception);
1388
                    break;
1389
                case WebMethod.Put:
1390
                    ExecutePostOrPut(PostOrPut.Put, url, key, cache, absoluteExpiration, out exception);
1391
                    break;
1392
                case WebMethod.Post:
1393
                    ExecutePostOrPut(PostOrPut.Post, url, key, cache, absoluteExpiration, out exception);
1394
                    break;
1395
                case WebMethod.Delete:
1396
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Delete, url, key, cache, absoluteExpiration, out exception);
1397
                    break;
1398
                case WebMethod.Head:
1399
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Head, url, key, cache, absoluteExpiration, out exception);
1400
                    break;
1401
                case WebMethod.Options:
1402
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Options, url, key, cache, absoluteExpiration, out exception);
1403
                    break;
1404
                default:
1405
                    throw new NotSupportedException("Unsupported web method");
1406
            }
1407
        }
1408

    
1409
        public virtual void Request(string url, string key, ICache cache, TimeSpan slidingExpiration, out WebException exception)
1410
        {
1411
            switch (Method)
1412
            {
1413
                case WebMethod.Get:
1414
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Get, url, key, cache, slidingExpiration, out exception);
1415
                    break;
1416
                case WebMethod.Put:
1417
                    ExecutePostOrPut(PostOrPut.Put, url, key, cache, slidingExpiration, out exception);
1418
                    break;
1419
                case WebMethod.Post:
1420
                    ExecutePostOrPut(PostOrPut.Post, url, key, cache, slidingExpiration, out exception);
1421
                    break;
1422
                case WebMethod.Delete:
1423
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Delete, url, key, cache, slidingExpiration, out exception);
1424
                    break;
1425
                case WebMethod.Head:
1426
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Head, url, key, cache, slidingExpiration, out exception);
1427
                    break;
1428
                case WebMethod.Options:
1429
                    ExecuteGetDeleteHeadOptions(GetDeleteHeadOptions.Options, url, key, cache, slidingExpiration, out exception);
1430
                    break;
1431
                default:
1432
                    throw new NotSupportedException("Unsupported web method");
1433
            }
1434
        }
1435

    
1436
        public virtual void Request(string url, IEnumerable<HttpPostParameter> parameters, out WebException exception)
1437
        {
1438
            switch (Method)
1439
            {
1440
                case WebMethod.Put:
1441
                    ExecutePostOrPut(PostOrPut.Put, url, parameters, out exception);
1442
                    break;
1443
                case WebMethod.Post:
1444
                    ExecutePostOrPut(PostOrPut.Post, url, parameters, out exception);
1445
                    break;
1446
                default:
1447
                    throw new NotSupportedException("Only HTTP POSTs and PUTs can use multi-part parameters");
1448
            }
1449
        }
1450
#endif
1451

    
1452
#if !WindowsPhone
1453
        public virtual WebQueryAsyncResult RequestAsync(string url, object userState)
1454
        {
1455
            UserState = userState;
1456

    
1457
            switch (Method)
1458
            {
1459
                case WebMethod.Get:
1460
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, userState);
1461
                case WebMethod.Put:
1462
                    return ExecutePostOrPutAsync(PostOrPut.Put, url, userState);
1463
                case WebMethod.Post:
1464
                    return ExecutePostOrPutAsync(PostOrPut.Post, url, userState);
1465
                case WebMethod.Delete:
1466
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, userState);
1467
                case WebMethod.Head:
1468
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, userState);
1469
                case WebMethod.Options:
1470
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, userState);
1471
                default:
1472
                    throw new NotSupportedException("Unknown web method");
1473
            }
1474
        }
1475
#else
1476
        public virtual void RequestAsync(string url, object userState)
1477
        {
1478
            UserState = userState;
1479

    
1480
            switch (Method)
1481
            {
1482
                case WebMethod.Get:
1483
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, userState);
1484
                    break;
1485
                case WebMethod.Put:
1486
                    ExecutePostOrPutAsync(PostOrPut.Put, url, userState);
1487
                    break;
1488
                case WebMethod.Post:
1489
                    ExecutePostOrPutAsync(PostOrPut.Post, url, userState);
1490
                    break;
1491
                case WebMethod.Delete:
1492
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, userState);
1493
                    break;
1494
                case WebMethod.Head:
1495
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, userState);
1496
                    break;
1497
                case WebMethod.Options:
1498
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, userState);
1499
                    break;
1500
                default:
1501
                    throw new NotSupportedException("Unknown web method");
1502
            }
1503
        }
1504
#endif
1505

    
1506
#if !WindowsPhone
1507
        public virtual WebQueryAsyncResult RequestAsync(string url, 
1508
                                                        string key, 
1509
                                                        ICache cache,
1510
                                                        object userState)
1511
        {
1512
            UserState = userState;
1513

    
1514
            switch (Method)
1515
            {
1516
                case WebMethod.Get:
1517
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, userState);
1518
                case WebMethod.Put:
1519
                    return ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, userState);
1520
                case WebMethod.Post:
1521
                    return ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, userState);
1522
                case WebMethod.Delete:
1523
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, userState);
1524
                case WebMethod.Head:
1525
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, key, cache, userState);
1526
                case WebMethod.Options:
1527
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, key, cache, userState);
1528
                default:
1529
                    throw new NotSupportedException(
1530
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1531
                        );
1532
            }
1533
        }
1534
#else
1535
        public virtual void RequestAsync(string url,
1536
                                         string key,
1537
                                         ICache cache,
1538
                                         object userState)
1539
        {
1540
            UserState = userState;
1541

    
1542
            switch (Method)
1543
            {
1544
                case WebMethod.Get:
1545
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, userState);
1546
                    break;
1547
                case WebMethod.Put:
1548
                    ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, userState);
1549
                    break;
1550
                case WebMethod.Post:
1551
                    ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, userState);
1552
                    break;
1553
                case WebMethod.Delete:
1554
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, userState);
1555
                    break;
1556
                case WebMethod.Head:
1557
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, key, cache, userState);
1558
                    break;
1559
                case WebMethod.Options:
1560
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, key, cache, userState);
1561
                    break;
1562
                default:
1563
                    throw new NotSupportedException(
1564
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1565
                        );
1566
            }
1567
        }
1568
#endif
1569

    
1570
#if !WindowsPhone
1571
        public virtual WebQueryAsyncResult RequestAsync(string url,
1572
                                                        string key, 
1573
                                                        ICache cache, 
1574
                                                        DateTime absoluteExpiration,
1575
                                                        object userState)
1576
        {
1577
            UserState = userState;
1578

    
1579
            switch (Method)
1580
            {
1581
                case WebMethod.Get:
1582
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, absoluteExpiration, userState);
1583
                case WebMethod.Put:
1584
                    return ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, absoluteExpiration, userState);
1585
                case WebMethod.Post:
1586
                    return ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, absoluteExpiration, userState);
1587
                case WebMethod.Delete:
1588
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, absoluteExpiration, userState);
1589
                default:
1590
                    throw new NotSupportedException(
1591
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1592
                        );
1593
            }
1594
        }
1595
#else
1596
        public virtual void RequestAsync(string url,
1597
                                         string key,
1598
                                         ICache cache,
1599
                                         DateTime absoluteExpiration,
1600
                                         object userState)
1601
        {
1602
            UserState = userState;
1603

    
1604
            switch (Method)
1605
            {
1606
                case WebMethod.Get:
1607
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, absoluteExpiration, userState);
1608
                    break;
1609
                case WebMethod.Put:
1610
                    ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, absoluteExpiration, userState);
1611
                    break;
1612
                case WebMethod.Post:
1613
                    ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, absoluteExpiration, userState);
1614
                    break;
1615
                case WebMethod.Delete:
1616
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, absoluteExpiration, userState);
1617
                    break;
1618
                case WebMethod.Head:
1619
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, key, cache, absoluteExpiration, userState);
1620
                    break;
1621
                case WebMethod.Options:
1622
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, key, cache, absoluteExpiration, userState);
1623
                    break;
1624
                default:
1625
                    throw new NotSupportedException(
1626
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1627
                        );
1628
            }
1629
        }
1630
#endif
1631

    
1632
#if !WindowsPhone
1633
        public virtual WebQueryAsyncResult RequestAsync(string url, 
1634
                                                        string key, 
1635
                                                        ICache cache, 
1636
                                                        TimeSpan slidingExpiration,
1637
                                                        object userState)
1638
        {
1639
            UserState = userState;
1640

    
1641
            switch (Method)
1642
            {
1643
                case WebMethod.Get:
1644
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, slidingExpiration, userState);
1645
                case WebMethod.Post:
1646
                    return ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, slidingExpiration, userState);
1647
                case WebMethod.Put:
1648
                    return ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, slidingExpiration, userState);
1649
                case WebMethod.Delete:
1650
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, slidingExpiration, userState);
1651
                case WebMethod.Head:
1652
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, key, cache, slidingExpiration, userState);
1653
                case WebMethod.Options:
1654
                    return ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, key, cache, slidingExpiration, userState);
1655
                default:
1656
                    throw new NotSupportedException(
1657
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1658
                        );
1659
            }
1660
        }
1661
#else
1662
        public virtual void RequestAsync(string url,
1663
                                         string key,
1664
                                         ICache cache,
1665
                                         TimeSpan slidingExpiration,
1666
                                         object userState)
1667
        {
1668
            UserState = userState;
1669

    
1670
            switch (Method)
1671
            {
1672
                case WebMethod.Get:
1673
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Get, url, key, cache, slidingExpiration, userState);
1674
                    break;
1675
                case WebMethod.Post:
1676
                    ExecutePostOrPutAsync(PostOrPut.Post, url, key, cache, slidingExpiration, userState);
1677
                    break;
1678
                case WebMethod.Put:
1679
                    ExecutePostOrPutAsync(PostOrPut.Put, url, key, cache, slidingExpiration, userState);
1680
                    break;
1681
                case WebMethod.Delete:
1682
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Delete, url, key, cache, slidingExpiration, userState);
1683
                    break;
1684
                case WebMethod.Head:
1685
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Head, url, key, cache, slidingExpiration, userState);
1686
                    break;
1687
                case WebMethod.Options:
1688
                    ExecuteGetOrDeleteAsync(GetDeleteHeadOptions.Options, url, key, cache, slidingExpiration, userState);
1689
                    break;
1690
                default:
1691
                    throw new NotSupportedException(
1692
                        "Unsupported web method: {0}".FormatWith(Method.ToUpper())
1693
                        );
1694
            }
1695
        }
1696
#endif
1697

    
1698
#if !WindowsPhone
1699
        public virtual WebQueryAsyncResult RequestAsync(string url, 
1700
                                                        IEnumerable<HttpPostParameter> parameters,
1701
                                                        object userState)
1702
        {
1703
            UserState = userState;
1704

    
1705
            switch (Method)
1706
            {
1707
                case WebMethod.Put:
1708
                    return ExecutePostOrPutAsync(PostOrPut.Put, url, parameters, userState);
1709
                case WebMethod.Post:
1710
                    return ExecutePostOrPutAsync(PostOrPut.Post, url, parameters, userState);
1711
                default:
1712
                    throw new NotSupportedException("Only HTTP POSTS can use multi-part forms");
1713
            }
1714
        }
1715
#else
1716
        public virtual void RequestAsync(string url,
1717
                                         IEnumerable<HttpPostParameter> parameters,
1718
                                         object userState)
1719
        {
1720
            UserState = userState;
1721

    
1722
            switch (Method)
1723
            {
1724
                case WebMethod.Put:
1725
                    ExecutePostOrPutAsync(PostOrPut.Put, url, parameters, userState);
1726
                    break;
1727
                case WebMethod.Post:
1728
                    ExecutePostOrPutAsync(PostOrPut.Post, url, parameters, userState);
1729
                    break;
1730
                default:
1731
                    throw new NotSupportedException("Only HTTP POSTS can use multi-part forms");
1732
            }
1733
        }
1734
#endif
1735

    
1736
#if !SILVERLIGHT
1737
        public virtual void ExecutePostOrPut(PostOrPut method, 
1738
                                               string url, 
1739
                                               string key, 
1740
                                               ICache cache, 
1741
                                               out WebException exception)
1742
        {
1743
            WebException ex = null; 
1744
            ExecuteWithCache(cache, url, key, (c, u) => ExecutePostOrPut(method, cache, url, key, out ex));
1745
            exception = ex;
1746
        }
1747

    
1748
        public virtual void ExecutePostOrPut(PostOrPut method, string url, string key, ICache cache, DateTime absoluteExpiration, out WebException exception)
1749
        {
1750
            WebException ex = null; 
1751
            ExecuteWithCacheAndAbsoluteExpiration(cache, url, key, absoluteExpiration,
1752
                                                            (c, u, e) =>
1753
                                                            ExecutePostOrPut(method, cache, url, key, absoluteExpiration, out ex));
1754
            exception = ex;
1755
        }
1756

    
1757
        public virtual void ExecutePostOrPut(PostOrPut method, string url, string key, ICache cache, TimeSpan slidingExpiration, out WebException exception)
1758
        {
1759
            WebException ex = null; 
1760
            ExecuteWithCacheAndSlidingExpiration(cache, url, key, slidingExpiration,
1761
                                                           (c, u, e) =>
1762
                                                           ExecutePostOrPut(method, cache, url, key, slidingExpiration, out ex));
1763
            exception = ex;
1764
        }
1765

    
1766
        private void ExecutePostOrPut(PostOrPut method, 
1767
                                      ICache cache, 
1768
                                      string url, 
1769
                                      string key, 
1770
                                      out WebException exception)
1771
        {
1772
            ExecutePostOrPut(method, url, out exception);
1773
            if (exception == null)
1774
            {
1775
                cache.Insert(CreateCacheKey(key, url), ContentStream);
1776
            }
1777
        }
1778

    
1779
        private void ExecutePostOrPut(PostOrPut method, 
1780
                                        ICache cache, 
1781
                                        string url, 
1782
                                        string key,
1783
                                        DateTime absoluteExpiration, 
1784
                                        out WebException exception)
1785
        {
1786
            ExecutePostOrPut(method, url, out exception);
1787
            if (exception == null)
1788
            {
1789
                cache.Insert(CreateCacheKey(key, url), ContentStream, absoluteExpiration);
1790
            }
1791
        }
1792

    
1793
        private void ExecutePostOrPut(PostOrPut method, ICache cache, string url, string key,
1794
                                        TimeSpan slidingExpiration, out WebException exception)
1795
        {
1796
            ExecutePostOrPut(method, url, out exception);
1797
            if (exception == null)
1798
            {
1799
                cache.Insert(CreateCacheKey(key, url), ContentStream, slidingExpiration);
1800
            }
1801
        }
1802
#endif
1803

    
1804
        public void Dispose()
1805
        {
1806
            if(ContentStream != null)
1807
            {
1808
                ContentStream.Dispose();
1809
            }
1810
        }
1811
    }
1812

    
1813
    internal class PostProgressEventArgs : EventArgs
1814
    {
1815
        public virtual string FileName { get; set; }
1816
        public virtual long BytesWritten { get; set; }
1817
        public virtual long TotalBytes { get; set; }
1818
    }
1819
}