File: Compilation\ObjectFactoryCodeDomTreeGenerator.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="ObjectFactoryCodeDomTreeGenerator.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
// This code is used to optimize the instantiation of generated types.
 
 
namespace System.Web.Compilation {
 
using System;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.Globalization;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Web.Util;
using Util = System.Web.UI.Util;
using System.Linq;
using System.Runtime.InteropServices;
 
internal delegate object InstantiateObject();
 
internal class ObjectFactoryCodeDomTreeGenerator {
 
    private CodeCompileUnit _codeCompileUnit;
    private CodeTypeDeclaration _factoryClass;
 
    private const string factoryClassNameBase = "FastObjectFactory_";
    private const string factoryFullClassNameBase = BaseCodeDomTreeGenerator.internalAspNamespace +
        "." + factoryClassNameBase;
    private static readonly string optionalAttributeTypeName = typeof(OptionalAttribute).FullName;
 
    internal ObjectFactoryCodeDomTreeGenerator(string outputAssemblyName) {
 
        _codeCompileUnit = new CodeCompileUnit();
 
        CodeNamespace sourceDataNamespace = new CodeNamespace(
            BaseCodeDomTreeGenerator.internalAspNamespace);
        _codeCompileUnit.Namespaces.Add(sourceDataNamespace);
 
        // Make the class name vary based on the assembly (VSWhidbey 363214)
        string factoryClassName = factoryClassNameBase +
            Util.MakeValidTypeNameFromString(outputAssemblyName).ToLower(CultureInfo.InvariantCulture);
 
        // Create a single class, in which a method will be added for each
        // type that needs to be fast created in this assembly
        _factoryClass = new CodeTypeDeclaration(factoryClassName);
 
        // Make the class internal (VSWhidbey 363214)
        _factoryClass.TypeAttributes &= ~TypeAttributes.Public;
 
        // We generate a dummy line pragma, just so it will end with a '#line hidden'
        // and prevent the following generated code from ever being treated as user
        // code.  We need to use this hack because CodeDOM doesn't allow simply generating
        // a '#line hidden'. (VSWhidbey 199384)
        CodeSnippetTypeMember dummySnippet = new CodeSnippetTypeMember(String.Empty);
#if !PLATFORM_UNIX /// Unix file system
        // CORIOLISTODO: Unix file system
        dummySnippet.LinePragma = new CodeLinePragma(@"c:\\dummy.txt", 1);
#else // !PLATFORM_UNIX
        dummySnippet.LinePragma = new CodeLinePragma(@"/dummy.txt", 1);
#endif // !PLATFORM_UNIX
        _factoryClass.Members.Add(dummySnippet);
 
        // Add a private default ctor to make the class non-instantiatable (VSWhidbey 340829)
        CodeConstructor ctor = new CodeConstructor();
        ctor.Attributes |= MemberAttributes.Private;
        _factoryClass.Members.Add(ctor);
 
        sourceDataNamespace.Types.Add(_factoryClass);
    }
 
    internal void AddFactoryMethod(string typeToCreate, CodeCompileUnit ccu = null) {
 
        // Generate a simple factory method for this type.  e.g.
        // static object Create_Acme_Foo() {
        //     return new ASP.foo_aspx();
        // }
 
        CodeMemberMethod method = new CodeMemberMethod();
        method.Name = GetCreateMethodNameForType(typeToCreate);
        method.ReturnType = new CodeTypeReference(typeof(object));
        method.Attributes = MemberAttributes.Static;
 
        AddCreateTypeInstanceStatement(typeToCreate, ccu, method.Statements);
 
        _factoryClass.Members.Add(method);
    }
 
    private static void AddCreateTypeInstanceStatement(string typeToCreate, CodeCompileUnit ccu, CodeStatementCollection statements) {
        if (BinaryCompatibility.Current.TargetsAtLeastFramework472 && ccu != null) {
            /* Generate code like below
 
            IServiceProvider __activator = HttpRuntime.WebObjectActivator;
 
            //-- Generate code like this if default c-tor exists
            if (activator != null) {
                return activator.GetService(ctrlType);
            }
 
            // if default c-tor exists
            else {
                _ctrl = new ....
            }
            // if no default c-tor
            else {
                throw new InvalidOperationException(SR.GetString(SR.Could_not_create_type_instance, ctrlType))
            }
 
            //-- if default c-tor doesn't exist, assume dev wants to use DI.
            // if there is no default c-tor, you will get compilation error on framework 4.7.1 and below.
            */
            var webObjectActivatorExpr = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("System.Web.HttpRuntime"), "WebObjectActivator");
            statements.Add(new CodeVariableDeclarationStatement(typeof(IServiceProvider), "__activator"));
            var activatorRefExpr = new CodeVariableReferenceExpression("__activator");
            statements.Add(new CodeAssignStatement(activatorRefExpr, webObjectActivatorExpr));
            var getServiceExpr = new CodeMethodInvokeExpression(activatorRefExpr, "GetService", new CodeTypeOfExpression(typeToCreate));
 
            var createObjectStatement = new CodeConditionStatement() {
                Condition = new CodeBinaryOperatorExpression(activatorRefExpr,
                CodeBinaryOperatorType.IdentityInequality,
                new CodePrimitiveExpression(null))
            };
            createObjectStatement.TrueStatements.Add(new CodeMethodReturnStatement(getServiceExpr));
 
            // If default c-tor exists
            if (DoesGeneratedCodeHaveDefaultCtor(typeToCreate, ccu)) {
                var newObjectExpr = new CodeObjectCreateExpression(typeToCreate);
                createObjectStatement.FalseStatements.Add(new CodeMethodReturnStatement(newObjectExpr));
            }
            else {
                var throwExceptionStatement = new CodeThrowExceptionStatement(new CodeObjectCreateExpression(
                    new CodeTypeReference(typeof(System.InvalidOperationException)),
                    new CodeExpression[] { new CodePrimitiveExpression(SR.GetString(SR.Could_not_create_type_instance, typeToCreate)) }));
                createObjectStatement.FalseStatements.Add(throwExceptionStatement);
            }
            statements.Add(createObjectStatement);
        }
        else {
            // Generate new typeToCreate()
            var newObjectExpression = new CodeObjectCreateExpression(typeToCreate);
            statements.Add(new CodeMethodReturnStatement(newObjectExpression));
        }
    }
        
    private static bool DoesGeneratedCodeHaveDefaultCtor(string typeToCreate, CodeCompileUnit ccu) {
        for(var i = 0; i < ccu.Namespaces.Count; i++) {
            var ns = ccu.Namespaces[i];
            for(var n = 0; n < ns.Types.Count; n++) {
                var type = ns.Types[n];
                if(StringUtil.Equals(typeToCreate, ns.Name + "." + type.Name)) {
                    foreach (var ctm in type.Members) {
                        var ctor = ctm as CodeConstructor;
                        if (ctor != null && (ctor.Attributes & MemberAttributes.Public) == MemberAttributes.Public
                                && DoesAllConstructorParametersHaveDefaultValue(ctor)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }
 
    private static bool DoesAllConstructorParametersHaveDefaultValue(CodeConstructor ctor) {
        foreach(CodeParameterDeclarationExpression paramExpr in ctor.Parameters) {
            var hasOptionalAttribute = false;
            foreach (CodeAttributeDeclaration attr in paramExpr.CustomAttributes) {
                if(attr.AttributeType?.BaseType == optionalAttributeTypeName) {
                    hasOptionalAttribute = true;
                    break;
                }
            }
            if(!hasOptionalAttribute) {
                return false;
            }
        }
        return true;
    }
 
    private static string GetCreateMethodNameForType(string typeToCreate) {
        return "Create_" + Util.MakeValidTypeNameFromString(typeToCreate);
    }
 
    internal CodeCompileUnit CodeCompileUnit {
        get { return _codeCompileUnit; }
    }
 
    // Get the factory delegate for this type
    // Could be called with user code on the stack, so need to assert here 
    // e.g. This can happen during a Server.Transfer, or a LoadControl.
    [ReflectionPermission(SecurityAction.Assert, Flags = ReflectionPermissionFlag.MemberAccess)]
    internal static InstantiateObject GetFastObjectCreationDelegate(Type t) {
        // Look for the factory class in the same assembly
        Assembly a = t.Assembly;
 
        string shortAssemblyName = Util.GetAssemblyShortName(t.Assembly);
        shortAssemblyName = shortAssemblyName.ToLower(CultureInfo.InvariantCulture);
        Type factoryType = a.GetType(factoryFullClassNameBase + Util.MakeValidTypeNameFromString(shortAssemblyName));
        if (factoryType == null)
            return null;
 
        string methodName = GetCreateMethodNameForType(t.FullName);
 
        try {
            return (InstantiateObject) Delegate.CreateDelegate(
                typeof(InstantiateObject), factoryType, methodName);
        }
        catch {
            return null;
        }
    }
}
 
}