536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1978 lines
76 KiB
C#
1978 lines
76 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="HostingEnvironment.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.Hosting {
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Configuration;
|
|
using System.Configuration.Provider;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.Caching;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Remoting;
|
|
using System.Runtime.Remoting.Messaging;
|
|
using System.Security;
|
|
using System.Security.Permissions;
|
|
using System.Security.Policy;
|
|
using System.Security.Principal;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Web;
|
|
using System.Web.Caching;
|
|
using System.Web.Compilation;
|
|
using System.Web.Configuration;
|
|
using System.Web.Management;
|
|
using System.Web.Util;
|
|
using System.Web.WebSockets;
|
|
using Microsoft.Win32;
|
|
|
|
[Flags]
|
|
internal enum HostingEnvironmentFlags {
|
|
Default = 0,
|
|
HideFromAppManager = 1,
|
|
ThrowHostingInitErrors = 2,
|
|
DontCallAppInitialize = 4,
|
|
ClientBuildManager = 8,
|
|
SupportsMultiTargeting = 16,
|
|
}
|
|
|
|
[Serializable]
|
|
internal class HostingEnvironmentParameters {
|
|
private HostingEnvironmentFlags _hostingFlags;
|
|
private ClientBuildManagerParameter _clientBuildManagerParameter;
|
|
private string _precompTargetPhysicalDir;
|
|
private string _iisExpressVersion;
|
|
|
|
public HostingEnvironmentFlags HostingFlags {
|
|
get { return _hostingFlags; }
|
|
set { _hostingFlags = value; }
|
|
}
|
|
|
|
// Directory where the precompiled site is placed
|
|
public string PrecompilationTargetPhysicalDirectory {
|
|
get { return _precompTargetPhysicalDir; }
|
|
set {
|
|
_precompTargetPhysicalDir = FileUtil.FixUpPhysicalDirectory(value);
|
|
}
|
|
}
|
|
|
|
// Determines the behavior of the precompilation
|
|
public ClientBuildManagerParameter ClientBuildManagerParameter {
|
|
get { return _clientBuildManagerParameter; }
|
|
set { _clientBuildManagerParameter = value; }
|
|
}
|
|
|
|
// Determines which config system to load
|
|
public string IISExpressVersion {
|
|
get { return _iisExpressVersion; }
|
|
set { _iisExpressVersion = value; }
|
|
}
|
|
|
|
// Determines what FileChangeMonitor mode to use
|
|
public FcnMode FcnMode {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
// Should FileChangesMonitor skip reading and caching DACLs?
|
|
public bool FcnSkipReadAndCacheDacls {
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public KeyValuePair<string, bool>[] ClrQuirksSwitches {
|
|
get;
|
|
set;
|
|
}
|
|
}
|
|
|
|
public sealed class HostingEnvironment : MarshalByRefObject {
|
|
|
|
private static HostingEnvironment _theHostingEnvironment;
|
|
private EventHandler _onAppDomainUnload;
|
|
private ApplicationManager _appManager;
|
|
private HostingEnvironmentParameters _hostingParameters;
|
|
private IApplicationHost _appHost;
|
|
private bool _externalAppHost;
|
|
private IConfigMapPath _configMapPath;
|
|
private IConfigMapPath2 _configMapPath2;
|
|
private IntPtr _configToken;
|
|
|
|
private IdentitySection _appIdentity;
|
|
private IntPtr _appIdentityToken;
|
|
private bool _appIdentityTokenSet;
|
|
|
|
private String _appId;
|
|
private VirtualPath _appVirtualPath;
|
|
private String _appPhysicalPath;
|
|
private String _siteName;
|
|
private String _siteID;
|
|
private String _appConfigPath;
|
|
|
|
private bool _isBusy;
|
|
private int _busyCount;
|
|
|
|
private volatile static bool _stopListeningWasCalled; // static since it's process-wide
|
|
private bool _removedFromAppManager;
|
|
private bool _appDomainShutdownStarted;
|
|
private bool _shutdownInitiated;
|
|
private bool _shutdownInProgress;
|
|
private String _shutDownStack;
|
|
|
|
private static NameValueCollection _cacheProviderSettings;
|
|
private int _inTrimCache;
|
|
private ObjectCacheHost _objectCacheHost;
|
|
|
|
// table of well know objects keyed by type
|
|
private Hashtable _wellKnownObjects = new Hashtable();
|
|
|
|
// list of registered IRegisteredObject instances, suspend listeners, and background work items
|
|
private Hashtable _registeredObjects = new Hashtable();
|
|
private SuspendManager _suspendManager = new SuspendManager();
|
|
private ApplicationMonitors _applicationMonitors;
|
|
private BackgroundWorkScheduler _backgroundWorkScheduler = null; // created on demand
|
|
private static readonly Task<object> _completedTask = Task.FromResult<object>(null);
|
|
|
|
// callback to make InitiateShutdown non-blocking
|
|
private WaitCallback _initiateShutdownWorkItemCallback;
|
|
|
|
// inside app domain idle shutdown logic
|
|
private IdleTimeoutMonitor _idleTimeoutMonitor;
|
|
|
|
private static IProcessHostSupportFunctions _functions;
|
|
private static bool _hasBeenRemovedFromAppManangerTable;
|
|
|
|
private const string TemporaryVirtualPathProviderKey = "__TemporaryVirtualPathProvider__";
|
|
|
|
// Determines what FileChangeMonitor mode to use
|
|
internal static FcnMode FcnMode {
|
|
get {
|
|
if (_theHostingEnvironment != null && _theHostingEnvironment._hostingParameters != null) {
|
|
return _theHostingEnvironment._hostingParameters.FcnMode;
|
|
}
|
|
return FcnMode.NotSet;
|
|
}
|
|
}
|
|
|
|
internal static bool FcnSkipReadAndCacheDacls {
|
|
get {
|
|
if (_theHostingEnvironment != null && _theHostingEnvironment._hostingParameters != null) {
|
|
return _theHostingEnvironment._hostingParameters.FcnSkipReadAndCacheDacls;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public override Object InitializeLifetimeService() {
|
|
return null; // never expire lease
|
|
}
|
|
|
|
/// <internalonly/>
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public HostingEnvironment() {
|
|
if (_theHostingEnvironment != null)
|
|
throw new InvalidOperationException(SR.GetString(SR.Only_1_HostEnv));
|
|
|
|
// remember singleton HostingEnvironment in a static
|
|
_theHostingEnvironment = this;
|
|
|
|
// start watching for app domain unloading
|
|
_onAppDomainUnload = new EventHandler(OnAppDomainUnload);
|
|
Thread.GetDomain().DomainUnload += _onAppDomainUnload;
|
|
|
|
// VSO 160528: We used to listen to the default AppDomain's UnhandledException only.
|
|
// However, non-serializable exceptions cannot be passed to the default domain. Therefore
|
|
// we should try to log exceptions in application AppDomains.
|
|
Thread.GetDomain().UnhandledException += new UnhandledExceptionEventHandler(ApplicationManager.OnUnhandledException);
|
|
}
|
|
|
|
internal static long TrimCache(int percent)
|
|
{
|
|
if (_theHostingEnvironment != null)
|
|
return _theHostingEnvironment.TrimCacheInternal(percent);
|
|
return 0;
|
|
}
|
|
|
|
private long TrimCacheInternal(int percent)
|
|
{
|
|
if (Interlocked.Exchange(ref _inTrimCache, 1) != 0)
|
|
return 0;
|
|
try {
|
|
long trimmedOrExpired = 0;
|
|
// do nothing if we're shutting down
|
|
if (!_shutdownInitiated) {
|
|
var iCache = HttpRuntime.Cache.GetInternalCache(createIfDoesNotExist: false);
|
|
var oCache = HttpRuntime.Cache.GetObjectCache(createIfDoesNotExist: false);
|
|
if (oCache != null) {
|
|
trimmedOrExpired = oCache.Trim(percent);
|
|
}
|
|
if (iCache != null && !iCache.Equals(oCache)) {
|
|
trimmedOrExpired += iCache.Trim(percent);
|
|
}
|
|
if (_objectCacheHost != null && !_shutdownInitiated) {
|
|
trimmedOrExpired += _objectCacheHost.TrimCache(percent);
|
|
}
|
|
}
|
|
return trimmedOrExpired;
|
|
}
|
|
finally {
|
|
Interlocked.Exchange(ref _inTrimCache, 0);
|
|
}
|
|
}
|
|
|
|
private void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) {
|
|
Debug.Trace("PipelineRuntime", "HE.OnAppDomainUnload");
|
|
|
|
Thread.GetDomain().DomainUnload -= _onAppDomainUnload;
|
|
|
|
// check for unexpected shutdown
|
|
if (!_removedFromAppManager) {
|
|
RemoveThisAppDomainFromAppManagerTableOnce();
|
|
}
|
|
|
|
HttpRuntime.RecoverFromUnexceptedAppDomainUnload();
|
|
|
|
// call Stop on all registered objects with immediate = true
|
|
StopRegisteredObjects(true);
|
|
|
|
// notify app manager
|
|
if (_appManager != null) {
|
|
// disconnect the real app host and substitute it with a bogus one
|
|
// to avoid exceptions later when app host is called (it normally wouldn't)
|
|
IApplicationHost originalAppHost = null;
|
|
|
|
if (_externalAppHost) {
|
|
originalAppHost = _appHost;
|
|
_appHost = new SimpleApplicationHost(_appVirtualPath, _appPhysicalPath);
|
|
_externalAppHost = false;
|
|
}
|
|
|
|
IDisposable configSystem = _configMapPath2 as IDisposable;
|
|
if (configSystem != null) {
|
|
configSystem.Dispose();
|
|
}
|
|
|
|
_appManager.HostingEnvironmentShutdownComplete(_appId, originalAppHost);
|
|
}
|
|
|
|
// free the config access token
|
|
if (_configToken != IntPtr.Zero) {
|
|
UnsafeNativeMethods.CloseHandle(_configToken);
|
|
_configToken = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
// called from app manager right after app domain (and hosting env) is created
|
|
internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters, PolicyLevel policyLevel) {
|
|
Initialize(appManager, appHost, configMapPathFactory, hostingParameters, policyLevel, null);
|
|
}
|
|
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "We carefully control this method's callers.")]
|
|
internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory,
|
|
HostingEnvironmentParameters hostingParameters, PolicyLevel policyLevel,
|
|
Exception appDomainCreationException) {
|
|
|
|
_hostingParameters = hostingParameters;
|
|
|
|
HostingEnvironmentFlags hostingFlags = HostingEnvironmentFlags.Default;
|
|
if (_hostingParameters != null) {
|
|
hostingFlags = _hostingParameters.HostingFlags;
|
|
if (_hostingParameters.IISExpressVersion != null) {
|
|
ServerConfig.IISExpressVersion = _hostingParameters.IISExpressVersion;
|
|
}
|
|
}
|
|
|
|
// Keep track of the app manager, unless HideFromAppManager flag was passed
|
|
if ((hostingFlags & HostingEnvironmentFlags.HideFromAppManager) == 0)
|
|
_appManager = appManager;
|
|
|
|
if ((hostingFlags & HostingEnvironmentFlags.ClientBuildManager) != 0) {
|
|
BuildManagerHost.InClientBuildManager = true;
|
|
}
|
|
|
|
if ((hostingFlags & HostingEnvironmentFlags.SupportsMultiTargeting) != 0) {
|
|
BuildManagerHost.SupportsMultiTargeting = true;
|
|
}
|
|
|
|
// Set CLR quirks switches before the config system is initialized since config might depend on them
|
|
if (_hostingParameters != null && _hostingParameters.ClrQuirksSwitches != null && _hostingParameters.ClrQuirksSwitches.Length > 0) {
|
|
SetClrQuirksSwitches(_hostingParameters.ClrQuirksSwitches);
|
|
}
|
|
|
|
//
|
|
// init config system using private config if applicable
|
|
//
|
|
if (appHost is ISAPIApplicationHost && !ServerConfig.UseMetabase) {
|
|
string rootWebConfigPath = ((ISAPIApplicationHost)appHost).ResolveRootWebConfigPath();
|
|
if (!String.IsNullOrEmpty(rootWebConfigPath)) {
|
|
Debug.Assert(File.Exists(rootWebConfigPath), "File.Exists(rootWebConfigPath)");
|
|
HttpConfigurationSystem.RootWebConfigurationFilePath = rootWebConfigPath;
|
|
}
|
|
|
|
// we need to explicit create a COM proxy in this app domain
|
|
// so we don't go back to the default domain or have lifetime issues
|
|
// remember support functions
|
|
IProcessHostSupportFunctions proxyFunctions = ((ISAPIApplicationHost)appHost).SupportFunctions;
|
|
if (null != proxyFunctions) {
|
|
_functions = Misc.CreateLocalSupportFunctions(proxyFunctions);
|
|
}
|
|
}
|
|
|
|
_appId = HttpRuntime.AppDomainAppId;
|
|
_appVirtualPath = HttpRuntime.AppDomainAppVirtualPathObject;
|
|
_appPhysicalPath = HttpRuntime.AppDomainAppPathInternal;
|
|
_appHost = appHost;
|
|
|
|
_configMapPath = configMapPathFactory.Create(_appVirtualPath.VirtualPathString, _appPhysicalPath);
|
|
HttpConfigurationSystem.EnsureInit(_configMapPath, true, false);
|
|
|
|
// attempt to cache and use IConfigMapPath2 provider
|
|
// which supports VirtualPath's to save on conversions
|
|
_configMapPath2 = _configMapPath as IConfigMapPath2;
|
|
|
|
|
|
_initiateShutdownWorkItemCallback = new WaitCallback(this.InitiateShutdownWorkItemCallback);
|
|
|
|
// notify app manager
|
|
if (_appManager != null) {
|
|
_appManager.HostingEnvironmentActivated();
|
|
}
|
|
|
|
// make sure there is always app host
|
|
if (_appHost == null) {
|
|
_appHost = new SimpleApplicationHost(_appVirtualPath, _appPhysicalPath);
|
|
}
|
|
else {
|
|
_externalAppHost = true;
|
|
}
|
|
|
|
// remember the token to access config
|
|
_configToken = _appHost.GetConfigToken();
|
|
|
|
// Start with a MapPath based virtual path provider
|
|
_mapPathBasedVirtualPathProvider = new MapPathBasedVirtualPathProvider();
|
|
_virtualPathProvider = _mapPathBasedVirtualPathProvider;
|
|
|
|
// initiaze HTTP-independent features
|
|
HttpRuntime.InitializeHostingFeatures(hostingFlags, policyLevel, appDomainCreationException);
|
|
|
|
// VSWhidbey 393259. Do not monitor idle timeout for CBM since Venus
|
|
// will always restart a new appdomain if old one is shutdown.
|
|
if (!BuildManagerHost.InClientBuildManager) {
|
|
// start monitoring for idle inside app domain
|
|
StartMonitoringForIdleTimeout();
|
|
}
|
|
|
|
// notify app manager if the app domain limit is violated
|
|
EnforceAppDomainLimit();
|
|
|
|
// get application identity (for explicit impersonation mode)
|
|
GetApplicationIdentity();
|
|
|
|
_applicationMonitors = new ApplicationMonitors();
|
|
|
|
// call AppInitialize, unless the flag says not to do it (e.g. CBM scenario).
|
|
// Also, don't call it if HostingInit failed (VSWhidbey 210495)
|
|
if(!HttpRuntime.HostingInitFailed) {
|
|
try {
|
|
BuildManager.ExecutePreAppStart();
|
|
if ((hostingFlags & HostingEnvironmentFlags.DontCallAppInitialize) == 0) {
|
|
BuildManager.CallAppInitializeMethod();
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
// could throw compilation errors in 'code' - report them with first http request
|
|
HttpRuntime.InitializationException = e;
|
|
|
|
if ((hostingFlags & HostingEnvironmentFlags.ThrowHostingInitErrors) != 0) {
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitializeObjectCacheHostPrivate() {
|
|
// set ObjectCacheHost if the Host is not already set
|
|
if (ObjectCache.Host == null) {
|
|
ObjectCacheHost objectCacheHost = new ObjectCacheHost();
|
|
ObjectCache.Host = objectCacheHost;
|
|
_objectCacheHost = objectCacheHost;
|
|
}
|
|
}
|
|
|
|
internal static void InitializeObjectCacheHost() {
|
|
if (_theHostingEnvironment != null) {
|
|
_theHostingEnvironment.InitializeObjectCacheHostPrivate();
|
|
}
|
|
}
|
|
|
|
private void StartMonitoringForIdleTimeout() {
|
|
HostingEnvironmentSection hostEnvConfig = RuntimeConfig.GetAppLKGConfig().HostingEnvironment;
|
|
|
|
TimeSpan idleTimeout = (hostEnvConfig != null) ? hostEnvConfig.IdleTimeout : HostingEnvironmentSection.DefaultIdleTimeout;
|
|
|
|
// always create IdleTimeoutMonitor (even if config value is TimeSpan.MaxValue (infinite)
|
|
// IdleTimeoutMonitor is also needed to keep the last event for app domain set trimming
|
|
// and the timer is used to trim the application instances
|
|
_idleTimeoutMonitor = new IdleTimeoutMonitor(idleTimeout);
|
|
}
|
|
|
|
// enforce app domain limit
|
|
private void EnforceAppDomainLimit() {
|
|
if (_appManager == null) /// detached app domain
|
|
return;
|
|
|
|
int limit = 0;
|
|
|
|
try {
|
|
ProcessModelSection pmConfig = RuntimeConfig.GetMachineConfig().ProcessModel;
|
|
limit = pmConfig.MaxAppDomains;
|
|
}
|
|
catch {
|
|
}
|
|
|
|
if (limit > 0 && _appManager.AppDomainsCount >= limit) {
|
|
// current app domain doesn't count yet (not in the table)
|
|
// that's why '>=' above
|
|
_appManager.ReduceAppDomainsCount(limit);
|
|
}
|
|
}
|
|
|
|
private void GetApplicationIdentity() {
|
|
// if the explicit impersonation is set, use it instead of UNC identity
|
|
try {
|
|
IdentitySection c = RuntimeConfig.GetAppConfig().Identity;
|
|
if (c.Impersonate && c.ImpersonateToken != IntPtr.Zero) {
|
|
_appIdentity = c;
|
|
_appIdentityToken = c.ImpersonateToken;
|
|
}
|
|
else {
|
|
_appIdentityToken = _configToken;
|
|
}
|
|
_appIdentityTokenSet = true;
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
|
|
private static void SetClrQuirksSwitches(KeyValuePair<string, bool>[] switches) {
|
|
// First, see if the static API AppContext.SetSwitch even exists.
|
|
// Type.GetType will return null if the type doesn't exist; it will throw on catastrophic failure.
|
|
|
|
Type appContextType = Type.GetType("System.AppContext, " + AssemblyRef.Mscorlib);
|
|
if (appContextType == null) {
|
|
return; // wrong version of mscorlib - do nothing
|
|
}
|
|
|
|
Action<string, bool> setter = (Action<string, bool>)Delegate.CreateDelegate(
|
|
typeof(Action<string, bool>),
|
|
appContextType,
|
|
"SetSwitch",
|
|
ignoreCase: false,
|
|
throwOnBindFailure: false);
|
|
if (setter == null) {
|
|
return; // wrong version of mscorlib - do nothing
|
|
}
|
|
|
|
// Finally, set each switch individually.
|
|
|
|
foreach (var sw in switches) {
|
|
setter(sw.Key, sw.Value);
|
|
}
|
|
}
|
|
|
|
|
|
// If an exception was thrown during initialization, return it.
|
|
public static Exception InitializationException {
|
|
get {
|
|
return HttpRuntime.InitializationException;
|
|
}
|
|
}
|
|
|
|
// called from app manager (from management APIs)
|
|
internal ApplicationInfo GetApplicationInfo() {
|
|
return new ApplicationInfo(_appId, _appVirtualPath, _appPhysicalPath);
|
|
}
|
|
|
|
//
|
|
// Shutdown logic
|
|
//
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
private void StopRegisteredObjects(bool immediate) {
|
|
if (_registeredObjects.Count > 0) {
|
|
ArrayList list = new ArrayList();
|
|
|
|
lock (this) {
|
|
foreach (DictionaryEntry e in _registeredObjects) {
|
|
Object x = e.Key;
|
|
|
|
// well-known objects first
|
|
if (IsWellKnownObject(x)) {
|
|
list.Insert(0, x);
|
|
}
|
|
else {
|
|
list.Add(x);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (IRegisteredObject obj in list) {
|
|
try {
|
|
obj.Stop(immediate);
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitiateShutdownWorkItemCallback(Object state /*not used*/) {
|
|
Debug.Trace("HostingEnvironmentShutdown", "Shutting down: appId=" + _appId);
|
|
|
|
// no registered objects -- shutdown
|
|
if (_registeredObjects.Count == 0) {
|
|
Debug.Trace("HostingEnvironmentShutdown", "No registered objects");
|
|
ShutdownThisAppDomainOnce();
|
|
return;
|
|
}
|
|
|
|
// call Stop on all registered objects with immediate = false
|
|
StopRegisteredObjects(false);
|
|
|
|
// no registered objects -- shutdown now
|
|
if (_registeredObjects.Count == 0) {
|
|
Debug.Trace("HostingEnvironmentShutdown", "All registered objects gone after Stop(false)");
|
|
ShutdownThisAppDomainOnce();
|
|
return;
|
|
}
|
|
|
|
// if not everything shutdown synchronously give it some time.
|
|
int shutdownTimeoutSeconds = HostingEnvironmentSection.DefaultShutdownTimeout;
|
|
HostingEnvironmentSection hostEnvConfig = RuntimeConfig.GetAppLKGConfig().HostingEnvironment;
|
|
if (hostEnvConfig != null) {
|
|
shutdownTimeoutSeconds = (int) hostEnvConfig.ShutdownTimeout.TotalSeconds;
|
|
}
|
|
Debug.Trace("HostingEnvironmentShutdown", "Waiting for " + shutdownTimeoutSeconds + " sec...");
|
|
|
|
DateTime waitUntil = DateTime.UtcNow.AddSeconds(shutdownTimeoutSeconds);
|
|
while (_registeredObjects.Count > 0 && DateTime.UtcNow < waitUntil) {
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
Debug.Trace("HostingEnvironmentShutdown", "Shutdown timeout (" + shutdownTimeoutSeconds + " sec) expired");
|
|
|
|
// call Stop on all registered objects with immediate = true
|
|
StopRegisteredObjects(true);
|
|
|
|
// no registered objects -- shutdown now
|
|
if (_registeredObjects.Count == 0) {
|
|
Debug.Trace("HostingEnvironmentShutdown", "All registered objects gone after Stop(true)");
|
|
ShutdownThisAppDomainOnce();
|
|
return;
|
|
}
|
|
|
|
// shutdown regardless
|
|
Debug.Trace("HostingEnvironmentShutdown", "Forced shutdown: " + _registeredObjects.Count + " registered objects left");
|
|
_registeredObjects = new Hashtable();
|
|
ShutdownThisAppDomainOnce();
|
|
}
|
|
|
|
// app domain shutdown logic
|
|
internal void InitiateShutdownInternal() {
|
|
#if DBG
|
|
try {
|
|
#endif
|
|
Debug.Trace("AppManager", "HostingEnvironment.InitiateShutdownInternal appId=" + _appId);
|
|
|
|
bool proceed = false;
|
|
|
|
if (!_shutdownInitiated) {
|
|
lock (this) {
|
|
if (!_shutdownInitiated) {
|
|
_shutdownInProgress = true;
|
|
proceed = true;
|
|
_shutdownInitiated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proceed) {
|
|
return;
|
|
}
|
|
|
|
HttpRuntime.SetShutdownReason(ApplicationShutdownReason.HostingEnvironment, "HostingEnvironment initiated shutdown");
|
|
|
|
// Avoid calling Environment.StackTrace if we are in the ClientBuildManager (Dev10 bug 824659)
|
|
if (!BuildManagerHost.InClientBuildManager) {
|
|
new EnvironmentPermission(PermissionState.Unrestricted).Assert();
|
|
try {
|
|
_shutDownStack = Environment.StackTrace;
|
|
}
|
|
finally {
|
|
CodeAccessPermission.RevertAssert();
|
|
}
|
|
}
|
|
|
|
// waitChangeNotification need not be honored in ClientBuildManager (Dev11 bug 264894)
|
|
if (!BuildManagerHost.InClientBuildManager) {
|
|
// this should only be called once, before the cache is disposed, and
|
|
// the config records are released.
|
|
HttpRuntime.CoalesceNotifications();
|
|
}
|
|
|
|
RemoveThisAppDomainFromAppManagerTableOnce();
|
|
|
|
// stop all registered objects without blocking
|
|
ThreadPool.QueueUserWorkItem(this._initiateShutdownWorkItemCallback);
|
|
#if DBG
|
|
} catch (Exception ex) {
|
|
HandleExceptionFromInitiateShutdownInternal(ex);
|
|
throw;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if DBG
|
|
// InitiateShutdownInternal should never throw an exception, but we have seen cases where
|
|
// CLR bugs can cause it to fail without running to completion. This could cause an ASP.NET
|
|
// AppDomain never to unload. If we detect that an exception is thrown, we should DebugBreak
|
|
// so that the fundamentals team can investigate. Taking the Exception object as a parameter
|
|
// makes it easy to locate when looking at a stack dump.
|
|
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
|
|
private static void HandleExceptionFromInitiateShutdownInternal(Exception ex) {
|
|
Debug.Break();
|
|
}
|
|
#endif
|
|
|
|
internal bool HasBeenRemovedFromAppManagerTable {
|
|
get {
|
|
return _hasBeenRemovedFromAppManangerTable;
|
|
}
|
|
set {
|
|
_hasBeenRemovedFromAppManangerTable = value;
|
|
}
|
|
}
|
|
|
|
private void RemoveThisAppDomainFromAppManagerTableOnce() {
|
|
bool proceed = false;
|
|
if (!_removedFromAppManager) {
|
|
lock (this) {
|
|
if (!_removedFromAppManager) {
|
|
proceed = true;
|
|
_removedFromAppManager = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proceed)
|
|
return;
|
|
|
|
if (_appManager != null) {
|
|
Debug.Trace("AppManager", "Removing HostingEnvironment from AppManager table, appId=" + _appId);
|
|
_appManager.HostingEnvironmentShutdownInitiated(_appId, this);
|
|
}
|
|
#if DBG
|
|
Debug.Trace("FileChangesMonitorIgnoreSubdirChange",
|
|
"*** REMOVE APPMANAGER TABLE" + DateTime.Now.ToString("hh:mm:ss.fff", CultureInfo.InvariantCulture)
|
|
+ ": _appId=" + _appId);
|
|
#endif
|
|
}
|
|
|
|
private void ShutdownThisAppDomainOnce() {
|
|
bool proceed = false;
|
|
|
|
if (!_appDomainShutdownStarted) {
|
|
lock (this) {
|
|
if (!_appDomainShutdownStarted) {
|
|
proceed = true;
|
|
_appDomainShutdownStarted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!proceed)
|
|
return;
|
|
|
|
Debug.Trace("AppManager", "HostingEnvironment - shutting down AppDomain, appId=" + _appId);
|
|
|
|
// stop the timer used for idle timeout
|
|
if (_idleTimeoutMonitor != null) {
|
|
_idleTimeoutMonitor.Stop();
|
|
_idleTimeoutMonitor = null;
|
|
}
|
|
|
|
while (_inTrimCache == 1) {
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
// close all outstanding WebSocket connections and begin winding down code that consumes them
|
|
AspNetWebSocketManager.Current.AbortAllAndWait();
|
|
|
|
//
|
|
HttpRuntime.SetUserForcedShutdown();
|
|
|
|
//WOS 1400290: CantUnloadAppDomainException in ISAPI mode, wait until HostingEnvironment.ShutdownThisAppDomainOnce completes
|
|
_shutdownInProgress = false;
|
|
|
|
HttpRuntime.ShutdownAppDomainWithStackTrace(ApplicationShutdownReason.HostingEnvironment,
|
|
SR.GetString(SR.Hosting_Env_Restart),
|
|
_shutDownStack);
|
|
}
|
|
|
|
//
|
|
// internal methods called by app manager
|
|
//
|
|
|
|
// helper for app manager to implement AppHost.CreateAppHost
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
internal ObjectHandle CreateInstance(String assemblyQualifiedName) {
|
|
Type type = Type.GetType(assemblyQualifiedName, true);
|
|
return new ObjectHandle(Activator.CreateInstance(type));
|
|
}
|
|
|
|
// start well known object
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
internal ObjectHandle CreateWellKnownObjectInstance(String assemblyQualifiedName, bool failIfExists) {
|
|
Type type = Type.GetType(assemblyQualifiedName, true);
|
|
IRegisteredObject obj = null;
|
|
String key = type.FullName;
|
|
bool exists = false;
|
|
|
|
lock (this) {
|
|
obj = _wellKnownObjects[key] as IRegisteredObject;
|
|
|
|
if (obj == null) {
|
|
obj = (IRegisteredObject)Activator.CreateInstance(type);
|
|
_wellKnownObjects[key] = obj;
|
|
}
|
|
else {
|
|
exists = true;
|
|
}
|
|
}
|
|
|
|
if (exists && failIfExists) {
|
|
throw new InvalidOperationException(SR.GetString(SR.Wellknown_object_already_exists, key));
|
|
}
|
|
|
|
return new ObjectHandle(obj);
|
|
}
|
|
|
|
// check if well known object
|
|
private bool IsWellKnownObject(Object obj) {
|
|
bool found = false;
|
|
String key = obj.GetType().FullName;
|
|
|
|
lock (this) {
|
|
if (_wellKnownObjects[key] == obj) {
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
// find well known object by type
|
|
internal ObjectHandle FindWellKnownObject(String assemblyQualifiedName) {
|
|
Type type = Type.GetType(assemblyQualifiedName, true);
|
|
IRegisteredObject obj = null;
|
|
String key = type.FullName;
|
|
|
|
lock (this) {
|
|
obj = _wellKnownObjects[key] as IRegisteredObject;
|
|
}
|
|
|
|
return (obj != null) ? new ObjectHandle(obj) : null;
|
|
}
|
|
|
|
// stop well known object by type
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
internal void StopWellKnownObject(String assemblyQualifiedName) {
|
|
Type type = Type.GetType(assemblyQualifiedName, true);
|
|
IRegisteredObject obj = null;
|
|
String key = type.FullName;
|
|
|
|
lock (this) {
|
|
obj = _wellKnownObjects[key] as IRegisteredObject;
|
|
if (obj != null) {
|
|
_wellKnownObjects.Remove(key);
|
|
obj.Stop(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal bool IsIdle() {
|
|
bool isBusy = _isBusy;
|
|
_isBusy = false;
|
|
return (!isBusy && _busyCount == 0);
|
|
}
|
|
|
|
internal bool GetIdleValue() {
|
|
return (!_isBusy && _busyCount == 0);
|
|
}
|
|
|
|
internal void IncrementBusyCountInternal() {
|
|
_isBusy = true;
|
|
Interlocked.Increment(ref _busyCount);
|
|
}
|
|
|
|
internal void DecrementBusyCountInternal() {
|
|
_isBusy = true;
|
|
Interlocked.Decrement(ref _busyCount);
|
|
|
|
// Notify idle timeout monitor
|
|
IdleTimeoutMonitor itm = _idleTimeoutMonitor;
|
|
if (itm != null) {
|
|
itm.LastEvent = DateTime.UtcNow;
|
|
}
|
|
}
|
|
internal void IsUnloaded()
|
|
{
|
|
return;
|
|
}
|
|
|
|
private void MessageReceivedInternal() {
|
|
_isBusy = true;
|
|
|
|
IdleTimeoutMonitor itm = _idleTimeoutMonitor;
|
|
if (itm != null) {
|
|
itm.LastEvent = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
// the busier the app domain the higher the score
|
|
internal int LruScore {
|
|
get {
|
|
if (_busyCount > 0)
|
|
return _busyCount;
|
|
|
|
IdleTimeoutMonitor itm = _idleTimeoutMonitor;
|
|
|
|
if (itm == null)
|
|
return 0;
|
|
|
|
// return negative number of seconds since last activity
|
|
return -(int)(DateTime.UtcNow - itm.LastEvent).TotalSeconds;
|
|
}
|
|
}
|
|
|
|
internal static ApplicationManager GetApplicationManager() {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._appManager;
|
|
}
|
|
|
|
//
|
|
// private helpers
|
|
//
|
|
|
|
// register protocol handler with hosting environment
|
|
private void RegisterRunningObjectInternal(IRegisteredObject obj) {
|
|
lock (this) {
|
|
_registeredObjects[obj] = obj;
|
|
|
|
ISuspendibleRegisteredObject suspendibleObject = obj as ISuspendibleRegisteredObject;
|
|
if (suspendibleObject != null) {
|
|
_suspendManager.RegisterObject(suspendibleObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
// unregister protocol handler from hosting environment
|
|
private void UnregisterRunningObjectInternal(IRegisteredObject obj) {
|
|
bool lastOne = false;
|
|
|
|
lock (this) {
|
|
// if it is a well known object, remove it from that table as well
|
|
String key = obj.GetType().FullName;
|
|
if (_wellKnownObjects[key] == obj) {
|
|
_wellKnownObjects.Remove(key);
|
|
}
|
|
|
|
// remove from running objects list
|
|
_registeredObjects.Remove(obj);
|
|
|
|
ISuspendibleRegisteredObject suspendibleObject = obj as ISuspendibleRegisteredObject;
|
|
if (suspendibleObject != null) {
|
|
_suspendManager.UnregisterObject(suspendibleObject);
|
|
}
|
|
|
|
if (_registeredObjects.Count == 0)
|
|
lastOne = true;
|
|
}
|
|
|
|
if (!lastOne)
|
|
return;
|
|
|
|
// shutdown app domain after last protocol handler is gone
|
|
|
|
InitiateShutdownInternal();
|
|
}
|
|
|
|
// site name
|
|
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "This method is not dangerous.")]
|
|
private String GetSiteName() {
|
|
if (_siteName == null) {
|
|
lock (this) {
|
|
if (_siteName == null) {
|
|
String s = null;
|
|
|
|
if (_appHost != null) {
|
|
//
|
|
InternalSecurityPermissions.Unrestricted.Assert();
|
|
try {
|
|
s = _appHost.GetSiteName();
|
|
}
|
|
finally {
|
|
CodeAccessPermission.RevertAssert();
|
|
}
|
|
}
|
|
|
|
if (s == null)
|
|
s = WebConfigurationHost.DefaultSiteName;
|
|
|
|
_siteName = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
return _siteName;
|
|
}
|
|
|
|
// site ID
|
|
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "This method is not dangerous.")]
|
|
private String GetSiteID() {
|
|
if (_siteID == null) {
|
|
lock (this) {
|
|
if (_siteID == null) {
|
|
String s = null;
|
|
|
|
if (_appHost != null) {
|
|
//
|
|
InternalSecurityPermissions.Unrestricted.Assert();
|
|
try {
|
|
s = _appHost.GetSiteID();
|
|
}
|
|
finally {
|
|
CodeAccessPermission.RevertAssert();
|
|
}
|
|
}
|
|
|
|
if (s == null)
|
|
s = WebConfigurationHost.DefaultSiteID;
|
|
|
|
_siteID = s.ToLower(CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _siteID;
|
|
}
|
|
|
|
// Return the configPath for the app, e.g. "machine/webroot/1/myapp"
|
|
private String GetAppConfigPath() {
|
|
if (_appConfigPath == null) {
|
|
_appConfigPath = WebConfigurationHost.GetConfigPathFromSiteIDAndVPath(SiteID, ApplicationVirtualPathObject);
|
|
}
|
|
|
|
return _appConfigPath;
|
|
}
|
|
|
|
// Return the call context slot name to use for a virtual path
|
|
private static string GetFixedMappingSlotName(VirtualPath virtualPath) {
|
|
return "MapPath_" + virtualPath.VirtualPathString.ToLowerInvariant().GetHashCode().ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
/*
|
|
* Map a virtual path to a physical path. i.e. the physicalPath will be returned
|
|
* when MapPath is called on the virtual path, bypassing the IApplicationHost
|
|
*/
|
|
private static string GetVirtualPathToFileMapping(VirtualPath virtualPath) {
|
|
return CallContext.GetData(GetFixedMappingSlotName(virtualPath)) as string;
|
|
}
|
|
|
|
/*
|
|
* Map a virtual path to a physical path. i.e. the physicalPath will be returned
|
|
* when MapPath is called on the virtual path, bypassing the IApplicationHost
|
|
*/
|
|
internal static object AddVirtualPathToFileMapping(
|
|
VirtualPath virtualPath, string physicalPath) {
|
|
|
|
// Save the mapping in the call context, using a key derived from the
|
|
// virtual path. The mapping is only valid for the duration of the request.
|
|
CallContext.SetData(GetFixedMappingSlotName(virtualPath), physicalPath);
|
|
|
|
// Return a mapping object to keep track of the virtual path, and of the current
|
|
// virtualPathProvider.
|
|
VirtualPathToFileMappingState state = new VirtualPathToFileMappingState();
|
|
state.VirtualPath = virtualPath;
|
|
state.VirtualPathProvider = _theHostingEnvironment._virtualPathProvider;
|
|
|
|
// Always use the MapPathBasedVirtualPathProvider, otherwise the mapping mechanism
|
|
// doesn't work (VSWhidbey 420702)
|
|
// Set/Get the VPP on the call context so as not to affect other concurrent requests (Dev10 852255)
|
|
CallContext.SetData(TemporaryVirtualPathProviderKey, _theHostingEnvironment._mapPathBasedVirtualPathProvider);
|
|
|
|
return state;
|
|
}
|
|
|
|
internal static void ClearVirtualPathToFileMapping(object state) {
|
|
|
|
VirtualPathToFileMappingState mapping = (VirtualPathToFileMappingState)state;
|
|
|
|
// Clear the mapping from the call context
|
|
CallContext.SetData(GetFixedMappingSlotName(mapping.VirtualPath), null);
|
|
|
|
// Restore the previous VirtualPathProvider
|
|
// Set/Get the VPP on the call context so as not to affect other concurrent requests (Dev10 852255)
|
|
CallContext.SetData(TemporaryVirtualPathProviderKey, null);
|
|
}
|
|
|
|
private string MapPathActual(VirtualPath virtualPath, bool permitNull)
|
|
{
|
|
string result = null;
|
|
|
|
Debug.Assert(virtualPath != null);
|
|
|
|
virtualPath.FailIfRelativePath();
|
|
|
|
VirtualPath reqpath = virtualPath;
|
|
|
|
if (String.CompareOrdinal(reqpath.VirtualPathString, _appVirtualPath.VirtualPathString) == 0) {
|
|
// for application path don't need to call app host
|
|
Debug.Trace("MapPath", reqpath +" is the app path");
|
|
result = _appPhysicalPath;
|
|
}
|
|
else {
|
|
using (new ProcessImpersonationContext()) {
|
|
// If there is a mapping for this virtual path in the call context, use it
|
|
result = GetVirtualPathToFileMapping(reqpath);
|
|
|
|
if (result == null) {
|
|
// call host's mappath
|
|
if (_configMapPath == null) {
|
|
Debug.Trace("MapPath", "Missing _configMapPath");
|
|
throw new InvalidOperationException(SR.GetString(SR.Cannot_map_path, reqpath));
|
|
}
|
|
Debug.Trace("MapPath", "call ConfigMapPath (" + reqpath + ")");
|
|
|
|
// see if the IConfigMapPath provider implements the interface
|
|
// with VirtualPath
|
|
try {
|
|
if (null != _configMapPath2) {
|
|
result = _configMapPath2.MapPath(GetSiteID(), reqpath);
|
|
}
|
|
else {
|
|
result = _configMapPath.MapPath(GetSiteID(), reqpath.VirtualPathString);
|
|
}
|
|
if (HttpRuntime.IsMapPathRelaxed)
|
|
result = HttpRuntime.GetRelaxedMapPathResult(result);
|
|
} catch {
|
|
if (HttpRuntime.IsMapPathRelaxed)
|
|
result = HttpRuntime.GetRelaxedMapPathResult(null);
|
|
else
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(result)) {
|
|
Debug.Trace("MapPath", "null Result");
|
|
if (!permitNull) {
|
|
if (HttpRuntime.IsMapPathRelaxed)
|
|
result = HttpRuntime.GetRelaxedMapPathResult(null);
|
|
else
|
|
throw new InvalidOperationException(SR.GetString(SR.Cannot_map_path, reqpath));
|
|
}
|
|
}
|
|
else {
|
|
// ensure extra '\\' in the physical path if the virtual path had extra '/'
|
|
// and the other way -- no extra '\\' in physical if virtual didn't have it.
|
|
if (virtualPath.HasTrailingSlash) {
|
|
if (!UrlPath.PathEndsWithExtraSlash(result) && !UrlPath.PathIsDriveRoot(result))
|
|
result = result + "\\";
|
|
}
|
|
else {
|
|
if (UrlPath.PathEndsWithExtraSlash(result) && !UrlPath.PathIsDriveRoot(result))
|
|
result = result.Substring(0, result.Length - 1);
|
|
}
|
|
|
|
Debug.Trace("MapPath", " result=" + result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//
|
|
// public static methods
|
|
//
|
|
|
|
|
|
// register protocol handler with hosting environment
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public static void RegisterObject(IRegisteredObject obj) {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.RegisterRunningObjectInternal(obj);
|
|
}
|
|
|
|
|
|
// unregister protocol handler from hosting environment
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public static void UnregisterObject(IRegisteredObject obj) {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.UnregisterRunningObjectInternal(obj);
|
|
}
|
|
|
|
// Schedules a task which can run in the background, independent of any request.
|
|
// This differs from a normal ThreadPool work item in that ASP.NET can keep track
|
|
// of how many work items registered through this API are currently running, and
|
|
// the ASP.NET runtime will try not to delay AppDomain shutdown until these work
|
|
// items have finished executing.
|
|
//
|
|
// Usage notes:
|
|
// - This API cannot be called outside of an ASP.NET-managed AppDomain.
|
|
// - The caller's ExecutionContext is not flowed to the work item.
|
|
// - Scheduled work items are not guaranteed to ever execute, e.g., when AppDomain
|
|
// shutdown has already started by the time this API was called.
|
|
// - The provided CancellationToken will be signaled when the application is
|
|
// shutting down. The work item should make every effort to honor this token.
|
|
// If a work item does not honor this token and continues executing it will
|
|
// eventually be considered rogue, and the ASP.NET runtime will rudely unload
|
|
// the AppDomain without waiting for the work item to finish.
|
|
//
|
|
// This overload of QueueBackgroundWorkItem takes a void-returning callback; the
|
|
// work item will be considered finished when the callback returns.
|
|
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
|
|
public static void QueueBackgroundWorkItem(Action<CancellationToken> workItem) {
|
|
if (workItem == null) {
|
|
throw new ArgumentNullException("workItem");
|
|
}
|
|
|
|
QueueBackgroundWorkItem(ct => { workItem(ct); return _completedTask; });
|
|
}
|
|
|
|
// See documentation on the other overload for a general API overview.
|
|
//
|
|
// This overload of QueueBackgroundWorkItem takes a Task-returning callback; the
|
|
// work item will be considered finished when the returned Task transitions to a
|
|
// terminal state.
|
|
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
|
|
public static void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem) {
|
|
if (workItem == null) {
|
|
throw new ArgumentNullException("workItem");
|
|
}
|
|
if (_theHostingEnvironment == null) {
|
|
throw new InvalidOperationException(); // can only be called within an ASP.NET AppDomain
|
|
}
|
|
|
|
_theHostingEnvironment.QueueBackgroundWorkItemInternal(workItem);
|
|
}
|
|
|
|
private void QueueBackgroundWorkItemInternal(Func<CancellationToken, Task> workItem) {
|
|
Debug.Assert(workItem != null);
|
|
|
|
BackgroundWorkScheduler scheduler = Volatile.Read(ref _backgroundWorkScheduler);
|
|
|
|
// If the scheduler doesn't exist, lazily create it, but only allow one instance to ever be published to the backing field
|
|
if (scheduler == null) {
|
|
BackgroundWorkScheduler newlyCreatedScheduler = new BackgroundWorkScheduler(UnregisterObject, Misc.WriteUnhandledExceptionToEventLog);
|
|
scheduler = Interlocked.CompareExchange(ref _backgroundWorkScheduler, newlyCreatedScheduler, null) ?? newlyCreatedScheduler;
|
|
if (scheduler == newlyCreatedScheduler) {
|
|
RegisterObject(scheduler); // Only call RegisterObject if we just created the "winning" one
|
|
}
|
|
}
|
|
|
|
scheduler.ScheduleWorkItem(workItem);
|
|
}
|
|
|
|
// This event is a simple way to hook IStopListeningRegisteredObject.StopListening
|
|
// without needing to call RegisterObject. The same restrictions which apply to
|
|
// that method apply to this event.
|
|
public static event EventHandler StopListening;
|
|
|
|
//
|
|
// public static methods for the user code to call
|
|
//
|
|
|
|
|
|
public static void IncrementBusyCount() {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.IncrementBusyCountInternal();
|
|
}
|
|
|
|
|
|
public static void DecrementBusyCount() {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.DecrementBusyCountInternal();
|
|
}
|
|
|
|
|
|
public static void MessageReceived() {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.MessageReceivedInternal();
|
|
}
|
|
|
|
public static bool InClientBuildManager {
|
|
get {
|
|
return BuildManagerHost.InClientBuildManager;
|
|
}
|
|
}
|
|
|
|
public static bool IsHosted {
|
|
get {
|
|
return (_theHostingEnvironment != null);
|
|
}
|
|
}
|
|
|
|
internal static bool IsUnderIISProcess {
|
|
get {
|
|
String process = VersionInfo.ExeName;
|
|
|
|
return process == "aspnet_wp" ||
|
|
process == "w3wp" ||
|
|
process == "inetinfo";
|
|
}
|
|
}
|
|
|
|
internal static bool IsUnderIIS6Process {
|
|
get {
|
|
return VersionInfo.ExeName == "w3wp";
|
|
}
|
|
}
|
|
|
|
public static IApplicationHost ApplicationHost {
|
|
//DevDivBugs 109864: ASP.NET: path discovery issue - In low trust, it is possible to get the physical path of any virtual path on the machine
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._appHost;
|
|
}
|
|
}
|
|
|
|
internal static IApplicationHost ApplicationHostInternal {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._appHost;
|
|
}
|
|
}
|
|
|
|
internal IApplicationHost InternalApplicationHost {
|
|
get {
|
|
return _appHost;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>A group of repleacable monitor objects used by ASP.Net subsystems to maintain
|
|
/// application health.</para>
|
|
/// </devdoc>
|
|
public static ApplicationMonitors ApplicationMonitors {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._applicationMonitors;
|
|
}
|
|
}
|
|
|
|
internal static int BusyCount {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return 0;
|
|
|
|
return _theHostingEnvironment._busyCount;
|
|
}
|
|
}
|
|
|
|
internal static bool ShutdownInitiated {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return false;
|
|
|
|
return _theHostingEnvironment._shutdownInitiated;
|
|
}
|
|
}
|
|
|
|
|
|
internal static bool ShutdownInProgress {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return false;
|
|
|
|
return _theHostingEnvironment._shutdownInProgress;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>The application ID (metabase path in IIS hosting).</para>
|
|
/// </devdoc>
|
|
public static String ApplicationID {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
InternalSecurityPermissions.AspNetHostingPermissionLevelHigh.Demand();
|
|
return _theHostingEnvironment._appId;
|
|
}
|
|
}
|
|
|
|
internal static String ApplicationIDNoDemand {
|
|
get {
|
|
if (_theHostingEnvironment == null) {
|
|
return null;
|
|
}
|
|
|
|
return _theHostingEnvironment._appId;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Physical path to the application root.</para>
|
|
/// </devdoc>
|
|
public static String ApplicationPhysicalPath {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
InternalSecurityPermissions.AppPathDiscovery.Demand();
|
|
return _theHostingEnvironment._appPhysicalPath;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Virtual path to the application root.</para>
|
|
/// </devdoc>
|
|
public static String ApplicationVirtualPath {
|
|
get {
|
|
return VirtualPath.GetVirtualPathStringNoTrailingSlash(ApplicationVirtualPathObject);
|
|
}
|
|
}
|
|
|
|
internal static VirtualPath ApplicationVirtualPathObject {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._appVirtualPath;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Site name.</para>
|
|
/// </devdoc>
|
|
public static String SiteName {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
InternalSecurityPermissions.AspNetHostingPermissionLevelMedium.Demand();
|
|
return _theHostingEnvironment.GetSiteName();
|
|
}
|
|
}
|
|
|
|
internal static String SiteNameNoDemand {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment.GetSiteName();
|
|
}
|
|
}
|
|
|
|
internal static String SiteID {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment.GetSiteID();
|
|
}
|
|
}
|
|
|
|
internal static IConfigMapPath ConfigMapPath {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._configMapPath;
|
|
}
|
|
}
|
|
|
|
internal static String AppConfigPath {
|
|
get {
|
|
if (_theHostingEnvironment == null) {
|
|
return null;
|
|
}
|
|
|
|
return _theHostingEnvironment.GetAppConfigPath();
|
|
}
|
|
}
|
|
|
|
// See comments in ApplicationManager.CreateAppDomainWithHostingEnvironment. This is the public API to access the
|
|
// information we determined in that method. Defaults to 'false' if our AppDomain data isn't present.
|
|
public static bool IsDevelopmentEnvironment {
|
|
get {
|
|
return (AppDomain.CurrentDomain.GetData(".devEnvironment") as bool?) == true;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets a reference to the System.Web.Cache.Cache object for the current request.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public static Cache Cache {
|
|
get { return HttpRuntime.Cache; }
|
|
}
|
|
|
|
internal static NameValueCollection CacheStoreProviderSettings {
|
|
get {
|
|
if (_cacheProviderSettings == null) {
|
|
if (AppDomain.CurrentDomain.IsDefaultAppDomain()) {
|
|
Configuration webConfig = WebConfigurationManager.OpenWebConfiguration(null /* root web.config */);
|
|
CacheSection cacheConfig = (CacheSection)webConfig.GetSection("system.web/caching/cache");
|
|
if (cacheConfig != null && cacheConfig.DefaultProvider != null && !String.IsNullOrWhiteSpace(cacheConfig.DefaultProvider)) {
|
|
ProviderSettingsCollection cacheProviders = cacheConfig.Providers;
|
|
if (cacheProviders == null || cacheProviders.Count < 1) {
|
|
throw new ProviderException(SR.GetString(SR.Def_provider_not_found));
|
|
}
|
|
|
|
ProviderSettings cacheProviderSettings = cacheProviders[cacheConfig.DefaultProvider];
|
|
if (cacheProviderSettings == null) {
|
|
throw new ProviderException(SR.GetString(SR.Def_provider_not_found));
|
|
}
|
|
|
|
NameValueCollection settings = cacheProviderSettings.Parameters;
|
|
settings["name"] = cacheProviderSettings.Name;
|
|
settings["type"] = cacheProviderSettings.Type;
|
|
_cacheProviderSettings = settings;
|
|
}
|
|
}
|
|
else {
|
|
_cacheProviderSettings = AppDomain.CurrentDomain.GetData(".defaultObjectCacheProvider") as NameValueCollection;
|
|
}
|
|
}
|
|
|
|
// Return a copy, so the consumer can't mess with our copy of the settings
|
|
if (_cacheProviderSettings != null)
|
|
return new NameValueCollection(_cacheProviderSettings);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// count of all app domain from app manager
|
|
internal static int AppDomainsCount {
|
|
get {
|
|
ApplicationManager appManager = GetApplicationManager();
|
|
return (appManager != null) ? appManager.AppDomainsCount : 0;
|
|
}
|
|
}
|
|
|
|
internal static HostingEnvironmentParameters HostingParameters {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
return _theHostingEnvironment._hostingParameters;
|
|
}
|
|
}
|
|
|
|
// Return an integer that is unique for each appdomain. This can be used
|
|
// to create things like once-per-appdomain temp files without having different
|
|
// processes/appdomains step on each other
|
|
private static int s_appDomainUniqueInteger;
|
|
internal static int AppDomainUniqueInteger {
|
|
get {
|
|
if (s_appDomainUniqueInteger == 0) {
|
|
s_appDomainUniqueInteger = Guid.NewGuid().GetHashCode();
|
|
}
|
|
|
|
return s_appDomainUniqueInteger;
|
|
}
|
|
}
|
|
|
|
public static ApplicationShutdownReason ShutdownReason {
|
|
get { return HttpRuntime.ShutdownReason; }
|
|
}
|
|
|
|
// Was CGlobalModule::OnGlobalStopListening called?
|
|
internal static bool StopListeningWasCalled {
|
|
get {
|
|
return _stopListeningWasCalled;
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive", Justification = "See comment in function.")]
|
|
internal static void SetupStopListeningHandler() {
|
|
StopListeningWaitHandle waitHandle = new StopListeningWaitHandle();
|
|
|
|
RegisteredWaitHandle registeredWaitHandle = null;
|
|
registeredWaitHandle = ThreadPool.UnsafeRegisterWaitForSingleObject(waitHandle, (_, __) => {
|
|
// Referencing the field from within the callback should be sufficient to keep the GC
|
|
// from reclaiming the RegisteredWaitHandle; the race condition is fine.
|
|
GC.KeepAlive(registeredWaitHandle);
|
|
OnGlobalStopListening();
|
|
}, null, Timeout.Infinite, executeOnlyOnce: true);
|
|
}
|
|
|
|
private static void OnGlobalStopListening() {
|
|
_stopListeningWasCalled = true;
|
|
|
|
EventHandler eventHandler = StopListening;
|
|
if (eventHandler != null) {
|
|
eventHandler(null /* static means no sender */, EventArgs.Empty);
|
|
}
|
|
|
|
if (_theHostingEnvironment != null) {
|
|
_theHostingEnvironment.FireStopListeningHandlers();
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Reliability", "CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification = "'this' always has strong identity.")]
|
|
private void FireStopListeningHandlers() {
|
|
List<IStopListeningRegisteredObject> listeners = new List<IStopListeningRegisteredObject>();
|
|
lock (this) {
|
|
foreach (DictionaryEntry e in _registeredObjects) {
|
|
IStopListeningRegisteredObject listener = e.Key as IStopListeningRegisteredObject;
|
|
if (listener != null) {
|
|
listeners.Add(listener);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var listener in listeners) {
|
|
listener.StopListening();
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Initiate app domain unloading for the current app.</para>
|
|
/// </devdoc>
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public static void InitiateShutdown() {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.InitiateShutdownInternal();
|
|
}
|
|
|
|
internal static void InitiateShutdownWithoutDemand() {
|
|
if (_theHostingEnvironment != null)
|
|
_theHostingEnvironment.InitiateShutdownInternal();
|
|
}
|
|
|
|
//
|
|
// Internal methods for the ApplicationManager to suspend / resume this application.
|
|
// Using GCHandle instead of ObjectHandle means we don't need to worry about lease lifetimes.
|
|
//
|
|
|
|
internal IntPtr SuspendApplication() {
|
|
var state = _suspendManager.Suspend();
|
|
return GCUtil.RootObject(state);
|
|
}
|
|
|
|
internal void ResumeApplication(IntPtr state) {
|
|
var unwrappedState = GCUtil.UnrootObject(state);
|
|
_suspendManager.Resume(unwrappedState);
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Maps a virtual path to a physical path.</para>
|
|
/// </devdoc>
|
|
public static string MapPath(string virtualPath) {
|
|
return MapPath(VirtualPath.Create(virtualPath));
|
|
}
|
|
|
|
internal static string MapPath(VirtualPath virtualPath) {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
String path = MapPathInternal(virtualPath);
|
|
|
|
if (path != null)
|
|
InternalSecurityPermissions.PathDiscovery(path).Demand();
|
|
|
|
return path;
|
|
}
|
|
|
|
internal static String MapPathInternal(string virtualPath) {
|
|
return MapPathInternal(VirtualPath.Create(virtualPath));
|
|
}
|
|
|
|
internal static String MapPathInternal(VirtualPath virtualPath) {
|
|
if (_theHostingEnvironment == null) {
|
|
return null;
|
|
}
|
|
|
|
return _theHostingEnvironment.MapPathActual(virtualPath, false);
|
|
}
|
|
|
|
internal static String MapPathInternal(string virtualPath, bool permitNull) {
|
|
return MapPathInternal(VirtualPath.Create(virtualPath), permitNull);
|
|
}
|
|
|
|
internal static String MapPathInternal(VirtualPath virtualPath, bool permitNull) {
|
|
if (_theHostingEnvironment == null) {
|
|
return null;
|
|
}
|
|
|
|
return _theHostingEnvironment.MapPathActual(virtualPath, permitNull);
|
|
}
|
|
|
|
internal static string MapPathInternal(string virtualPath, string baseVirtualDir, bool allowCrossAppMapping) {
|
|
return MapPathInternal(VirtualPath.Create(virtualPath),
|
|
VirtualPath.CreateNonRelative(baseVirtualDir), allowCrossAppMapping);
|
|
}
|
|
|
|
internal static string MapPathInternal(VirtualPath virtualPath, VirtualPath baseVirtualDir, bool allowCrossAppMapping) {
|
|
Debug.Assert(baseVirtualDir != null, "baseVirtualDir != null");
|
|
|
|
// Combine it with the base and reduce
|
|
virtualPath = baseVirtualDir.Combine(virtualPath);
|
|
|
|
if (!allowCrossAppMapping && !virtualPath.IsWithinAppRoot)
|
|
throw new ArgumentException(SR.GetString(SR.Cross_app_not_allowed, virtualPath));
|
|
|
|
return MapPathInternal(virtualPath);
|
|
}
|
|
|
|
internal static WebApplicationLevel GetPathLevel(String path) {
|
|
WebApplicationLevel pathLevel = WebApplicationLevel.AboveApplication;
|
|
|
|
if (_theHostingEnvironment != null && !String.IsNullOrEmpty(path)) {
|
|
String appPath = ApplicationVirtualPath;
|
|
|
|
if (appPath == "/") {
|
|
if (path == "/") {
|
|
pathLevel = WebApplicationLevel.AtApplication;
|
|
}
|
|
else if (path[0] == '/') {
|
|
pathLevel = WebApplicationLevel.BelowApplication;
|
|
}
|
|
}
|
|
else {
|
|
if (StringUtil.EqualsIgnoreCase(appPath, path)) {
|
|
pathLevel = WebApplicationLevel.AtApplication;
|
|
}
|
|
else if (path.Length > appPath.Length && path[appPath.Length] == '/' &&
|
|
StringUtil.StringStartsWithIgnoreCase(path, appPath)) {
|
|
|
|
pathLevel = WebApplicationLevel.BelowApplication;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pathLevel;
|
|
}
|
|
|
|
|
|
//
|
|
// Impersonation helpers
|
|
//
|
|
// user token for the app (hosting / unc)
|
|
internal static IntPtr ApplicationIdentityToken {
|
|
get {
|
|
if (_theHostingEnvironment == null) {
|
|
return IntPtr.Zero;
|
|
}
|
|
else {
|
|
if (_theHostingEnvironment._appIdentityTokenSet)
|
|
return _theHostingEnvironment._appIdentityToken;
|
|
else
|
|
return _theHostingEnvironment._configToken;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// check if application impersonation != process impersonation
|
|
internal static bool HasHostingIdentity {
|
|
get {
|
|
return (ApplicationIdentityToken != IntPtr.Zero);
|
|
}
|
|
}
|
|
|
|
// impersonate application identity
|
|
[SecurityPermission(SecurityAction.Demand, ControlPrincipal = true)]
|
|
public static IDisposable Impersonate() {
|
|
return new ApplicationImpersonationContext();
|
|
}
|
|
|
|
// impersonate the given user identity
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public static IDisposable Impersonate(IntPtr token) {
|
|
if (token == IntPtr.Zero) {
|
|
return new ProcessImpersonationContext();
|
|
}
|
|
else {
|
|
return new ImpersonationContext(token);
|
|
}
|
|
}
|
|
|
|
// impersonate as configured for a given path
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
public static IDisposable Impersonate(IntPtr userToken, String virtualPath) {
|
|
virtualPath = UrlPath.MakeVirtualPathAppAbsoluteReduceAndCheck(virtualPath);
|
|
|
|
if (_theHostingEnvironment == null) {
|
|
return Impersonate(userToken);
|
|
}
|
|
|
|
IdentitySection c = RuntimeConfig.GetConfig(virtualPath).Identity;
|
|
if (c.Impersonate) {
|
|
if (c.ImpersonateToken != IntPtr.Zero) {
|
|
return new ImpersonationContext(c.ImpersonateToken);
|
|
}
|
|
else {
|
|
return new ImpersonationContext(userToken);
|
|
}
|
|
}
|
|
else {
|
|
return new ApplicationImpersonationContext();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Culture helpers
|
|
//
|
|
|
|
public static IDisposable SetCultures() {
|
|
return SetCultures(RuntimeConfig.GetAppLKGConfig().Globalization);
|
|
}
|
|
|
|
public static IDisposable SetCultures(string virtualPath) {
|
|
virtualPath = UrlPath.MakeVirtualPathAppAbsoluteReduceAndCheck(virtualPath);
|
|
return SetCultures(RuntimeConfig.GetConfig(virtualPath).Globalization);
|
|
}
|
|
|
|
private static IDisposable SetCultures(GlobalizationSection gs) {
|
|
CultureContext c = new CultureContext();
|
|
|
|
if (gs != null) {
|
|
CultureInfo culture = null;
|
|
CultureInfo uiCulture = null;
|
|
|
|
if (gs.Culture != null && gs.Culture.Length > 0) {
|
|
try {
|
|
culture = HttpServerUtility.CreateReadOnlyCultureInfo(gs.Culture);
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
|
|
if (gs.UICulture != null && gs.UICulture.Length > 0) {
|
|
try {
|
|
uiCulture = HttpServerUtility.CreateReadOnlyCultureInfo(gs.UICulture);
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
|
|
c.SetCultures(culture, uiCulture);
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
class CultureContext : IDisposable {
|
|
CultureInfo _savedCulture;
|
|
CultureInfo _savedUICulture;
|
|
|
|
internal CultureContext() {
|
|
}
|
|
|
|
void IDisposable.Dispose() {
|
|
RestoreCultures();
|
|
}
|
|
|
|
internal void SetCultures(CultureInfo culture, CultureInfo uiCulture) {
|
|
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
|
|
CultureInfo currentUICulture = Thread.CurrentThread.CurrentUICulture;
|
|
|
|
if (culture != null && culture != currentCulture) {
|
|
Thread.CurrentThread.CurrentCulture = culture;
|
|
_savedCulture = currentCulture;
|
|
}
|
|
|
|
if (uiCulture != null && uiCulture != currentCulture) {
|
|
Thread.CurrentThread.CurrentUICulture = uiCulture;
|
|
_savedUICulture = currentUICulture;
|
|
}
|
|
}
|
|
|
|
internal void RestoreCultures() {
|
|
if (_savedCulture != null && _savedCulture != Thread.CurrentThread.CurrentCulture) {
|
|
Thread.CurrentThread.CurrentCulture = _savedCulture;
|
|
_savedCulture = null;
|
|
}
|
|
|
|
if (_savedUICulture != null && _savedUICulture != Thread.CurrentThread.CurrentUICulture) {
|
|
Thread.CurrentThread.CurrentUICulture = _savedUICulture;
|
|
_savedUICulture = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// VirtualPathProvider related code
|
|
//
|
|
|
|
private VirtualPathProvider _virtualPathProvider;
|
|
private VirtualPathProvider _mapPathBasedVirtualPathProvider;
|
|
|
|
|
|
public static VirtualPathProvider VirtualPathProvider {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return null;
|
|
|
|
// Set/Get the VPP on the call context so as not to affect other concurrent requests (Dev10 852255)
|
|
var tempVPP = CallContext.GetData(TemporaryVirtualPathProviderKey);
|
|
if (tempVPP != null) {
|
|
return tempVPP as VirtualPathProvider;
|
|
}
|
|
|
|
return _theHostingEnvironment._virtualPathProvider;
|
|
}
|
|
}
|
|
|
|
internal static bool UsingMapPathBasedVirtualPathProvider {
|
|
get {
|
|
if (_theHostingEnvironment == null)
|
|
return true;
|
|
|
|
return (_theHostingEnvironment._virtualPathProvider ==
|
|
_theHostingEnvironment._mapPathBasedVirtualPathProvider);
|
|
}
|
|
}
|
|
|
|
// [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.High)]
|
|
// Removed the above LinkDemand for AspNetHostingPermissionLevel.High. If we decide to add VPP
|
|
// support for config in the future, we should have a separate API with a demand for registering
|
|
// VPPs supporting configuration.
|
|
public static void RegisterVirtualPathProvider(VirtualPathProvider virtualPathProvider) {
|
|
|
|
if (_theHostingEnvironment == null)
|
|
throw new InvalidOperationException();
|
|
|
|
// Ignore the VirtualPathProvider on precompiled sites (VSWhidbey 368169,404844)
|
|
if (BuildManager.IsPrecompiledApp)
|
|
return;
|
|
|
|
RegisterVirtualPathProviderInternal(virtualPathProvider);
|
|
}
|
|
|
|
internal static void RegisterVirtualPathProviderInternal(VirtualPathProvider virtualPathProvider) {
|
|
VirtualPathProvider previous = _theHostingEnvironment._virtualPathProvider;
|
|
_theHostingEnvironment._virtualPathProvider = virtualPathProvider;
|
|
|
|
// Give it the previous provider so it can delegate if needed
|
|
virtualPathProvider.Initialize(previous);
|
|
}
|
|
|
|
// Helper class used to keep track of state when using
|
|
// AddVirtualPathToFileMapping & ClearVirtualPathToFileMapping
|
|
internal class VirtualPathToFileMappingState {
|
|
internal VirtualPath VirtualPath;
|
|
internal VirtualPathProvider VirtualPathProvider;
|
|
}
|
|
|
|
internal static IProcessHostSupportFunctions SupportFunctions {
|
|
get {
|
|
return _functions;
|
|
}
|
|
set {
|
|
_functions = value;
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased",
|
|
Justification="matches casing of config attribute")]
|
|
public static int MaxConcurrentRequestsPerCPU {
|
|
get {
|
|
if (!HttpRuntime.UseIntegratedPipeline) {
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
|
|
}
|
|
return UnsafeIISMethods.MgdGetMaxConcurrentRequestsPerCPU();
|
|
}
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
set {
|
|
if (!HttpRuntime.UseIntegratedPipeline) {
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
|
|
}
|
|
int hr = UnsafeIISMethods.MgdSetMaxConcurrentRequestsPerCPU(value);
|
|
switch (hr) {
|
|
case HResults.S_FALSE:
|
|
// Because "maxConcurrentRequestsPerCPU" is currently zero, we cannot set the value, since that would
|
|
// enable the feature, which can only be done via configuration.
|
|
throw new InvalidOperationException(SR.GetString(SR.Queue_limit_is_zero, "maxConcurrentRequestsPerCPU"));
|
|
case HResults.E_INVALIDARG:
|
|
// The value must be greater than zero. A value of zero would disable the feature, but this can only be done via configuration.
|
|
throw new ArgumentException(SR.GetString(SR.Invalid_queue_limit));
|
|
}
|
|
}
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased",
|
|
Justification="matches casing of config attribute")]
|
|
public static int MaxConcurrentThreadsPerCPU {
|
|
get {
|
|
if (!HttpRuntime.UseIntegratedPipeline) {
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
|
|
}
|
|
return UnsafeIISMethods.MgdGetMaxConcurrentThreadsPerCPU();
|
|
}
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted = true)]
|
|
set {
|
|
if (!HttpRuntime.UseIntegratedPipeline) {
|
|
throw new PlatformNotSupportedException(SR.GetString(SR.Requires_Iis_Integrated_Mode));
|
|
}
|
|
int hr = UnsafeIISMethods.MgdSetMaxConcurrentThreadsPerCPU(value);
|
|
switch (hr) {
|
|
case HResults.S_FALSE:
|
|
// Because "maxConcurrentThreadsPerCPU" is currently zero, we cannot set the value, since that would
|
|
// enable the feature, which can only be done via configuration.
|
|
throw new InvalidOperationException(SR.GetString(SR.Queue_limit_is_zero, "maxConcurrentThreadsPerCPU"));
|
|
case HResults.E_INVALIDARG:
|
|
// The value must be greater than zero. A value of zero would disable the feature, but this can only be done via configuration.
|
|
throw new ArgumentException(SR.GetString(SR.Invalid_queue_limit));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the ASP.NET hosted domain.
|
|
/// </summary>
|
|
internal AppDomain HostedAppDomain {
|
|
get {
|
|
return AppDomain.CurrentDomain;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|