2016-08-03 10:59:49 +00:00
//----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------------------
namespace System.ServiceModel.Activation
{
using System.Collections.Generic ;
using System.Configuration ;
using System.Diagnostics.CodeAnalysis ;
using System.Globalization ;
using System.Net ;
using System.Runtime ;
using System.Runtime.CompilerServices ;
using System.Security ;
using System.Security.Authentication.ExtendedProtection ;
using System.Security.Permissions ;
using System.ServiceModel ;
using System.ServiceModel.Channels ;
using System.ServiceModel.Description ;
using System.Threading ;
using System.Transactions ;
using System.Web ;
using System.Web.Compilation ;
using System.Web.Configuration ;
class HostedAspNetEnvironment : AspNetEnvironment
{
// On IIS 8.0 (or later) the "WEBSOCKET_VERSION" server property indicates the WebSocket protocol version supported by the server.
// The IIS WebSocket module sets this property when initialized.
private const string WebSocketVersionServerProperty = "WEBSOCKET_VERSION" ;
// Indicates if we determined the WebSocket version. If false, we'll need to check the "WEBSOCKET_VERSION" server property.
private static bool isWebSocketVersionSet = false ;
// Provides the version of the WebSocket protocol supported by IIS.
private static string webSocketVersion ;
// used to cache SiteName|ApplicationVirtualPath
static string cachedServiceReference ;
// used to cache if windows auth is being used
Nullable < bool > isWindowsAuthentication ;
HostedAspNetEnvironment ( )
: base ( )
{
}
public override bool AspNetCompatibilityEnabled
{
get
{
return ServiceHostingEnvironment . AspNetCompatibilityEnabled ;
}
}
public override string ConfigurationPath
{
get
{
if ( ServiceHostingEnvironment . CurrentVirtualPath ! = null )
{
return ServiceHostingEnvironment . CurrentVirtualPath + "web.config" ;
}
else
{
return base . ConfigurationPath ;
}
}
}
public override bool IsConfigurationBased
{
get
{
return ServiceHostingEnvironment . IsConfigurationBased ;
}
}
public override string CurrentVirtualPath
{
get
{
return ServiceHostingEnvironment . CurrentVirtualPath ;
}
}
public override string XamlFileBaseLocation
{
get
{
return ServiceHostingEnvironment . XamlFileBaseLocation ;
}
}
public override bool UsingIntegratedPipeline
{
get
{
return HttpRuntime . UsingIntegratedPipeline ;
}
}
2016-11-10 13:04:39 +00:00
// Provides the version of the WebSocket protocol supported by IIS.
2016-08-03 10:59:49 +00:00
// Returns null if WebSockets are not supported (because the IIS WebSocketModule is not installed or enabled).
public override string WebSocketVersion
{
get
{
2016-11-10 13:04:39 +00:00
return isWebSocketVersionSet ? webSocketVersion : null ;
2016-08-03 10:59:49 +00:00
}
}
public static void Enable ( )
{
AspNetEnvironment hostedEnvironment = new HostedAspNetEnvironment ( ) ;
AspNetEnvironment . Current = hostedEnvironment ;
}
/// <summary>
/// Tries to set the 'WebSocketVersion' property. The first call of this method sets the property (based on the "WEBSOCKET_VERSION" server property).
/// Subsequent calls do nothing.
/// </summary>
/// <param name="application">The HttpApplication used to determine the WebSocket version.</param>
/// <remarks>
/// Take caution when calling this method. The method initializes the 'WebSocketVersion' property based on the "WEBSOCKET_VERSION" server variable.
/// 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),
/// this method might incorrectly set 'WebSocketVersion' to 'null'.
/// </remarks>
public static void TrySetWebSocketVersion ( HttpApplication application )
{
if ( ! isWebSocketVersionSet )
{
webSocketVersion = application . Request . ServerVariables [ WebSocketVersionServerProperty ] ;
isWebSocketVersionSet = true ;
}
}
public override void AddHostingBehavior ( ServiceHostBase serviceHost , ServiceDescription description )
{
VirtualPathExtension virtualPathExtension = serviceHost . Extensions . Find < VirtualPathExtension > ( ) ;
if ( virtualPathExtension ! = null )
{
description . Behaviors . Add ( new HostedBindingBehavior ( virtualPathExtension ) ) ;
}
foreach ( ServiceEndpoint endpoint in description . Endpoints )
{
if ( ServiceMetadataBehavior . IsMetadataEndpoint ( description , endpoint ) )
{
endpoint . Behaviors . Add ( new HostedMetadataExchangeEndpointBehavior ( ) ) ;
}
}
}
public override bool IsWebConfigAboveApplication ( object configHostingContext )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
WebContext context = configHostingContext as WebContext ;
if ( context ! = null )
{
return context . ApplicationLevel = = WebApplicationLevel . AboveApplication ;
}
return false ; // if we don't recognize the context we can't enforce the special web.config logic
}
public override void EnsureCompatibilityRequirements ( ServiceDescription description )
{
AspNetCompatibilityRequirementsAttribute aspNetCompatibilityRequirements = description . Behaviors . Find < AspNetCompatibilityRequirementsAttribute > ( ) ;
if ( aspNetCompatibilityRequirements = = null )
{
aspNetCompatibilityRequirements = new AspNetCompatibilityRequirementsAttribute ( ) ;
description . Behaviors . Add ( aspNetCompatibilityRequirements ) ;
}
}
public override bool TryGetFullVirtualPath ( out string virtualPath )
{
// subclass will use the virtual path from the compiled string
virtualPath = ServiceHostingEnvironment . FullVirtualPath ;
return true ;
}
public override string GetAnnotationFromHost ( ServiceHostBase host )
{
//Format Website name\Application Virtual Path|\relative service virtual path|serviceName
if ( host ! = null & & host . Extensions ! = null )
{
string serviceName = ( host . Description ! = null ) ? host . Description . Name : string . Empty ;
string application = ServiceHostingEnvironment . ApplicationVirtualPath ;
string servicePath = string . Empty ;
VirtualPathExtension extension = host . Extensions . Find < VirtualPathExtension > ( ) ;
if ( extension ! = null & & extension . VirtualPath ! = null )
{
servicePath = extension . VirtualPath . Replace ( "~" , application + "|" ) ;
return string . Format ( CultureInfo . InvariantCulture , "{0}{1}|{2}" , ServiceHostingEnvironment . SiteName , servicePath , serviceName ) ;
}
}
if ( string . IsNullOrEmpty ( HostedAspNetEnvironment . cachedServiceReference ) )
{
HostedAspNetEnvironment . cachedServiceReference = string . Format ( CultureInfo . InvariantCulture , "{0}{1}" , ServiceHostingEnvironment . SiteName , ServiceHostingEnvironment . ApplicationVirtualPath ) ;
}
return HostedAspNetEnvironment . cachedServiceReference ;
}
[Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - can be called outside of user context.")]
public override void EnsureAllReferencedAssemblyLoaded ( )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
BuildManager . GetReferencedAssemblies ( ) ;
}
public override BaseUriWithWildcard GetBaseUri ( string transportScheme , Uri listenUri )
{
BaseUriWithWildcard baseAddress = null ;
HostedTransportConfigurationBase hostedConfiguration =
HostedTransportConfigurationManager . GetConfiguration ( transportScheme ) as HostedTransportConfigurationBase ;
if ( hostedConfiguration ! = null )
{
baseAddress = hostedConfiguration . FindBaseAddress ( listenUri ) ;
if ( baseAddress = = null )
{
throw FxTrace . Exception . AsError ( new InvalidOperationException ( SR . Hosting_TransportBindingNotFound ( listenUri . ToString ( ) ) ) ) ;
}
}
return baseAddress ;
}
public override void ValidateHttpSettings ( string virtualPath , bool isMetadataListener , bool usingDefaultSpnList , ref AuthenticationSchemes bindingElementAuthenticationSchemes , ref ExtendedProtectionPolicy extendedProtectionPolicy , ref string realm )
{
// Verify the authentication settings
AuthenticationSchemes hostSupportedSchemes = HostedTransportConfigurationManager . MetabaseSettings . GetAuthenticationSchemes ( virtualPath ) ;
if ( ( bindingElementAuthenticationSchemes & hostSupportedSchemes ) = = 0 )
{
if ( bindingElementAuthenticationSchemes = = AuthenticationSchemes . Negotiate | |
bindingElementAuthenticationSchemes = = AuthenticationSchemes . Ntlm | |
bindingElementAuthenticationSchemes = = AuthenticationSchemes . IntegratedWindowsAuthentication )
{
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . Hosting_AuthSchemesRequireWindowsAuth ) ) ;
}
else
{
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . Hosting_AuthSchemesRequireOtherAuth ( bindingElementAuthenticationSchemes . ToString ( ) ) ) ) ;
}
}
//only use AuthenticationSchemes, which are supported both in IIS and the WCF binding
bindingElementAuthenticationSchemes & = hostSupportedSchemes ;
if ( bindingElementAuthenticationSchemes ! = AuthenticationSchemes . Anonymous )
{
//Compare the ExtendedProtectionPolicy setttings to IIS
ExtendedProtectionPolicy iisPolicy = HostedTransportConfigurationManager . MetabaseSettings . GetExtendedProtectionPolicy ( virtualPath ) ;
if ( iisPolicy = = null ) //OS doesn't support CBT
{
if ( extendedProtectionPolicy . PolicyEnforcement = = PolicyEnforcement . Always )
{
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . ExtendedProtectionNotSupported ) ) ;
}
}
else
{
if ( isMetadataListener & & ChannelBindingUtility . IsDefaultPolicy ( extendedProtectionPolicy ) )
{
//push the IIS policy onto the metadataListener if and only if the default policy is
//in force. policy for non metadata listeners will still have to match IIS policy.
extendedProtectionPolicy = iisPolicy ;
}
else
{
if ( ! ChannelBindingUtility . AreEqual ( iisPolicy , extendedProtectionPolicy ) )
{
string mismatchErrorMessage ;
if ( iisPolicy . PolicyEnforcement ! = extendedProtectionPolicy . PolicyEnforcement )
{
mismatchErrorMessage = SR . ExtendedProtectionPolicyEnforcementMismatch ( iisPolicy . PolicyEnforcement , extendedProtectionPolicy . PolicyEnforcement ) ;
}
else if ( iisPolicy . ProtectionScenario ! = extendedProtectionPolicy . ProtectionScenario )
{
mismatchErrorMessage = SR . ExtendedProtectionPolicyScenarioMismatch ( iisPolicy . ProtectionScenario , extendedProtectionPolicy . ProtectionScenario ) ;
}
else
{
Fx . Assert ( iisPolicy . CustomChannelBinding ! = extendedProtectionPolicy . CustomChannelBinding , "new case in ChannelBindingUtility.AreEqual to account for" ) ;
mismatchErrorMessage = SR . ExtendedProtectionPolicyCustomChannelBindingMismatch ;
}
if ( mismatchErrorMessage ! = null )
{
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . Hosting_ExtendedProtectionPoliciesMustMatch ( mismatchErrorMessage ) ) ) ;
}
}
//when using the default SPN list we auto generate, we should make sure that the IIS policy is also the default...
ServiceNameCollection listenerSpnList = usingDefaultSpnList ? null : extendedProtectionPolicy . CustomServiceNames ;
if ( ! ChannelBindingUtility . IsSubset ( iisPolicy . CustomServiceNames , listenerSpnList ) )
{
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . Hosting_ExtendedProtectionPoliciesMustMatch ( SR . Hosting_ExtendedProtectionSPNListNotSubset ) ) ) ;
}
}
}
}
// Do not set realm for Cassini.
if ( ! ServiceHostingEnvironment . IsSimpleApplicationHost )
{
// Set the realm
realm = HostedTransportConfigurationManager . MetabaseSettings . GetRealm ( virtualPath ) ;
}
}
public override bool ValidateHttpsSettings ( string virtualPath , ref bool requireClientCertificate )
{
// Do not validate settings for Cassini. Actually current implementation of Cassini does not support HTTPS.
if ( ServiceHostingEnvironment . IsSimpleApplicationHost )
{
return false ;
}
// Validate Ssl Settings
HttpAccessSslFlags sslFlags = HostedTransportConfigurationManager . MetabaseSettings . GetAccessSslFlags ( virtualPath ) ;
HttpAccessSslFlags channelListenerSslFlags = HttpAccessSslFlags . None ;
// Validating SSL flags. SslRequireCert means "require client certificate" in IIS terminology.
if ( ( sslFlags & HttpAccessSslFlags . SslRequireCert ) ! = 0 )
{
// Require SSL.
// We apply IIS settings to the ChannelListener to fix the endpoint
requireClientCertificate = true ;
}
else if ( requireClientCertificate & &
// Validating SSL flags. SslNegotiateCert means "accept client certificate" in IIS terminology.
// We want to allow SslNegotiateCert in IIS to support hosting one endpoint requiring client
// certificates and another endpoint not using client certificates in the same VirtualDirectory.
// HttpsChannelListener.ValidateAuthentication ensures that authentication is denied for services
// requiring client certificates when the client does not present one.
( sslFlags & HttpAccessSslFlags . SslNegotiateCert ) = = 0 )
{
// IIS ignores client cert but the binding requires it.
channelListenerSslFlags | = HttpAccessSslFlags . SslRequireCert ;
throw FxTrace . Exception . AsError ( new NotSupportedException ( SR . Hosting_SslSettingsMisconfigured (
channelListenerSslFlags . ToString ( ) , sslFlags . ToString ( ) ) ) ) ;
}
return ( sslFlags & HttpAccessSslFlags . SslMapCert ) ! = 0 ;
}
public override void ProcessNotMatchedEndpointAddress ( Uri uri , string endpointName )
{
if ( ! object . ReferenceEquals ( uri . Scheme , Uri . UriSchemeHttp ) & &
! object . ReferenceEquals ( uri . Scheme , Uri . UriSchemeHttps ) )
{
throw FxTrace . Exception . AsError ( new InvalidOperationException ( SR . Hosting_NonHTTPInCompatibilityMode ( endpointName ) ) ) ;
}
}
public override void ValidateCompatibilityRequirements ( AspNetCompatibilityRequirementsMode compatibilityMode )
{
if ( compatibilityMode = = AspNetCompatibilityRequirementsMode . Allowed )
{
return ;
}
else if ( ServiceHostingEnvironment . AspNetCompatibilityEnabled & &
compatibilityMode = = AspNetCompatibilityRequirementsMode . NotAllowed )
{
throw FxTrace . Exception . AsError ( new InvalidOperationException ( SR . Hosting_ServiceCompatibilityNotAllowed ) ) ;
}
else if ( ! ServiceHostingEnvironment . AspNetCompatibilityEnabled & &
compatibilityMode = = AspNetCompatibilityRequirementsMode . Required )
{
throw FxTrace . Exception . AsError ( new InvalidOperationException ( SR . Hosting_ServiceCompatibilityRequire ) ) ;
}
}
public override IAspNetMessageProperty GetHostingProperty ( Message message )
{
return GetHostingProperty ( message , false ) ;
}
public override IAspNetMessageProperty GetHostingProperty ( Message message , bool removeFromMessage )
{
IAspNetMessageProperty result = null ;
object property ;
if ( message . Properties . TryGetValue ( HostingMessageProperty . Name , out property ) )
{
result = ( HostingMessageProperty ) property ;
if ( removeFromMessage )
{
message . Properties . Remove ( HostingMessageProperty . Name ) ;
}
}
return result ;
}
public override void PrepareMessageForDispatch ( Message message )
{
ReceiveContext context = null ;
if ( ReceiveContext . TryGet ( message , out context ) & & ! ( context is ReceiveContextBusyCountWrapper ) )
{
ReceiveContextBusyCountWrapper wrapper = new ReceiveContextBusyCountWrapper ( context ) ;
message . Properties . Remove ( ReceiveContext . Name ) ;
message . Properties . Add ( ReceiveContext . Name , wrapper ) ;
}
}
public override void ApplyHostedContext ( TransportChannelListener listener , BindingContext context )
{
VirtualPathExtension virtualPathExtension = context . BindingParameters . Find < VirtualPathExtension > ( ) ;
if ( virtualPathExtension ! = null )
{
HostedMetadataBindingParameter metadataBindingParameter = context . BindingParameters . Find < HostedMetadataBindingParameter > ( ) ;
listener . ApplyHostedContext ( virtualPathExtension . VirtualPath , metadataBindingParameter ! = null ) ;
}
}
internal override void AddMetadataBindingParameters ( Uri listenUri , KeyedByTypeCollection < IServiceBehavior > serviceBehaviors , BindingParameterCollection bindingParameters )
{
if ( serviceBehaviors . Find < HostedBindingBehavior > ( ) ! = null )
{
bindingParameters . Add ( new HostedMetadataBindingParameter ( ) ) ;
}
VirtualPathExtension virtualPathExtension = bindingParameters . Find < VirtualPathExtension > ( ) ;
if ( virtualPathExtension ! = null )
{
AuthenticationSchemes hostSupportedAuthenticationSchemes = AspNetEnvironment . Current . GetAuthenticationSchemes ( listenUri ) ;
if ( hostSupportedAuthenticationSchemes ! = AuthenticationSchemes . None )
{
if ( bindingParameters . Find < AuthenticationSchemesBindingParameter > ( ) = = null )
{
bindingParameters . Add ( new AuthenticationSchemesBindingParameter ( hostSupportedAuthenticationSchemes ) ) ;
}
}
}
base . AddMetadataBindingParameters ( listenUri , serviceBehaviors , bindingParameters ) ;
}
internal override bool IsMetadataListener ( BindingParameterCollection bindingParameters )
{
return base . IsMetadataListener ( bindingParameters ) | | bindingParameters . Find < HostedMetadataBindingParameter > ( ) ! = null ;
}
public override void IncrementBusyCount ( )
{
HostingEnvironmentWrapper . IncrementBusyCount ( ) ;
}
public override void DecrementBusyCount ( )
{
HostingEnvironmentWrapper . DecrementBusyCount ( ) ;
}
public override bool TraceIncrementBusyCountIsEnabled ( )
{
return TD . IncrementBusyCountIsEnabled ( ) ;
}
public override bool TraceDecrementBusyCountIsEnabled ( )
{
return TD . DecrementBusyCountIsEnabled ( ) ;
}
public override void TraceIncrementBusyCount ( string data )
{
if ( data = = null )
{
data = SR . DefaultBusyCountSource ;
}
TD . IncrementBusyCount ( data ) ;
}
public override void TraceDecrementBusyCount ( string data )
{
if ( data = = null )
{
data = SR . DefaultBusyCountSource ;
}
TD . DecrementBusyCount ( data ) ;
}
public override object GetConfigurationSection ( string sectionPath )
{
return GetSectionFromWebConfigurationManager ( sectionPath , ServiceHostingEnvironment . FullVirtualPath ) ;
}
[Fx.Tag.SecurityNote(Critical = "Uses SecurityCritical method UnsafeGetSectionFromWebConfigurationManager which elevates.")]
[SecurityCritical]
public override object UnsafeGetConfigurationSection ( string sectionPath )
{
return UnsafeGetSectionFromWebConfigurationManager ( sectionPath , ServiceHostingEnvironment . FullVirtualPath ) ;
}
public override bool IsSimpleApplicationHost
{
get
{
return ServiceHostingEnvironment . IsSimpleApplicationHost ;
}
}
public override AuthenticationSchemes GetAuthenticationSchemes ( Uri baseAddress )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
string fileName = VirtualPathUtility . GetFileName ( baseAddress . AbsolutePath ) ;
string virtualPath = ServiceHostingEnvironment . CurrentVirtualPath ;
string completePath ;
if ( virtualPath ! = null & & virtualPath . EndsWith ( "/" , StringComparison . Ordinal ) )
{
completePath = virtualPath + fileName ;
}
else
{
completePath = string . Format ( CultureInfo . InvariantCulture , "{0}/{1}" , virtualPath , fileName ) ;
}
AuthenticationSchemes supportedSchemes = HostedTransportConfigurationManager . MetabaseSettings . GetAuthenticationSchemes ( completePath ) ;
return supportedSchemes ;
}
[ Fx . Tag . SecurityNote ( Critical = "Handles config objects, which should not be leaked." ,
Safe = "Doesn't leak config objects out of SecurityCritical code." ) ]
[SecuritySafeCritical]
[MethodImpl(MethodImplOptions.NoInlining)]
public override bool IsWindowsAuthenticationConfigured ( )
{
if ( ! this . isWindowsAuthentication . HasValue )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
AuthenticationSection authSection = ( AuthenticationSection ) UnsafeGetConfigurationSection ( "system.web/authentication" ) ;
if ( authSection ! = null )
{
this . isWindowsAuthentication = ( authSection . Mode = = AuthenticationMode . Windows ) ;
}
else
{
this . isWindowsAuthentication = false ;
}
}
return this . isWindowsAuthentication . Value ;
}
/// Be sure to update UnsafeGetSectionFromWebConfigurationManager if you modify this method
[MethodImpl(MethodImplOptions.NoInlining)]
static object GetSectionFromWebConfigurationManager ( string sectionPath , string virtualPath )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
if ( virtualPath ! = null )
{
return WebConfigurationManager . GetSection ( sectionPath , virtualPath ) ;
}
else
{
return WebConfigurationManager . GetSection ( sectionPath ) ;
}
}
// Be sure to update GetSectionFromWebConfigurationManager if you modify this method
[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.")]
[ Fx . Tag . SecurityNote ( Critical = "Asserts ConfigurationPermission in order to fetch config from WebConfigurationManager,"
+ "caller must guard return value." ) ]
[SecurityCritical]
[ConfigurationPermission(SecurityAction.Assert, Unrestricted = true)]
[MethodImpl(MethodImplOptions.NoInlining)]
internal static object UnsafeGetSectionFromWebConfigurationManager ( string sectionPath , string virtualPath )
{
AspNetPartialTrustHelpers . FailIfInPartialTrustOutsideAspNet ( ) ;
if ( virtualPath ! = null )
{
return WebConfigurationManager . GetSection ( sectionPath , virtualPath ) ;
}
else
{
return WebConfigurationManager . GetSection ( sectionPath ) ;
}
}
public override bool IsWithinApp ( string absoluteVirtualPath )
{
return HostedTransportConfigurationManager . MetabaseSettings . IsWithinApp ( absoluteVirtualPath ) ;
}
// This class is intended to be empty.
class HostedMetadataBindingParameter
{
}
class HostedMetadataExchangeEndpointBehavior : IEndpointBehavior
{
void IEndpointBehavior . AddBindingParameters ( ServiceEndpoint endpoint , BindingParameterCollection bindingParameters )
{
bindingParameters . Add ( new HostedMetadataBindingParameter ( ) ) ;
}
void IEndpointBehavior . ApplyClientBehavior ( ServiceEndpoint endpoint , Dispatcher . ClientRuntime clientRuntime )
{
}
void IEndpointBehavior . ApplyDispatchBehavior ( ServiceEndpoint endpoint , Dispatcher . EndpointDispatcher endpointDispatcher )
{
}
void IEndpointBehavior . Validate ( ServiceEndpoint endpoint )
{
}
}
class ReceiveContextBusyCountWrapper : ReceiveContext
{
ReceiveContext wrappedContext ;
//possible values are 0 and 1.
//using an integer to allow usage with Interlocked methods
//synchronized access needed as there could be ---- between calls
//to EndComplete and Tx notification.
int busyCount ;
//possible values are 0 and 1
//using an integer to allow usage with Interlocked methods
//synchronized access needed as there could be ---- between calls
//to EndComplete and Tx Status notification.
int ambientTransactionCount ;
internal ReceiveContextBusyCountWrapper ( ReceiveContext context )
{
this . wrappedContext = context ;
this . wrappedContext . Faulted + = new EventHandler ( OnWrappedContextFaulted ) ;
AspNetEnvironment . Current . IncrementBusyCount ( ) ;
if ( AspNetEnvironment . Current . TraceIncrementBusyCountIsEnabled ( ) )
{
AspNetEnvironment . Current . TraceIncrementBusyCount ( this . GetType ( ) . FullName ) ;
}
Interlocked . Increment ( ref busyCount ) ;
}
protected override void OnAbandon ( TimeSpan timeout )
{
this . wrappedContext . Abandon ( timeout ) ;
DecrementBusyCount ( ) ;
}
protected override IAsyncResult OnBeginAbandon ( TimeSpan timeout , AsyncCallback callback , object state )
{
return this . wrappedContext . BeginAbandon ( timeout , callback , state ) ;
}
protected override IAsyncResult OnBeginComplete ( TimeSpan timeout , AsyncCallback callback , object state )
{
RegisterForTransactionNotification ( Transaction . Current ) ;
return this . wrappedContext . BeginComplete ( timeout , callback , state ) ;
}
protected override void OnComplete ( TimeSpan timeout )
{
RegisterForTransactionNotification ( Transaction . Current ) ;
this . wrappedContext . Complete ( timeout ) ;
DecrementOnNoAmbientTransaction ( ) ;
}
protected override void OnEndAbandon ( IAsyncResult result )
{
this . wrappedContext . EndAbandon ( result ) ;
DecrementBusyCount ( ) ;
}
protected override void OnEndComplete ( IAsyncResult result )
{
this . wrappedContext . EndComplete ( result ) ;
DecrementOnNoAmbientTransaction ( ) ;
}
protected override void OnFaulted ( )
{
try
{
this . wrappedContext . Fault ( ) ;
}
finally
{
base . OnFaulted ( ) ;
}
}
void OnWrappedContextFaulted ( object sender , EventArgs e )
{
try
{
Fault ( ) ;
}
finally
{
DecrementBusyCount ( ) ;
}
}
void RegisterForTransactionNotification ( Transaction transaction )
{
if ( Transaction . Current ! = null )
{
ReceiveContextEnlistmentNotification notification = new ReceiveContextEnlistmentNotification ( this ) ;
transaction . EnlistVolatile ( notification , EnlistmentOptions . None ) ;
Interlocked . Increment ( ref this . ambientTransactionCount ) ;
}
}
void DecrementOnNoAmbientTransaction ( )
{
if ( Interlocked . Exchange ( ref this . ambientTransactionCount , 0 ) ! = 1 )
{
DecrementBusyCount ( ) ;
}
}
void DecrementBusyCount ( )
{
if ( Interlocked . Exchange ( ref this . busyCount , 0 ) = = 1 )
{
AspNetEnvironment . Current . DecrementBusyCount ( ) ;
if ( AspNetEnvironment . Current . TraceDecrementBusyCountIsEnabled ( ) )
{
AspNetEnvironment . Current . TraceDecrementBusyCount ( this . GetType ( ) . FullName ) ;
}
}
}
class ReceiveContextEnlistmentNotification : IEnlistmentNotification
{
ReceiveContextBusyCountWrapper context ;
internal ReceiveContextEnlistmentNotification ( ReceiveContextBusyCountWrapper context )
{
this . context = context ;
}
public void Commit ( Enlistment enlistment )
{
this . context . DecrementBusyCount ( ) ;
enlistment . Done ( ) ;
}
public void InDoubt ( Enlistment enlistment )
{
this . context . DecrementBusyCount ( ) ;
enlistment . Done ( ) ;
}
public void Prepare ( PreparingEnlistment preparingEnlistment )
{
preparingEnlistment . Prepared ( ) ;
}
public void Rollback ( Enlistment enlistment )
{
enlistment . Done ( ) ;
}
}
}
}
}