Merge pull request #3389 from lambdageek/bug-43099
[mono.git] / mcs / class / referencesource / System.ServiceModel.Activation / System / ServiceModel / Activation / HostedAspNetEnvironment.cs
1 //----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //----------------------------------------------------------------------------
4
5 namespace System.ServiceModel.Activation
6 {
7     using System.Collections.Generic;
8     using System.Configuration;
9     using System.Diagnostics.CodeAnalysis;
10     using System.Globalization;
11     using System.Net;
12     using System.Runtime;
13     using System.Runtime.CompilerServices;
14     using System.Security;
15     using System.Security.Authentication.ExtendedProtection;
16     using System.Security.Permissions;
17     using System.ServiceModel;
18     using System.ServiceModel.Channels;
19     using System.ServiceModel.Description;
20     using System.Threading;
21     using System.Transactions;
22     using System.Web;
23     using System.Web.Compilation;
24     using System.Web.Configuration;
25
26     class HostedAspNetEnvironment : AspNetEnvironment
27     {
28         // On IIS 8.0 (or later) the "WEBSOCKET_VERSION" server property indicates the WebSocket protocol version supported by the server.
29         // The IIS WebSocket module sets this property when initialized.
30         private const string WebSocketVersionServerProperty = "WEBSOCKET_VERSION";
31
32         // Indicates if we determined the WebSocket version. If false, we'll need to check the "WEBSOCKET_VERSION" server property.
33         private static bool isWebSocketVersionSet = false;
34
35         // Provides the version of the WebSocket protocol supported by IIS.
36         private static string webSocketVersion;
37
38         // used to cache SiteName|ApplicationVirtualPath
39         static string cachedServiceReference;
40
41         // used to cache if windows auth is being used
42         Nullable<bool> isWindowsAuthentication;
43
44         HostedAspNetEnvironment()
45             : base()
46         {
47         }
48
49         public override bool AspNetCompatibilityEnabled
50         {
51             get
52             {
53                 return ServiceHostingEnvironment.AspNetCompatibilityEnabled;
54             }
55         }
56
57         public override string ConfigurationPath
58         {
59             get
60             {
61                 if (ServiceHostingEnvironment.CurrentVirtualPath != null)
62                 {
63                     return ServiceHostingEnvironment.CurrentVirtualPath + "web.config";
64                 }
65                 else
66                 {
67                     return base.ConfigurationPath;
68                 }
69             }
70         }
71
72         public override bool IsConfigurationBased
73         {
74             get
75             {
76                 return ServiceHostingEnvironment.IsConfigurationBased;
77             }
78         }
79
80         public override string CurrentVirtualPath
81         {
82             get
83             {
84                 return ServiceHostingEnvironment.CurrentVirtualPath;
85             }
86         }
87
88         public override string XamlFileBaseLocation
89         {
90             get
91             {
92                 return ServiceHostingEnvironment.XamlFileBaseLocation;
93             }
94         }
95
96         public override bool UsingIntegratedPipeline
97         {
98             get
99             {
100                 return HttpRuntime.UsingIntegratedPipeline;
101             }
102         }
103
104         // Provides the version of the WebSocket protocol supported by IIS.
105         // Returns null if WebSockets are not supported (because the IIS WebSocketModule is not installed or enabled).
106         public override string WebSocketVersion
107         {
108             get
109             {
110                 return isWebSocketVersionSet ? webSocketVersion : null;
111             }
112         }
113
114         public static void Enable()
115         {
116             AspNetEnvironment hostedEnvironment = new HostedAspNetEnvironment();
117             AspNetEnvironment.Current = hostedEnvironment;
118         }
119
120         /// <summary>
121         /// Tries to set the 'WebSocketVersion' property. The first call of this method sets the property (based on the "WEBSOCKET_VERSION" server property). 
122         /// Subsequent calls do nothing.
123         /// </summary>
124         /// <param name="application">The HttpApplication used to determine the WebSocket version.</param>
125         /// <remarks>
126         /// Take caution when calling this method. The method initializes the 'WebSocketVersion' property based on the "WEBSOCKET_VERSION" server variable.
127         /// This variable gets set by the WebSocketModule when it's loaded by IIS. If you call this method too early (before IIS got a chance to load the module list), 
128         /// this method might incorrectly set 'WebSocketVersion' to 'null'.
129         /// </remarks>
130         public static void TrySetWebSocketVersion(HttpApplication application)
131         {
132             if (!isWebSocketVersionSet)
133             {
134                 webSocketVersion = application.Request.ServerVariables[WebSocketVersionServerProperty];
135                 isWebSocketVersionSet = true;
136             }
137         }
138
139         public override void AddHostingBehavior(ServiceHostBase serviceHost, ServiceDescription description)
140         {
141             VirtualPathExtension virtualPathExtension = serviceHost.Extensions.Find<VirtualPathExtension>();
142             if (virtualPathExtension != null)
143             {
144                 description.Behaviors.Add(new HostedBindingBehavior(virtualPathExtension));
145             }
146
147             foreach (ServiceEndpoint endpoint in description.Endpoints)
148             {
149                 if (ServiceMetadataBehavior.IsMetadataEndpoint(description, endpoint))
150                 {
151                     endpoint.Behaviors.Add(new HostedMetadataExchangeEndpointBehavior());
152                 }
153             }
154         }
155
156         public override bool IsWebConfigAboveApplication(object configHostingContext)
157         {
158             AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
159
160             WebContext context = configHostingContext as WebContext;
161             if (context != null)
162             {
163                 return context.ApplicationLevel == WebApplicationLevel.AboveApplication;
164             }
165
166             return false; // if we don't recognize the context we can't enforce the special web.config logic
167         }
168
169         public override void EnsureCompatibilityRequirements(ServiceDescription description)
170         {
171             AspNetCompatibilityRequirementsAttribute aspNetCompatibilityRequirements = description.Behaviors.Find<AspNetCompatibilityRequirementsAttribute>();
172             if (aspNetCompatibilityRequirements == null)
173             {
174                 aspNetCompatibilityRequirements = new AspNetCompatibilityRequirementsAttribute();
175                 description.Behaviors.Add(aspNetCompatibilityRequirements);
176             }
177         }
178
179         public override bool TryGetFullVirtualPath(out string virtualPath)
180         {
181             // subclass will use the virtual path from the compiled string
182             virtualPath = ServiceHostingEnvironment.FullVirtualPath;
183             return true;
184         }
185
186         public override string GetAnnotationFromHost(ServiceHostBase host)
187         {
188             //Format Website name\Application Virtual Path|\relative service virtual path|serviceName 
189             if (host != null && host.Extensions != null)
190             {
191                 string serviceName = (host.Description != null) ? host.Description.Name : string.Empty;
192                 string application = ServiceHostingEnvironment.ApplicationVirtualPath;
193                 string servicePath = string.Empty;
194                 VirtualPathExtension extension = host.Extensions.Find<VirtualPathExtension>();
195                 if (extension != null && extension.VirtualPath != null)
196                 {
197                     servicePath = extension.VirtualPath.Replace("~", application + "|");
198                     return string.Format(CultureInfo.InvariantCulture, "{0}{1}|{2}", ServiceHostingEnvironment.SiteName, servicePath, serviceName);
199                 }
200             }
201             if (string.IsNullOrEmpty(HostedAspNetEnvironment.cachedServiceReference))
202             {
203                 HostedAspNetEnvironment.cachedServiceReference = string.Format(CultureInfo.InvariantCulture, "{0}{1}", ServiceHostingEnvironment.SiteName, ServiceHostingEnvironment.ApplicationVirtualPath);
204             }
205             return HostedAspNetEnvironment.cachedServiceReference;
206         }
207
208         [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
209         public override void EnsureAllReferencedAssemblyLoaded()
210         {
211             AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
212             BuildManager.GetReferencedAssemblies();
213         }
214
215         public override BaseUriWithWildcard GetBaseUri(string transportScheme, Uri listenUri)
216         {
217             BaseUriWithWildcard baseAddress = null;
218             HostedTransportConfigurationBase hostedConfiguration =
219                 HostedTransportConfigurationManager.GetConfiguration(transportScheme) as HostedTransportConfigurationBase;
220             if (hostedConfiguration != null)
221             {
222                 baseAddress = hostedConfiguration.FindBaseAddress(listenUri);
223                 if (baseAddress == null)
224                 {
225                     throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Hosting_TransportBindingNotFound(listenUri.ToString())));
226                 }
227             }
228             return baseAddress;
229         }
230
231         public override void ValidateHttpSettings(string virtualPath, bool isMetadataListener, bool usingDefaultSpnList, ref AuthenticationSchemes bindingElementAuthenticationSchemes, ref ExtendedProtectionPolicy extendedProtectionPolicy, ref string realm)
232         {
233             // Verify the authentication settings
234             AuthenticationSchemes hostSupportedSchemes = HostedTransportConfigurationManager.MetabaseSettings.GetAuthenticationSchemes(virtualPath);
235
236             if ((bindingElementAuthenticationSchemes & hostSupportedSchemes) == 0)
237             {
238                 if (bindingElementAuthenticationSchemes == AuthenticationSchemes.Negotiate ||
239                     bindingElementAuthenticationSchemes == AuthenticationSchemes.Ntlm ||
240                     bindingElementAuthenticationSchemes == AuthenticationSchemes.IntegratedWindowsAuthentication)
241                 {
242                     throw FxTrace.Exception.AsError(new NotSupportedException(SR.Hosting_AuthSchemesRequireWindowsAuth));
243                 }
244                 else
245                 {
246                     throw FxTrace.Exception.AsError(new NotSupportedException(SR.Hosting_AuthSchemesRequireOtherAuth(bindingElementAuthenticationSchemes.ToString())));
247                 }
248             }
249
250             //only use AuthenticationSchemes, which are supported both in IIS and the WCF binding
251             bindingElementAuthenticationSchemes &= hostSupportedSchemes;
252
253             if (bindingElementAuthenticationSchemes != AuthenticationSchemes.Anonymous)
254             {
255                 //Compare the ExtendedProtectionPolicy setttings to IIS
256                 ExtendedProtectionPolicy iisPolicy = HostedTransportConfigurationManager.MetabaseSettings.GetExtendedProtectionPolicy(virtualPath);
257
258                 if (iisPolicy == null) //OS doesn't support CBT
259                 {
260                     if (extendedProtectionPolicy.PolicyEnforcement == PolicyEnforcement.Always)
261                     {
262                         throw FxTrace.Exception.AsError(new NotSupportedException(SR.ExtendedProtectionNotSupported));
263                     }
264                 }
265                 else
266                 {
267                     if (isMetadataListener && ChannelBindingUtility.IsDefaultPolicy(extendedProtectionPolicy))
268                     {
269                         //push the IIS policy onto the metadataListener if and only if the default policy is 
270                         //in force. policy for non metadata listeners will still have to match IIS policy.
271                         extendedProtectionPolicy = iisPolicy;
272                     }
273                     else
274                     {
275                         if (!ChannelBindingUtility.AreEqual(iisPolicy, extendedProtectionPolicy))
276                         {
277                             string mismatchErrorMessage;
278                             if (iisPolicy.PolicyEnforcement != extendedProtectionPolicy.PolicyEnforcement)
279                             {
280                                 mismatchErrorMessage = SR.ExtendedProtectionPolicyEnforcementMismatch(iisPolicy.PolicyEnforcement, extendedProtectionPolicy.PolicyEnforcement);
281                             }
282                             else if (iisPolicy.ProtectionScenario != extendedProtectionPolicy.ProtectionScenario)
283                             {
284                                 mismatchErrorMessage = SR.ExtendedProtectionPolicyScenarioMismatch(iisPolicy.ProtectionScenario, extendedProtectionPolicy.ProtectionScenario);
285                             }
286                             else 
287                             {
288                                 Fx.Assert(iisPolicy.CustomChannelBinding != extendedProtectionPolicy.CustomChannelBinding, "new case in ChannelBindingUtility.AreEqual to account for");
289                                 mismatchErrorMessage = SR.ExtendedProtectionPolicyCustomChannelBindingMismatch;
290                             }
291
292                             if (mismatchErrorMessage != null)
293                             {
294                                 throw FxTrace.Exception.AsError(new NotSupportedException(SR.Hosting_ExtendedProtectionPoliciesMustMatch(mismatchErrorMessage)));
295                             }
296                         }
297
298                         //when using the default SPN list we auto generate, we should make sure that the IIS policy is also the default...
299                         ServiceNameCollection listenerSpnList = usingDefaultSpnList ? null : extendedProtectionPolicy.CustomServiceNames;
300                         if (!ChannelBindingUtility.IsSubset(iisPolicy.CustomServiceNames, listenerSpnList))
301                         {
302                             throw FxTrace.Exception.AsError(new NotSupportedException(SR.Hosting_ExtendedProtectionPoliciesMustMatch(SR.Hosting_ExtendedProtectionSPNListNotSubset)));
303                         }
304                     }
305                 }
306             }
307
308             
309
310             // Do not set realm for Cassini.
311             if (!ServiceHostingEnvironment.IsSimpleApplicationHost)
312             {
313                 // Set the realm
314                 realm = HostedTransportConfigurationManager.MetabaseSettings.GetRealm(virtualPath);
315             }
316         }
317
318         public override bool ValidateHttpsSettings(string virtualPath, ref bool requireClientCertificate)
319         {
320             // Do not validate settings for Cassini. Actually current implementation of Cassini does not support HTTPS.
321             if (ServiceHostingEnvironment.IsSimpleApplicationHost)
322             {
323                 return false;
324             }
325
326             // Validate Ssl Settings
327             HttpAccessSslFlags sslFlags = HostedTransportConfigurationManager.MetabaseSettings.GetAccessSslFlags(virtualPath);
328             HttpAccessSslFlags channelListenerSslFlags = HttpAccessSslFlags.None;
329
330             // Validating SSL flags. SslRequireCert means "require client certificate" in IIS terminology.
331             if ((sslFlags & HttpAccessSslFlags.SslRequireCert) != 0)
332             {
333                 // Require SSL.
334                 // We apply IIS settings to the ChannelListener to fix the endpoint
335                 requireClientCertificate = true;
336             }
337             else if (requireClientCertificate &&
338                 // Validating SSL flags. SslNegotiateCert means "accept client certificate" in IIS terminology.
339                 // We want to allow SslNegotiateCert in IIS to support hosting one endpoint requiring client
340                 // certificates and another endpoint not using client certificates in the same VirtualDirectory.
341                 // HttpsChannelListener.ValidateAuthentication ensures that authentication is denied for services
342                 // requiring client certificates when the client does not present one.
343                 (sslFlags & HttpAccessSslFlags.SslNegotiateCert) == 0)
344             {
345                 // IIS ignores client cert but the binding requires it.
346                 channelListenerSslFlags |= HttpAccessSslFlags.SslRequireCert;
347
348                 throw FxTrace.Exception.AsError(new NotSupportedException(SR.Hosting_SslSettingsMisconfigured(
349                     channelListenerSslFlags.ToString(), sslFlags.ToString())));
350             }
351
352             return (sslFlags & HttpAccessSslFlags.SslMapCert) != 0;
353         }
354
355         public override void ProcessNotMatchedEndpointAddress(Uri uri, string endpointName)
356         {
357             if (!object.ReferenceEquals(uri.Scheme, Uri.UriSchemeHttp) &&
358                 !object.ReferenceEquals(uri.Scheme, Uri.UriSchemeHttps))
359             {
360                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Hosting_NonHTTPInCompatibilityMode(endpointName)));
361             }
362         }
363
364         public override void ValidateCompatibilityRequirements(AspNetCompatibilityRequirementsMode compatibilityMode)
365         {
366             if (compatibilityMode == AspNetCompatibilityRequirementsMode.Allowed)
367             {
368                 return;
369             }
370             else if (ServiceHostingEnvironment.AspNetCompatibilityEnabled &&
371                 compatibilityMode == AspNetCompatibilityRequirementsMode.NotAllowed)
372             {
373                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Hosting_ServiceCompatibilityNotAllowed));
374             }
375             else if (!ServiceHostingEnvironment.AspNetCompatibilityEnabled &&
376                 compatibilityMode == AspNetCompatibilityRequirementsMode.Required)
377             {
378                 throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Hosting_ServiceCompatibilityRequire));
379             }
380         }
381
382         public override IAspNetMessageProperty GetHostingProperty(Message message)
383         {
384             return GetHostingProperty(message, false);
385         }
386
387         public override IAspNetMessageProperty GetHostingProperty(Message message, bool removeFromMessage)
388         {
389             IAspNetMessageProperty result = null;
390             object property;
391
392             if (message.Properties.TryGetValue(HostingMessageProperty.Name, out property))
393             {
394                 result = (HostingMessageProperty)property;
395                 if (removeFromMessage)
396                 {
397                     message.Properties.Remove(HostingMessageProperty.Name);
398                 }
399             }
400
401             return result;
402         }
403
404         public override void PrepareMessageForDispatch(Message message)
405         {
406             ReceiveContext context = null;
407             if (ReceiveContext.TryGet(message, out context) && !(context is ReceiveContextBusyCountWrapper))
408             {
409                 ReceiveContextBusyCountWrapper wrapper = new ReceiveContextBusyCountWrapper(context);
410                 message.Properties.Remove(ReceiveContext.Name);
411                 message.Properties.Add(ReceiveContext.Name, wrapper);
412             }
413         }
414
415         public override void ApplyHostedContext(TransportChannelListener listener, BindingContext context)
416         {
417             VirtualPathExtension virtualPathExtension = context.BindingParameters.Find<VirtualPathExtension>();
418
419             if (virtualPathExtension != null)
420             {
421                 HostedMetadataBindingParameter metadataBindingParameter = context.BindingParameters.Find<HostedMetadataBindingParameter>();
422                 listener.ApplyHostedContext(virtualPathExtension.VirtualPath, metadataBindingParameter != null);
423             }
424         }
425
426         internal override void AddMetadataBindingParameters(Uri listenUri, KeyedByTypeCollection<IServiceBehavior> serviceBehaviors, BindingParameterCollection bindingParameters)
427         {
428             if (serviceBehaviors.Find<HostedBindingBehavior>() != null)
429             {
430                 bindingParameters.Add(new HostedMetadataBindingParameter());
431             }
432
433             VirtualPathExtension virtualPathExtension = bindingParameters.Find<VirtualPathExtension>();
434
435             if (virtualPathExtension != null)
436             {
437                 AuthenticationSchemes hostSupportedAuthenticationSchemes = AspNetEnvironment.Current.GetAuthenticationSchemes(listenUri);
438
439                 if (hostSupportedAuthenticationSchemes != AuthenticationSchemes.None)
440                 {
441                     if (bindingParameters.Find<AuthenticationSchemesBindingParameter>() == null)
442                     {
443                         bindingParameters.Add(new AuthenticationSchemesBindingParameter(hostSupportedAuthenticationSchemes));
444                     }
445                 }
446             }
447
448             base.AddMetadataBindingParameters(listenUri, serviceBehaviors, bindingParameters);
449         }
450
451         internal override bool IsMetadataListener(BindingParameterCollection bindingParameters)
452         {
453             return base.IsMetadataListener(bindingParameters) || bindingParameters.Find<HostedMetadataBindingParameter>() != null;
454         }
455
456         public override void IncrementBusyCount()
457         {
458             HostingEnvironmentWrapper.IncrementBusyCount();
459         }
460
461         public override void DecrementBusyCount()
462         {
463             HostingEnvironmentWrapper.DecrementBusyCount();
464         }
465         
466         public override bool TraceIncrementBusyCountIsEnabled()
467         {
468             return TD.IncrementBusyCountIsEnabled();
469         }
470
471         public override bool TraceDecrementBusyCountIsEnabled()
472         {
473             return TD.DecrementBusyCountIsEnabled();
474         }
475         public override void TraceIncrementBusyCount(string data)
476         {
477             if (data == null)
478             {
479                 data = SR.DefaultBusyCountSource;
480             }
481             TD.IncrementBusyCount(data);
482         }
483
484         public override void TraceDecrementBusyCount(string data)
485         {
486             if (data == null)
487             {
488                 data = SR.DefaultBusyCountSource;
489             }
490             TD.DecrementBusyCount(data);
491         }
492
493         public override object GetConfigurationSection(string sectionPath)
494         {
495             return GetSectionFromWebConfigurationManager(sectionPath, ServiceHostingEnvironment.FullVirtualPath);
496         }
497
498         [Fx.Tag.SecurityNote(Critical = "Uses SecurityCritical method UnsafeGetSectionFromWebConfigurationManager which elevates.")]
499         [SecurityCritical]
500         public override object UnsafeGetConfigurationSection(string sectionPath)
501         {
502             return UnsafeGetSectionFromWebConfigurationManager(sectionPath, ServiceHostingEnvironment.FullVirtualPath);
503         }
504
505         public override bool IsSimpleApplicationHost
506         {
507             get
508             {
509                 return ServiceHostingEnvironment.IsSimpleApplicationHost;
510             }
511         }
512
513         public override AuthenticationSchemes GetAuthenticationSchemes(Uri baseAddress)
514         {
515             AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
516
517             string fileName = VirtualPathUtility.GetFileName(baseAddress.AbsolutePath);
518             string virtualPath = ServiceHostingEnvironment.CurrentVirtualPath;
519             string completePath;
520             if (virtualPath != null && virtualPath.EndsWith("/", StringComparison.Ordinal))
521             {
522                 completePath = virtualPath + fileName;
523             }
524             else
525             {
526                 completePath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", virtualPath, fileName);
527             }
528             AuthenticationSchemes supportedSchemes = HostedTransportConfigurationManager.MetabaseSettings.GetAuthenticationSchemes(completePath);
529
530             return supportedSchemes;
531         }
532
533         [Fx.Tag.SecurityNote(Critical = "Handles config objects, which should not be leaked.",
534              Safe = "Doesn't leak config objects out of SecurityCritical code.")]
535         [SecuritySafeCritical]
536         [MethodImpl(MethodImplOptions.NoInlining)]
537         public override bool IsWindowsAuthenticationConfigured()
538         {
539             if (!this.isWindowsAuthentication.HasValue)
540             {
541                 AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
542
543                 AuthenticationSection authSection = (AuthenticationSection)UnsafeGetConfigurationSection("system.web/authentication");
544                 if (authSection != null)
545                 {
546                     this.isWindowsAuthentication = (authSection.Mode == AuthenticationMode.Windows);
547                 }
548                 else
549                 {
550                     this.isWindowsAuthentication = false;
551                 }
552             }
553
554             return this.isWindowsAuthentication.Value;
555         }
556
557         /// Be sure to update UnsafeGetSectionFromWebConfigurationManager if you modify this method
558         [MethodImpl(MethodImplOptions.NoInlining)]
559         static object GetSectionFromWebConfigurationManager(string sectionPath, string virtualPath)
560         {
561             AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
562
563             if (virtualPath != null)
564             {
565                 return WebConfigurationManager.GetSection(sectionPath, virtualPath);
566             }
567             else
568             {
569                 return WebConfigurationManager.GetSection(sectionPath);
570             }
571         }
572
573         // Be sure to update GetSectionFromWebConfigurationManager if you modify this method
574         [SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts, Justification = "This is from an internal helper class and users have no way to pass arbitrary information to this code.")]
575         [Fx.Tag.SecurityNote(Critical = "Asserts ConfigurationPermission in order to fetch config from WebConfigurationManager,"
576             + "caller must guard return value.")]
577         [SecurityCritical]
578         [ConfigurationPermission(SecurityAction.Assert, Unrestricted = true)]
579         [MethodImpl(MethodImplOptions.NoInlining)]
580         internal static object UnsafeGetSectionFromWebConfigurationManager(string sectionPath, string virtualPath)
581         {
582             AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
583
584             if (virtualPath != null)
585             {
586                 return WebConfigurationManager.GetSection(sectionPath, virtualPath);
587             }
588             else
589             {
590                 return WebConfigurationManager.GetSection(sectionPath);
591             }
592         }
593
594         public override bool IsWithinApp(string absoluteVirtualPath)
595         {
596             return HostedTransportConfigurationManager.MetabaseSettings.IsWithinApp(absoluteVirtualPath);
597         }
598
599         // This class is intended to be empty.
600         class HostedMetadataBindingParameter
601         {
602         }
603
604         class HostedMetadataExchangeEndpointBehavior : IEndpointBehavior
605         {
606             void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
607             {
608                 bindingParameters.Add(new HostedMetadataBindingParameter());
609             }
610
611             void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, Dispatcher.ClientRuntime clientRuntime)
612             {
613             }
614
615             void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, Dispatcher.EndpointDispatcher endpointDispatcher)
616             {
617             }
618
619             void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
620             {
621             }
622         }
623
624         class ReceiveContextBusyCountWrapper : ReceiveContext
625         {
626             ReceiveContext wrappedContext;
627             //possible values are 0 and 1.
628             //using an integer to allow usage with Interlocked methods
629             //synchronized access needed as there could be ---- between calls
630             //to EndComplete and Tx notification.
631             int busyCount;
632             //possible values are 0 and 1
633             //using an integer to allow usage with Interlocked methods
634             //synchronized access needed as there could be ---- between calls
635             //to EndComplete and Tx Status notification.
636             int ambientTransactionCount;
637
638             internal ReceiveContextBusyCountWrapper(ReceiveContext context)
639             {
640                 this.wrappedContext = context;
641                 this.wrappedContext.Faulted += new EventHandler(OnWrappedContextFaulted);
642                 AspNetEnvironment.Current.IncrementBusyCount();
643                 if (AspNetEnvironment.Current.TraceIncrementBusyCountIsEnabled())
644                 {
645                     AspNetEnvironment.Current.TraceIncrementBusyCount(this.GetType().FullName);
646                 }
647                 Interlocked.Increment(ref busyCount);
648             }
649
650             protected override void OnAbandon(TimeSpan timeout)
651             {
652                 this.wrappedContext.Abandon(timeout);
653                 DecrementBusyCount();
654             }
655
656             protected override IAsyncResult OnBeginAbandon(TimeSpan timeout, AsyncCallback callback, object state)
657             {
658                 return this.wrappedContext.BeginAbandon(timeout, callback, state);
659             }
660
661             protected override IAsyncResult OnBeginComplete(TimeSpan timeout, AsyncCallback callback, object state)
662             {
663                 RegisterForTransactionNotification(Transaction.Current);
664                 return this.wrappedContext.BeginComplete(timeout, callback, state);
665             }
666
667             protected override void OnComplete(TimeSpan timeout)
668             {
669                 RegisterForTransactionNotification(Transaction.Current);
670                 this.wrappedContext.Complete(timeout);
671                 DecrementOnNoAmbientTransaction();
672
673             }
674
675             protected override void OnEndAbandon(IAsyncResult result)
676             {
677                 this.wrappedContext.EndAbandon(result);
678                 DecrementBusyCount();
679             }
680
681             protected override void OnEndComplete(IAsyncResult result)
682             {
683                 this.wrappedContext.EndComplete(result);
684                 DecrementOnNoAmbientTransaction();
685             }
686
687             protected override void OnFaulted()
688             {
689                 try
690                 {
691                     this.wrappedContext.Fault();
692                 }
693                 finally
694                 {
695                     base.OnFaulted();
696                 }
697             }
698
699             void OnWrappedContextFaulted(object sender, EventArgs e)
700             {
701                 try
702                 {
703                     Fault();
704                 }
705                 finally
706                 {
707                     DecrementBusyCount();
708                 }
709             }
710
711             void RegisterForTransactionNotification(Transaction transaction)
712             {
713                 if (Transaction.Current != null)
714                 {
715                     ReceiveContextEnlistmentNotification notification = new ReceiveContextEnlistmentNotification(this);
716                     transaction.EnlistVolatile(notification, EnlistmentOptions.None);
717                     Interlocked.Increment(ref this.ambientTransactionCount);
718                 }
719             }
720
721             void DecrementOnNoAmbientTransaction()
722             {
723                 if (Interlocked.Exchange(ref this.ambientTransactionCount, 0) != 1)
724                 {
725                     DecrementBusyCount();
726                 }
727
728             }
729
730             void DecrementBusyCount()
731             {
732                 if (Interlocked.Exchange(ref this.busyCount, 0) == 1)
733                 {
734                     AspNetEnvironment.Current.DecrementBusyCount();
735                     if (AspNetEnvironment.Current.TraceDecrementBusyCountIsEnabled())
736                     {
737                         AspNetEnvironment.Current.TraceDecrementBusyCount(this.GetType().FullName);
738                     }
739                 }
740             }
741
742             class ReceiveContextEnlistmentNotification : IEnlistmentNotification
743             {
744                 ReceiveContextBusyCountWrapper context;
745
746                 internal ReceiveContextEnlistmentNotification(ReceiveContextBusyCountWrapper context)
747                 {
748                     this.context = context;
749                 }
750
751                 public void Commit(Enlistment enlistment)
752                 {
753                     this.context.DecrementBusyCount();
754                     enlistment.Done();
755                 }
756
757                 public void InDoubt(Enlistment enlistment)
758                 {
759                     this.context.DecrementBusyCount();
760                     enlistment.Done();
761                 }
762
763                 public void Prepare(PreparingEnlistment preparingEnlistment)
764                 {
765                     preparingEnlistment.Prepared();
766                 }
767
768                 public void Rollback(Enlistment enlistment)
769                 {
770                     enlistment.Done();
771                 }
772             }
773         }
774     }
775 }