Files
linux-packaging-mono/external/aspnetwebstack/src/System.Web.WebPages/BuildManagerWrapper.cs
Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

205 lines
8.4 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Caching;
using System.Web.Compilation;
using System.Web.Hosting;
using System.Web.Util;
using System.Xml.Linq;
using Microsoft.Internal.Web.Utils;
namespace System.Web.WebPages
{
/// <summary>
/// Wraps the caching and instantiation of paths of the BuildManager.
/// In case of precompiled non-updateable sites, the only way to verify if a file exists is to call BuildManager.GetObjectFactory. However this method is less performant than
/// VirtualPathProvider.FileExists which is used for all other scenarios. In this class, we optimize for the first scenario by storing the results of GetObjectFactory for a
/// long duration.
/// </summary>
internal sealed class BuildManagerWrapper : IVirtualPathFactory
{
internal static readonly Guid KeyGuid = Guid.NewGuid();
private static readonly TimeSpan _objectFactoryCacheDuration = TimeSpan.FromMinutes(1);
private readonly IVirtualPathUtility _virtualPathUtility;
private readonly VirtualPathProvider _vpp;
private readonly bool _isPrecompiled;
private readonly FileExistenceCache _vppCache;
private IEnumerable<string> _supportedExtensions;
public BuildManagerWrapper()
: this(HostingEnvironment.VirtualPathProvider, new VirtualPathUtilityWrapper())
{
}
public BuildManagerWrapper(VirtualPathProvider vpp, IVirtualPathUtility virtualPathUtility)
{
_vpp = vpp;
_virtualPathUtility = virtualPathUtility;
_isPrecompiled = IsNonUpdatablePrecompiledApp();
if (!_isPrecompiled)
{
_vppCache = new FileExistenceCache(vpp);
}
}
public IEnumerable<string> SupportedExtensions
{
get { return _supportedExtensions ?? WebPageHttpHandler.GetRegisteredExtensions(); }
set { _supportedExtensions = value; }
}
/// <summary>
/// Determines if a page exists in the website.
/// This method switches between a long duration cache or a short duration FileExistenceCache depending on whether the site is precompiled.
/// This is an optimization because BuildManager.GetObjectFactory is comparably slower than performing VirtualPathFactory.Exists
/// </summary>
public bool Exists(string virtualPath)
{
if (_isPrecompiled)
{
return ExistsInPrecompiledSite(virtualPath);
}
return ExistsInVpp(virtualPath);
}
/// <summary>
/// An app's is precompiled for our purposes if
/// (a) it has a PreCompiledApp.config file in the site root,
/// (b) The PreCompiledApp.config says that the app is not Updatable.
/// </summary>
/// <remarks>
/// This code is based on System.Web.DynamicData.Misc.IsNonUpdatablePrecompiledAppNoCache (DynamicData)
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We want to replicate the behavior of BuildManager which catches all exceptions.")]
internal bool IsNonUpdatablePrecompiledApp()
{
if (_vpp == null)
{
return false;
}
var virtualPath = _virtualPathUtility.ToAbsolute("~/PrecompiledApp.config");
if (!_vpp.FileExists(virtualPath))
{
return false;
}
XDocument document;
using (var stream = _vpp.GetFile(virtualPath).Open())
{
try
{
document = XDocument.Load(_vpp.GetFile(virtualPath).Open());
}
catch
{
// If we are unable to load the file, ignore it. The BuildManager behaves identically.
return false;
}
}
if (document.Root == null || !document.Root.Name.LocalName.Equals("precompiledApp", StringComparison.OrdinalIgnoreCase))
{
return false;
}
var updatableAttribute = document.Root.Attribute("updatable");
if (updatableAttribute != null)
{
bool result;
return Boolean.TryParse(updatableAttribute.Value, out result) && (result == false);
}
return false;
}
private bool ExistsInPrecompiledSite(string virtualPath)
{
var key = GetKeyFromVirtualPath(virtualPath);
// We assume that the key is unique enough to avoid collisions.
var buildManagerResult = (BuildManagerResult)HttpRuntime.Cache.Get(key);
if (buildManagerResult == null)
{
// For precompiled apps, we cache the ObjectFactory and use it in the CreateInstance method.
var objectFactory = GetObjectFactory(virtualPath);
buildManagerResult = new BuildManagerResult { ObjectFactory = objectFactory, Exists = objectFactory != null };
// Cache the result with a sliding expiration for a long duration.
HttpRuntime.Cache.Add(key, buildManagerResult, null, Cache.NoAbsoluteExpiration, _objectFactoryCacheDuration, CacheItemPriority.Low, null);
}
return buildManagerResult.Exists;
}
/// <summary>
/// Determines if a site exists in the VirtualPathProvider.
/// Results of hits are cached for a very short amount of time in the FileExistenceCache.
/// </summary>
private bool ExistsInVpp(string virtualPath)
{
Debug.Assert(_vppCache != null);
return _vppCache.FileExists(virtualPath);
}
/// <summary>
/// Determines if an ObjectFactory exists for the virtualPath.
/// The BuildManager complains if we pass in extensions that aren't registered for compilation. So we ensure that the virtual path is not
/// extensionless and that it is one of the extension
/// </summary>
private IWebObjectFactory GetObjectFactory(string virtualPath)
{
if (IsPathExtensionSupported(virtualPath))
{
return BuildManager.GetObjectFactory(virtualPath, throwIfNotFound: false);
}
return null;
}
public object CreateInstance(string virtualPath)
{
return CreateInstanceOfType<object>(virtualPath);
}
public T CreateInstanceOfType<T>(string virtualPath) where T : class
{
if (_isPrecompiled)
{
var buildManagerResult = (BuildManagerResult)HttpRuntime.Cache.Get(GetKeyFromVirtualPath(virtualPath));
// The cache could have evicted our results. In this case, we'll simply fall through to CreateInstanceFromVirtualPath
if (buildManagerResult != null)
{
Debug.Assert(buildManagerResult.Exists && buildManagerResult.ObjectFactory != null, "This method must only be called if the file exists.");
return buildManagerResult.ObjectFactory.CreateInstance() as T;
}
}
return (T)BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(T));
}
/// <summary>
/// Determines if the extension is one of the extensions registered with WebPageHttpHandler.
/// </summary>
public bool IsPathExtensionSupported(string virtualPath)
{
string extension = PathUtil.GetExtension(virtualPath);
return !String.IsNullOrEmpty(extension)
&& SupportedExtensions.Contains(extension.Substring(1), StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Creates a reasonably unique key for a given virtual path by concatenating it with a Guid.
/// </summary>
private static string GetKeyFromVirtualPath(string virtualPath)
{
return KeyGuid.ToString() + "_" + virtualPath;
}
private class BuildManagerResult
{
public bool Exists { get; set; }
public IWebObjectFactory ObjectFactory { get; set; }
}
}
}