File: Hosting\CustomLoaderHelper.cs
Project: ndp\fx\src\xsp\system\ApplicationServices\System.Web.ApplicationServices.csproj (System.Web.ApplicationServices)
//------------------------------------------------------------------------------
// <copyright file="CustomLoaderHelper.cs" company="Microsoft">
//    Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.Hosting {
    using System;
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Runtime.Remoting;
    using System.Runtime.Versioning;
 
    // Used to locate a custom loader implementation within a bin-deployed assembly.
 
    internal sealed class CustomLoaderHelper : MarshalByRefObject {
 
        // the first framework version where the custom loader feature was implemented
        private static readonly string _customLoaderTargetFrameworkName = new FrameworkName(".NETFramework", new Version(4, 5, 1)).ToString();
 
        private static readonly string _customLoaderAssemblyName = typeof(CustomLoaderHelper).Assembly.FullName;
        private static readonly string _customLoaderTypeName = typeof(CustomLoaderHelper).FullName;
        private static readonly Guid IID_ICustomLoader = new Guid("50A3CE65-2F9F-44E9-9094-32C6C928F966");
 
        // Instances of this type should only ever be created via reflection (see call to CreateObjectAndUnwrap
        // in GetCustomLoader).
        private CustomLoaderHelper() { }
 
        internal static IObjectHandle GetCustomLoader(ICustomLoaderHelperFunctions helperFunctions, string appConfigMetabasePath, string configFilePath, string customLoaderPhysicalPath, out AppDomain newlyCreatedAppDomain) {
            // Step 1: Does the host allow custom loaders?
 
            bool? customLoaderIsEnabled = helperFunctions.CustomLoaderIsEnabled;
            if (customLoaderIsEnabled.HasValue) {
                if ((bool)customLoaderIsEnabled) {
                    // The custom loader is enabled; move on to the next step.
                }
                else {
                    // The custom loader is disabled, fail.
                    throw new NotSupportedException(ApplicationServicesStrings.CustomLoader_ForbiddenByHost);
                }
            }
            else {
                // The host hasn't set a policy, so we'll fall back to our default logic of checking the application's trust level.
                if (!IsFullyTrusted(helperFunctions, appConfigMetabasePath)) {
                    throw new NotSupportedException(ApplicationServicesStrings.CustomLoader_NotInFullTrust);
                }
            }
 
            // Step 2: Create the new AD
 
            string binFolderPhysicalPath = helperFunctions.MapPath("/bin/");
 
            AppDomainSetup setup = new AppDomainSetup() {
                PrivateBinPathProbe = "*",  // disable loading from app base
                PrivateBinPath = binFolderPhysicalPath,
                ApplicationBase = helperFunctions.AppPhysicalPath,
                TargetFrameworkName = _customLoaderTargetFrameworkName
            };
 
            if (configFilePath != null) {
                setup.ConfigurationFile = configFilePath;
            }
 
            AppDomain newAppDomainForCustomLoader = AppDomain.CreateDomain("aspnet-custom-loader-" + Guid.NewGuid(), null, setup);
            try {
                // Step 3: Instantiate helper in new AD so that we can get a reference to the loader
                CustomLoaderHelper helper = (CustomLoaderHelper)newAppDomainForCustomLoader.CreateInstanceAndUnwrap(_customLoaderAssemblyName, _customLoaderTypeName,
                    ignoreCase: false,
                    bindingAttr: BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.Instance,
                    binder: null,
                    args: null,
                    culture: null,
                    activationAttributes: null);
                ObjectHandle ohCustomLoader = helper.GetCustomLoaderImpl(customLoaderPhysicalPath);
 
                // If we got this far, success!
                newlyCreatedAppDomain = newAppDomainForCustomLoader;
                return ohCustomLoader;
            }
            catch {
                // If something went wrong, kill the new AD.
                AppDomain.Unload(newAppDomainForCustomLoader);
                throw;
            }
        }
 
        private ObjectHandle GetCustomLoaderImpl(string customLoaderPhysicalPath) {
            // Step 4: Find the implementation in the custom loader assembly
 
            // Since we have set the private bin path, we can use this call to Assembly.Load
            // to avoid the load-from context, which has weird behaviors.
            AssemblyName customLoaderAssemblyName = AssemblyName.GetAssemblyName(customLoaderPhysicalPath);
            Assembly customLoaderAssembly = Assembly.Load(customLoaderAssemblyName);
            CustomLoaderAttribute customLoaderAttribute = customLoaderAssembly.GetCustomAttribute<CustomLoaderAttribute>();
 
            if (customLoaderAttribute == null) {
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ApplicationServicesStrings.CustomLoader_NoAttributeFound, customLoaderAssemblyName));
            }
 
            // Step 5: Instantiate the custom loader and return a reference back to native code
 
            object customLoader = Activator.CreateInstance(customLoaderAttribute.CustomLoaderType);
 
            // This check isn't strictly necessary since the unmanaged layer will handle QueryInterface failures
            // appropriately, but we have an opportunity to provide a better error message at this layer.
            if (!ObjectImplementsComInterface(customLoader, IID_ICustomLoader)) {
                throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ApplicationServicesStrings.CustomLoader_MustImplementICustomLoader, customLoader.GetType()));
            }
 
            return new ObjectHandle(customLoader);
        }
 
        private static bool IsFullyTrusted(ICustomLoaderHelperFunctions helperFunctions, string appConfigMetabasePath) {
            // The managed configuration system hasn't yet been instantiated but the IIS native config system understands
            // ASP.NET configuration and honors hierarchy and section locking.
 
            try {
                // Must exactly match <trust level="Full" />, as this is what ApplicationManager expects.
                string trustLevel = helperFunctions.GetTrustLevel(appConfigMetabasePath);
                return String.Equals("Full", trustLevel, StringComparison.Ordinal);
            }
            catch {
                // If any of the sections are locked or there is a config error, bail.
                return false;
            }
        }
 
        private static bool ObjectImplementsComInterface(object o, Guid iid) {
            IntPtr pUnknown = IntPtr.Zero;
            IntPtr pInterface = IntPtr.Zero;
 
            try {
                pUnknown = Marshal.GetIUnknownForObject(o); // AddRef
                int hr = Marshal.QueryInterface(pUnknown, ref iid, out pInterface); // AddRef
                return (hr == 0 && pInterface != IntPtr.Zero);
            }
            finally {
                if (pUnknown != IntPtr.Zero) {
                    Marshal.Release(pUnknown);
                }
                if (pInterface != IntPtr.Zero) {
                    Marshal.Release(pInterface);
                }
            }
        }
    }
}