//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Compilation { using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Web.Caching; using System.Web.Configuration; using System.Web.UI; using System.Web.Util; using Debug = System.Web.Util.Debug; internal abstract class BaseTemplateCodeDomTreeGenerator : BaseCodeDomTreeGenerator { protected static readonly string buildMethodPrefix = "__BuildControl"; protected static readonly string extractTemplateValuesMethodPrefix = "__ExtractValues"; protected static readonly string templateSourceDirectoryName = "AppRelativeTemplateSourceDirectory"; protected static readonly string applyStyleSheetMethodName = "ApplyStyleSheetSkin"; protected static readonly string pagePropertyName = "Page"; internal const string skinIDPropertyName = "SkinID"; private const string _localVariableRef = "__ctrl"; private TemplateParser _parser; private int _controlCount; // Minimum literal string length for it to be placed in the resource private const int minLongLiteralStringLength = 256; private const string renderMethodParameterName = "__w"; // Used in designer mode internal const string tempObjectVariable = "__o"; /* * Set some fields that are needed for code generation */ internal BaseTemplateCodeDomTreeGenerator(TemplateParser parser) : base(parser) { _parser = parser; } private TemplateParser Parser { get { return _parser; } } private CodeStatement GetOutputWriteStatement(CodeExpression expr, bool encode) { // Call HttpUtility.HtmlEncode on the expression if needed if (encode) { expr = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression( new CodeTypeReferenceExpression(typeof(HttpUtility)), "HtmlEncode"), expr); } CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(); CodeExpressionStatement call = new CodeExpressionStatement(methodInvoke); methodInvoke.Method.TargetObject = new CodeArgumentReferenceExpression(renderMethodParameterName); methodInvoke.Method.MethodName = "Write"; methodInvoke.Parameters.Add(expr); return call; } /// /// Append an output.Write() statement to a Render method /// private void AddOutputWriteStatement(CodeStatementCollection methodStatements, CodeExpression expr, CodeLinePragma linePragma) { CodeStatement outputWriteStmt = GetOutputWriteStatement(expr, false /*encode*/); if (linePragma != null) outputWriteStmt.LinePragma = linePragma; methodStatements.Add(outputWriteStmt); } private void AddOutputWriteStringStatement(CodeStatementCollection methodStatements, String s) { if (!UseResourceLiteralString(s)) { AddOutputWriteStatement(methodStatements, new CodePrimitiveExpression(s), null); return; } // Add the string to the resource builder, and get back its offset/size int offset, size; bool fAsciiOnly; _stringResourceBuilder.AddString(s, out offset, out size, out fAsciiOnly); // e.g. WriteUTF8ResourceString(output, 314, 20); CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(); CodeExpressionStatement call = new CodeExpressionStatement(methodInvoke); methodInvoke.Method.TargetObject = new CodeThisReferenceExpression(); methodInvoke.Method.MethodName = "WriteUTF8ResourceString"; methodInvoke.Parameters.Add(new CodeArgumentReferenceExpression(renderMethodParameterName)); methodInvoke.Parameters.Add(new CodePrimitiveExpression(offset)); methodInvoke.Parameters.Add(new CodePrimitiveExpression(size)); methodInvoke.Parameters.Add(new CodePrimitiveExpression(fAsciiOnly)); methodStatements.Add(call); } private static void BuildAddParsedSubObjectStatement( CodeStatementCollection statements, CodeExpression ctrlToAdd, CodeLinePragma linePragma, CodeExpression ctrlRefExpr, ref bool gotParserVariable) { if (!gotParserVariable) { // e.g. IParserAccessor __parser = ((IParserAccessor)__ctrl); CodeVariableDeclarationStatement parserDeclaration = new CodeVariableDeclarationStatement(); parserDeclaration.Name = "__parser"; parserDeclaration.Type = new CodeTypeReference(typeof(IParserAccessor)); parserDeclaration.InitExpression = new CodeCastExpression( typeof(IParserAccessor), ctrlRefExpr); statements.Add(parserDeclaration); gotParserVariable = true; } // e.g. __parser.AddParsedSubObject({{controlName}}); CodeMethodInvokeExpression methCallExpression = new CodeMethodInvokeExpression( new CodeVariableReferenceExpression("__parser"), "AddParsedSubObject"); methCallExpression.Parameters.Add(ctrlToAdd); CodeExpressionStatement methCallStatement = new CodeExpressionStatement(methCallExpression); methCallStatement.LinePragma = linePragma; statements.Add(methCallStatement); } internal virtual CodeExpression BuildPagePropertyReferenceExpression() { return new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), pagePropertyName); } /* * Build the data tree for a control's build method */ protected CodeMemberMethod BuildBuildMethod(ControlBuilder builder, bool fTemplate, bool fInTemplate, bool topLevelControlInTemplate, PropertyEntry pse, bool fControlSkin) { Debug.Assert(builder.ServiceProvider == null); ServiceContainer container = new ServiceContainer(); container.AddService(typeof(IFilterResolutionService), HttpCapabilitiesBase.EmptyHttpCapabilitiesBase); try { builder.SetServiceProvider(container); builder.EnsureEntriesSorted(); } finally { builder.SetServiceProvider(null); } string methodName = GetMethodNameForBuilder(buildMethodPrefix, builder); Type ctrlType = GetCtrlTypeForBuilder(builder, fTemplate); bool fStandardControl = false; bool fControlFieldDeclared = false; CodeMemberMethod method = new CodeMemberMethod(); AddDebuggerNonUserCodeAttribute(method); method.Name = methodName; method.Attributes = MemberAttributes.Private | MemberAttributes.Final; _sourceDataClass.Members.Add(method); // If it's for a template or a r/o complex prop, pass a parameter of the control's type ComplexPropertyEntry cpse = pse as ComplexPropertyEntry; if (fTemplate || (cpse != null && cpse.ReadOnly)) { if (builder is RootBuilder) method.Parameters.Add(new CodeParameterDeclarationExpression(_sourceDataClass.Name, "__ctrl")); else method.Parameters.Add(new CodeParameterDeclarationExpression(ctrlType, "__ctrl")); } else { // If it's a standard control, return it from the method if (typeof(Control).IsAssignableFrom(builder.ControlType)) { fStandardControl = true; } Debug.Assert(builder.ControlType != null); if (builder.ControlType != null) { if (fControlSkin) { // ReturnType needs to be of type Control in a skin file to match // the controlskin delegate. if (fStandardControl) { method.ReturnType = new CodeTypeReference(typeof(Control)); } } else { PartialCachingAttribute cacheAttrib = (PartialCachingAttribute) TypeDescriptor.GetAttributes(builder.ControlType)[typeof(PartialCachingAttribute)]; if (cacheAttrib != null) { method.ReturnType = new CodeTypeReference(typeof(Control)); } else { // Otherwise the return type is always the actual component type. method.ReturnType = CodeDomUtility.BuildGlobalCodeTypeReference(builder.ControlType); } } } // A control field declaration is required, this field will be returned // in the method. fControlFieldDeclared = true; } // Add a control parameter if it's a ControlSkin if (fControlSkin) { method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Control).FullName, "ctrl")); } BuildBuildMethodInternal(builder, builder.ControlType, fInTemplate, topLevelControlInTemplate, pse, method.Statements, fStandardControl, fControlFieldDeclared, null, fControlSkin); return method; } /* Helper method to generate the content of the control's build method * Type _ctrl; * _ctrl = new Type(); * ... * return _ctrl; */ [SuppressMessage("Microsoft.Usage", "CA2303:FlagTypeGetHashCode", Justification = "This is used for caching - it's ok to supress.")] private void BuildBuildMethodInternal(ControlBuilder builder, Type ctrlType, bool fInTemplate, bool topLevelControlInTemplate, PropertyEntry pse, CodeStatementCollection statements, bool fStandardControl, bool fControlFieldDeclared, string deviceFilter, bool fControlSkin) { // Same linePragma in the entire build method CodeLinePragma linePragma = CreateCodeLinePragma(builder); CodeObjectCreateExpression newExpr; CodeExpressionStatement methCallStatement; CodeMethodInvokeExpression methCallExpression; CodeExpression ctrlRefExpr; if (fControlSkin) { CodeCastExpression cast = new CodeCastExpression(builder.ControlType.FullName, new CodeArgumentReferenceExpression("ctrl")); statements.Add(new CodeVariableDeclarationStatement(builder.ControlType.FullName, "__ctrl", cast)); ctrlRefExpr = new CodeVariableReferenceExpression("__ctrl"); } // Not a control. ie. it's for a template or a r/o complex prop, else if (!fControlFieldDeclared) { ctrlRefExpr = new CodeArgumentReferenceExpression("__ctrl"); } else { CodeTypeReference ctrlTypeRef = CodeDomUtility.BuildGlobalCodeTypeReference(ctrlType); newExpr = new CodeObjectCreateExpression(ctrlTypeRef); // If it has a ConstructorNeedsTagAttribute, it needs a tag name ConstructorNeedsTagAttribute cnta = (ConstructorNeedsTagAttribute) TypeDescriptor.GetAttributes(ctrlType)[typeof(ConstructorNeedsTagAttribute)]; if (cnta != null && cnta.NeedsTag) { newExpr.Parameters.Add(new CodePrimitiveExpression(builder.TagName)); } // If it's for a DataBoundLiteralControl, pass it the number of // entries in the constructor DataBoundLiteralControlBuilder dataBoundBuilder = builder as DataBoundLiteralControlBuilder; if (dataBoundBuilder != null) { newExpr.Parameters.Add(new CodePrimitiveExpression( dataBoundBuilder.GetStaticLiteralsCount())); newExpr.Parameters.Add(new CodePrimitiveExpression( dataBoundBuilder.GetDataBoundLiteralCount())); } // e.g. {{controlTypeName}} __ctrl; statements.Add(new CodeVariableDeclarationStatement(ctrlTypeRef, "__ctrl")); ctrlRefExpr = new CodeVariableReferenceExpression("__ctrl"); // e.g. __ctrl = new {{controlTypeName}}(); CodeAssignStatement setCtl = new CodeAssignStatement(ctrlRefExpr, newExpr); setCtl.LinePragma = linePragma; statements.Add(setCtl); if (!builder.IsGeneratedID) { // Assign the local control reference to the global control variable CodeFieldReferenceExpression ctrlNameExpr = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), builder.ID); // e.g. {{controlName}} = __ctrl; CodeAssignStatement underscoreCtlSet = new CodeAssignStatement(ctrlNameExpr, ctrlRefExpr); statements.Add(underscoreCtlSet); } // Don't do this if the control is itself a TemplateControl, in which case it // will point its TemplateControl property to itself (instead of its parent // TemplateControl). VSWhidbey 214356. if (topLevelControlInTemplate && !typeof(TemplateControl).IsAssignableFrom(ctrlType)) { statements.Add(BuildTemplatePropertyStatement(ctrlRefExpr)); } if (fStandardControl) { // e.g. __ctrl.SkinID = {{skinID}}; if (builder.SkinID != null) { CodeAssignStatement set = new CodeAssignStatement(); set.Left = new CodePropertyReferenceExpression(ctrlRefExpr, skinIDPropertyName); set.Right = new CodePrimitiveExpression(builder.SkinID); statements.Add(set); } // e.g. __ctrl.ApplyStyleSheetSkin(this); if (ThemeableAttribute.IsTypeThemeable(ctrlType)) { // e.g. __ctrl.ApplyStyleSheetSkin(this.Page); CodeMethodInvokeExpression applyStyleSheetExpr = new CodeMethodInvokeExpression(ctrlRefExpr, applyStyleSheetMethodName); applyStyleSheetExpr.Parameters.Add(BuildPagePropertyReferenceExpression()); statements.Add(applyStyleSheetExpr); } } } // Process the templates if (builder.TemplatePropertyEntries.Count > 0) { // Used to deal with the device filter conditionals CodeStatementCollection currentStmts; CodeStatementCollection nextStmts = statements; PropertyEntry previous = null; foreach (TemplatePropertyEntry pseSub in builder.TemplatePropertyEntries) { currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, pseSub, statements, ref currentStmts, out nextStmts); string controlName = pseSub.Builder.ID; CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); newDelegate.DelegateType = new CodeTypeReference(typeof(BuildTemplateMethod)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = buildMethodPrefix + controlName; CodeAssignStatement set = new CodeAssignStatement(); if (pseSub.PropertyInfo != null) { set.Left = new CodePropertyReferenceExpression(ctrlRefExpr, pseSub.Name); } else { set.Left = new CodeFieldReferenceExpression(ctrlRefExpr, pseSub.Name); } if (pseSub.BindableTemplate) { // e.g. __ctrl.{{templateName}} = new CompiledBindableTemplateBuilder( // e.g. new BuildTemplateMethod(this.__BuildControl {{controlName}}), // e.g. new ExtractTemplateValuesMethod(this.__ExtractValues {{controlName}})); CodeExpression newExtractValuesDelegate; if (pseSub.Builder.HasTwoWayBoundProperties) { newExtractValuesDelegate = new CodeDelegateCreateExpression(); ((CodeDelegateCreateExpression)newExtractValuesDelegate).DelegateType = new CodeTypeReference(typeof(ExtractTemplateValuesMethod)); ((CodeDelegateCreateExpression)newExtractValuesDelegate).TargetObject = new CodeThisReferenceExpression(); ((CodeDelegateCreateExpression)newExtractValuesDelegate).MethodName = extractTemplateValuesMethodPrefix + controlName; } else { newExtractValuesDelegate = new CodePrimitiveExpression(null); } newExpr = new CodeObjectCreateExpression(typeof(CompiledBindableTemplateBuilder)); newExpr.Parameters.Add(newDelegate); newExpr.Parameters.Add(newExtractValuesDelegate); } else { // e.g. __ctrl.{{templateName}} = new CompiledTemplateBuilder( // e.g. new BuildTemplateMethod(this.__BuildControl {{controlName}})); newExpr = new CodeObjectCreateExpression(typeof(CompiledTemplateBuilder)); newExpr.Parameters.Add(newDelegate); } set.Right = newExpr; set.LinePragma = CreateCodeLinePragma(pseSub.Builder); currentStmts.Add(set); } } // Is this BuilderData for a declarative control? If so initialize it (75330) // Only do this is the control field has been declared (i.e. not with templates) if (typeof(UserControl).IsAssignableFrom(ctrlType) && fControlFieldDeclared && !fControlSkin) { // e.g. _ctrl.InitializeAsUserControl(Context, Page); methCallExpression = new CodeMethodInvokeExpression(ctrlRefExpr, "InitializeAsUserControl"); methCallExpression.Parameters.Add(new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), pagePropertyName)); methCallStatement = new CodeExpressionStatement(methCallExpression); methCallStatement.LinePragma = linePragma; statements.Add(methCallStatement); } // Process the simple attributes if (builder.SimplePropertyEntries.Count > 0) { // Used to deal with the device filter conditionals CodeStatementCollection currentStmts; CodeStatementCollection nextStmts = statements; PropertyEntry previous = null; foreach (SimplePropertyEntry pseSub in builder.SimplePropertyEntries) { currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, pseSub, statements, ref currentStmts, out nextStmts); CodeStatement statement = pseSub.GetCodeStatement(this, ctrlRefExpr); statement.LinePragma = linePragma; currentStmts.Add(statement); } } // Call the helper method for allowing page developers to customize culture settings if (typeof(Page).IsAssignableFrom(ctrlType) && !fControlSkin) { // e.g. this.InitializeCulture(); methCallExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InitializeCulture"); methCallStatement = new CodeExpressionStatement(methCallExpression); methCallStatement.LinePragma = linePragma; statements.Add(methCallStatement); } // Automatic template support (i.e. ) CodeMethodInvokeExpression instantiateTemplateExpr = null; CodeConditionStatement templateIfStmt = null; CodeStatementCollection buildSubControlBlock = statements; string autoTemplateName = null; if (builder is System.Web.UI.WebControls.ContentPlaceHolderBuilder) { string templateName = ((System.Web.UI.WebControls.ContentPlaceHolderBuilder)builder).Name; autoTemplateName = MasterPageControlBuilder.AutoTemplatePrefix + templateName; Debug.Assert(autoTemplateName != null && autoTemplateName.Length > 0, "Template Name is empty."); // Generate a private field and public property for the ITemplate string fieldName = "__"+ autoTemplateName; Type containerType = builder.BindingContainerType; // Use the base class or template type if INamingContainer cannot be found. if (!typeof(INamingContainer).IsAssignableFrom(containerType)) { if (typeof(INamingContainer).IsAssignableFrom(Parser.BaseType)) { containerType = Parser.BaseType; } else { // This should not occur as all base classes are namingcontainers. Debug.Assert(false, "baseClassType is not an INamingContainer"); containerType = typeof(System.Web.UI.Control); } } CodeAttributeDeclarationCollection attrDeclarations = new CodeAttributeDeclarationCollection(); CodeAttributeDeclaration templateContainerAttrDeclaration = new CodeAttributeDeclaration( "TemplateContainer", new CodeAttributeArgument[] { new CodeAttributeArgument(new CodeTypeOfExpression(containerType))}); attrDeclarations.Add(templateContainerAttrDeclaration); // If the template control is in a template, assume its container allows multiple instances, // otherwise set the TemplateInstanceAttribute if (fInTemplate == false) { CodeAttributeDeclaration templateInstanceAttrDeclaration = new CodeAttributeDeclaration( "TemplateInstanceAttribute", new CodeAttributeArgument[] { new CodeAttributeArgument( new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(TemplateInstance)), "Single"))}); attrDeclarations.Add(templateInstanceAttrDeclaration); } BuildFieldAndAccessorProperty(autoTemplateName, fieldName, typeof(ITemplate), false /*fStatic*/, attrDeclarations); CodeExpression templateFieldRef = new CodeFieldReferenceExpression( new CodeThisReferenceExpression(), fieldName); if (builder is System.Web.UI.WebControls.ContentPlaceHolderBuilder) { // We generate something like this: // if (this.ContentTemplates != null) { // this.__Template_TestTemplate = (ITemplate)this.ContentTemplates[{templateName}]; // } CodePropertyReferenceExpression contentTemplatesFieldRef = new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "ContentTemplates"); CodeAssignStatement setStatement = new CodeAssignStatement(); setStatement.Left = templateFieldRef; setStatement.Right = new CodeCastExpression(typeof(ITemplate), new CodeIndexerExpression(contentTemplatesFieldRef, new CodePrimitiveExpression(templateName))); CodeConditionStatement contentTemplateIfStmt = new CodeConditionStatement(); CodeBinaryOperatorExpression contentNullCheckExpr = new CodeBinaryOperatorExpression(contentTemplatesFieldRef, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)); CodeMethodInvokeExpression removeExpr = new CodeMethodInvokeExpression(contentTemplatesFieldRef, "Remove"); removeExpr.Parameters.Add(new CodePrimitiveExpression(templateName)); contentTemplateIfStmt.Condition = contentNullCheckExpr; contentTemplateIfStmt.TrueStatements.Add(setStatement); statements.Add(contentTemplateIfStmt); } // We generate something like this: // if ((this.__Template_TestTemplate != null)) { // // For 2.0: // this.__Template_TestTemplate.InstantiateIn(__ctrl); // // For 4.0, use a new method. This is for fixing Dev10 bug 776195. // this.InstantiateInContentPlaceHolder(__ctrl, this.__Template_TestTemplate); // } // else { // // normal sub control building code // } if (MultiTargetingUtil.IsTargetFramework40OrAbove) { instantiateTemplateExpr = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InstantiateInContentPlaceHolder"); instantiateTemplateExpr.Parameters.Add(ctrlRefExpr); instantiateTemplateExpr.Parameters.Add(templateFieldRef); } else { instantiateTemplateExpr = new CodeMethodInvokeExpression(templateFieldRef, "InstantiateIn"); instantiateTemplateExpr.Parameters.Add(ctrlRefExpr); } templateIfStmt = new CodeConditionStatement(); templateIfStmt.Condition = new CodeBinaryOperatorExpression(templateFieldRef, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null)); templateIfStmt.TrueStatements.Add(new CodeExpressionStatement(instantiateTemplateExpr)); buildSubControlBlock = templateIfStmt.FalseStatements; statements.Add(templateIfStmt); } ICollection contentBuilderEntries = null; if (builder is FileLevelPageControlBuilder) { contentBuilderEntries = ((FileLevelPageControlBuilder)builder).ContentBuilderEntries; if (contentBuilderEntries != null) { CodeStatementCollection currentStmts; CodeStatementCollection nextStmts = statements; PropertyEntry previous = null; foreach (TemplatePropertyEntry entry in contentBuilderEntries) { System.Web.UI.WebControls.ContentBuilderInternal child = (System.Web.UI.WebControls.ContentBuilderInternal)entry.Builder; currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, entry, statements, ref currentStmts, out nextStmts); string controlName = child.ID; string contentPlaceHolderID = child.ContentPlaceHolder; CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); newDelegate.DelegateType = new CodeTypeReference(typeof(BuildTemplateMethod)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = buildMethodPrefix + controlName; // e.g. this.AddContentTemplate(contentPlaceHolderID, new CompiledTemplateBuilder( // e.g. new BuildTemplateMethod(this.__BuildControl {{controlName}})); CodeObjectCreateExpression cocExpr = new CodeObjectCreateExpression(typeof(CompiledTemplateBuilder)); cocExpr.Parameters.Add(newDelegate); CodeMethodInvokeExpression cmiExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "AddContentTemplate"); cmiExpression.Parameters.Add(new CodePrimitiveExpression(contentPlaceHolderID)); cmiExpression.Parameters.Add(cocExpr); CodeExpressionStatement ceStatement = new CodeExpressionStatement(cmiExpression); ceStatement.LinePragma = CreateCodeLinePragma((ControlBuilder)child); currentStmts.Add(ceStatement); } } } if (builder is DataBoundLiteralControlBuilder) { // If it's a DataBoundLiteralControl, build it by calling SetStaticString // on all the static literal strings. int i = -1; foreach (object child in builder.SubBuilders) { i++; // Ignore it if it's null if (child == null) continue; // Only deal with the strings here, which have even index if (i % 2 == 1) { Debug.Assert(child is CodeBlockBuilder, "child is CodeBlockBuilder"); continue; } string s = (string) child; // e.g. __ctrl.SetStaticString(3, "literal string"); methCallExpression = new CodeMethodInvokeExpression(ctrlRefExpr, "SetStaticString"); methCallExpression.Parameters.Add(new CodePrimitiveExpression(i/2)); methCallExpression.Parameters.Add(new CodePrimitiveExpression(s)); statements.Add(new CodeExpressionStatement(methCallExpression)); } } // Process the children else if (builder.SubBuilders != null) { bool gotParserVariable = false; int localVarIndex = 1; foreach (object child in builder.SubBuilders) { if (child is ControlBuilder && !(child is CodeBlockBuilder) && !(child is CodeStatementBuilder) && !(child is System.Web.UI.WebControls.ContentBuilderInternal)) { ControlBuilder ctrlBuilder = (ControlBuilder) child; if (fControlSkin) { throw new HttpParseException(SR.GetString(SR.ControlSkin_cannot_contain_controls), null, builder.VirtualPath, null, builder.Line); } PartialCachingAttribute cacheAttrib = (PartialCachingAttribute) TypeDescriptor.GetAttributes(ctrlBuilder.ControlType)[typeof(PartialCachingAttribute)]; methCallExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), buildMethodPrefix + ctrlBuilder.ID); methCallStatement = new CodeExpressionStatement(methCallExpression); if (cacheAttrib == null) { string localVariableRef = _localVariableRef + (localVarIndex++).ToString(CultureInfo.InvariantCulture); // Variable reference to the local control variable CodeVariableReferenceExpression childCtrlRefExpr = new CodeVariableReferenceExpression(localVariableRef); // e.g. {{controlTypeName}} ctrl5; CodeTypeReference ctrlTypeReference = CodeDomUtility.BuildGlobalCodeTypeReference(ctrlBuilder.ControlType); buildSubControlBlock.Add(new CodeVariableDeclarationStatement(ctrlTypeReference, localVariableRef)); // e.g. ctrl5 = __BuildControl__control6(); CodeAssignStatement setCtl = new CodeAssignStatement(childCtrlRefExpr, methCallExpression); setCtl.LinePragma = linePragma; buildSubControlBlock.Add(setCtl); // If there is no caching on the control, just create it and add it // e.g. __parser.AddParsedSubObject(ctrl5); BuildAddParsedSubObjectStatement( buildSubControlBlock, childCtrlRefExpr, linePragma, ctrlRefExpr, ref gotParserVariable); } else { string providerName = null; // Only use the providerName parameter when targeting 4.0 and above bool useProviderName = MultiTargetingUtil.IsTargetFramework40OrAbove; if (useProviderName) { providerName = cacheAttrib.ProviderName; if (providerName == OutputCache.ASPNET_INTERNAL_PROVIDER_NAME) { providerName = null; } } // The control's output is getting cached. Call // StaticPartialCachingControl.BuildCachedControl to do the work. // e.g. StaticPartialCachingControl.BuildCachedControl(__ctrl, Request, "e4192e6d-cbe0-4df5-b516-682c10415590", __pca, new System.Web.UI.BuildMethod(this.__BuildControlt1)); CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(); call.Method.TargetObject = new CodeTypeReferenceExpression(typeof(System.Web.UI.StaticPartialCachingControl)); call.Method.MethodName = "BuildCachedControl"; call.Parameters.Add(ctrlRefExpr); call.Parameters.Add(new CodePrimitiveExpression(ctrlBuilder.ID)); // If the caching is shared, use the type of the control as the key // otherwise, generate a guid if (cacheAttrib.Shared) { call.Parameters.Add(new CodePrimitiveExpression( ctrlBuilder.ControlType.GetHashCode().ToString(CultureInfo.InvariantCulture))); } else call.Parameters.Add(new CodePrimitiveExpression(Guid.NewGuid().ToString())); call.Parameters.Add(new CodePrimitiveExpression(cacheAttrib.Duration)); call.Parameters.Add(new CodePrimitiveExpression(cacheAttrib.VaryByParams)); call.Parameters.Add(new CodePrimitiveExpression(cacheAttrib.VaryByControls)); call.Parameters.Add(new CodePrimitiveExpression(cacheAttrib.VaryByCustom)); call.Parameters.Add(new CodePrimitiveExpression(cacheAttrib.SqlDependency)); CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); newDelegate.DelegateType = new CodeTypeReference(typeof(BuildMethod)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = buildMethodPrefix + ctrlBuilder.ID; call.Parameters.Add(newDelegate); if (useProviderName) { call.Parameters.Add(new CodePrimitiveExpression(providerName)); } buildSubControlBlock.Add(new CodeExpressionStatement(call)); } } else if (child is string && !builder.HasAspCode) { // VSWhidbey 276806: if the control cares about the inner text (builder does not allow whitespace literals) // the inner literals should be added to the control. if (!fControlSkin || !builder.AllowWhitespaceLiterals()) { string s = (string) child; CodeExpression expr; if (!UseResourceLiteralString(s)) { // e.g. ((IParserAccessor)__ctrl).AddParsedSubObject(new LiteralControl({{@QuoteCString(text)}})); newExpr = new CodeObjectCreateExpression(typeof(LiteralControl)); newExpr.Parameters.Add(new CodePrimitiveExpression(s)); expr = newExpr; } else { // Add the string to the resource builder, and get back its offset/size int offset, size; bool fAsciiOnly; _stringResourceBuilder.AddString(s, out offset, out size, out fAsciiOnly); methCallExpression = new CodeMethodInvokeExpression(); methCallExpression.Method.TargetObject = new CodeThisReferenceExpression(); methCallExpression.Method.MethodName = "CreateResourceBasedLiteralControl"; methCallExpression.Parameters.Add(new CodePrimitiveExpression(offset)); methCallExpression.Parameters.Add(new CodePrimitiveExpression(size)); methCallExpression.Parameters.Add(new CodePrimitiveExpression(fAsciiOnly)); expr = methCallExpression; } BuildAddParsedSubObjectStatement(buildSubControlBlock, expr, linePragma, ctrlRefExpr, ref gotParserVariable); } } } } // Process the complex attributes if (builder.ComplexPropertyEntries.Count > 0) { // Used to deal with the device filter conditionals CodeStatementCollection currentStmts; CodeStatementCollection nextStmts = statements; PropertyEntry previous = null; int localVarIndex = 1; String localVariableRef = null; foreach (ComplexPropertyEntry pseSub in builder.ComplexPropertyEntries) { currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, pseSub, statements, ref currentStmts, out nextStmts); if (pseSub.Builder is StringPropertyBuilder) { // If it's a string inner property, treat it like a simple property CodeExpression leftExpr, rightExpr = null; // __ctrl.{{_name}} // In case of a string property, there should only be one property name (unlike complex properties) Debug.Assert(pseSub.Name.IndexOf('.') < 0, "pseSub._name.IndexOf('.') < 0"); leftExpr = new CodePropertyReferenceExpression(ctrlRefExpr, pseSub.Name); // We need to call BuildStringPropertyExpression so any additional processing can be done rightExpr = BuildStringPropertyExpression(pseSub); // Now that we have both side, add the assignment CodeAssignStatement setStatment = new CodeAssignStatement(leftExpr, rightExpr); setStatment.LinePragma = linePragma; currentStmts.Add(setStatment); continue; } if (pseSub.ReadOnly) { if (fControlSkin && pseSub.Builder != null && pseSub.Builder is CollectionBuilder && pseSub.Builder.ComplexPropertyEntries.Count > 0) { // If it's a collection on a control theme and the themed collection is not empty, clear it first. // e.g. __ctrl.{{pse_name}}.Clear(); BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance; // Look for the "Clear" method on the collection. if (pseSub.Type.GetMethod("Clear", bindingFlags) != null) { CodeMethodReferenceExpression refExpr = new CodeMethodReferenceExpression(); refExpr.MethodName = "Clear"; refExpr.TargetObject = new CodePropertyReferenceExpression(ctrlRefExpr, pseSub.Name); CodeMethodInvokeExpression invokeClearExpr = new CodeMethodInvokeExpression(); invokeClearExpr.Method = refExpr; currentStmts.Add(invokeClearExpr); } } // If it's a readonly prop, pass it as a parameter to the // build method. // e.g. __BuildControl {{controlName}}(__ctrl.{{pse._name}}); methCallExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), buildMethodPrefix + pseSub.Builder.ID); methCallExpression.Parameters.Add(new CodePropertyReferenceExpression(ctrlRefExpr, pseSub.Name)); methCallStatement = new CodeExpressionStatement(methCallExpression); methCallStatement.LinePragma = linePragma; currentStmts.Add(methCallStatement); } else { localVariableRef = _localVariableRef + (localVarIndex++).ToString(CultureInfo.InvariantCulture); // e.g. {{controlTypeName}} ctrl4; CodeTypeReference ctrlTypeReference = CodeDomUtility.BuildGlobalCodeTypeReference(pseSub.Builder.ControlType); currentStmts.Add(new CodeVariableDeclarationStatement(ctrlTypeReference, localVariableRef)); // Variable reference to the local control variable. CodeVariableReferenceExpression childCtrlRefExpr = new CodeVariableReferenceExpression(localVariableRef); methCallExpression = new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), buildMethodPrefix + pseSub.Builder.ID); methCallStatement = new CodeExpressionStatement(methCallExpression); // e.g. ctrl4 = __BuildControl {{controlName}}(); CodeAssignStatement setCtl = new CodeAssignStatement(childCtrlRefExpr, methCallExpression); setCtl.LinePragma = linePragma; currentStmts.Add(setCtl); if (pseSub.IsCollectionItem) { // e.g. __ctrl.Add(ctrl4); methCallExpression = new CodeMethodInvokeExpression(ctrlRefExpr, "Add"); methCallStatement = new CodeExpressionStatement(methCallExpression); methCallStatement.LinePragma = linePragma; currentStmts.Add(methCallStatement); methCallExpression.Parameters.Add(childCtrlRefExpr); } else { // e.g. __ctrl.{{pse._name}} = {{controlName}}; CodeAssignStatement set = new CodeAssignStatement(); set.Left = new CodePropertyReferenceExpression(ctrlRefExpr, pseSub.Name); set.Right = childCtrlRefExpr; set.LinePragma = linePragma; currentStmts.Add(set); } } } } // If there are bound properties, hook up the binding method if (builder.BoundPropertyEntries.Count > 0) { bool isBindableTemplateBuilder = builder is BindableTemplateBuilder; bool hasDataBindingEntry = false; // Used to deal with the device filter conditionals CodeStatementCollection currentStmts; CodeStatementCollection methodStatements = statements; CodeStatementCollection nextStmts = statements; PropertyEntry previous = null; bool hasTempObject = false; foreach (BoundPropertyEntry entry in builder.BoundPropertyEntries) { // Skip two-way entries if it's a BindableTemplateBuilder or the two-way entry has no setter if (entry.TwoWayBound && (isBindableTemplateBuilder || entry.ReadOnlyProperty)) continue; if (entry.IsDataBindingEntry) { hasDataBindingEntry = true; continue; } currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, entry, statements, ref currentStmts, out nextStmts); ExpressionBuilder eb = entry.ExpressionBuilder; Debug.Assert(eb != null, "Did not expect null expression builder"); eb.BuildExpression(entry, builder, ctrlRefExpr, methodStatements, currentStmts, null, ref hasTempObject); } if (hasDataBindingEntry) { EventInfo eventInfo = DataBindingExpressionBuilder.Event; // __ctrl.{EventName} += new EventHandler(this.{{bindingMethod}}) CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); CodeAttachEventStatement attachEvent = new CodeAttachEventStatement(ctrlRefExpr, eventInfo.Name, newDelegate); attachEvent.LinePragma = linePragma; newDelegate.DelegateType = new CodeTypeReference(typeof(EventHandler)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = GetExpressionBuilderMethodName(eventInfo.Name, builder); statements.Add(attachEvent); } } if (builder is DataBoundLiteralControlBuilder) { // __ctrl.DataBinding += new EventHandler(this.{{bindingMethod}}) CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); CodeAttachEventStatement attachEvent = new CodeAttachEventStatement(ctrlRefExpr, "DataBinding", newDelegate); attachEvent.LinePragma = linePragma; newDelegate.DelegateType = new CodeTypeReference(typeof(EventHandler)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = BindingMethodName(builder); statements.Add(attachEvent); } // If there is any ASP code, set the render method delegate if (builder.HasAspCode && !fControlSkin) { // e.g. __ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render {{controlName}})); CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); newDelegate.DelegateType = new CodeTypeReference(typeof(RenderMethod)); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = "__Render" + builder.ID; methCallExpression = new CodeMethodInvokeExpression(ctrlRefExpr, "SetRenderMethodDelegate"); methCallExpression.Parameters.Add(newDelegate); methCallStatement = new CodeExpressionStatement(methCallExpression); // VSWhidbey 579101 // If this is a contentPlaceHolder, we need to check if there is any content defined. // We set the render method only when there is no contentTemplate defined. // if ((this.__Template_TestTemplate == null)) { // __ctrl.SetRenderMethodDelegate(new RenderMethod(this.__Render {{controlName}})); // } if (builder is System.Web.UI.WebControls.ContentPlaceHolderBuilder) { string templateName = ((System.Web.UI.WebControls.ContentPlaceHolderBuilder)builder).Name; autoTemplateName = MasterPageControlBuilder.AutoTemplatePrefix + templateName; string fieldName = "__" + autoTemplateName; CodeExpression templateFieldRef = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName); templateIfStmt = new CodeConditionStatement(); templateIfStmt.Condition = new CodeBinaryOperatorExpression(templateFieldRef, CodeBinaryOperatorType.IdentityEquality, new CodePrimitiveExpression(null)); templateIfStmt.TrueStatements.Add(methCallStatement); statements.Add(templateIfStmt); } else { statements.Add(methCallStatement); } } // Process the events if (builder.EventEntries.Count > 0) { foreach (EventEntry eventEntry in builder.EventEntries) { // Attach the event. Detach it first to avoid duplicates (see ASURT 42603), // but only if there is codebehind // // e.g. __ctrl.ServerClick -= new System.EventHandler(this.buttonClicked); // e.g. __ctrl.ServerClick += new System.EventHandler(this.buttonClicked); CodeDelegateCreateExpression newDelegate = new CodeDelegateCreateExpression(); newDelegate.DelegateType = new CodeTypeReference(eventEntry.HandlerType); newDelegate.TargetObject = new CodeThisReferenceExpression(); newDelegate.MethodName = eventEntry.HandlerMethodName; if (Parser.HasCodeBehind) { CodeRemoveEventStatement detachEvent = new CodeRemoveEventStatement(ctrlRefExpr, eventEntry.Name, newDelegate); detachEvent.LinePragma = linePragma; statements.Add(detachEvent); } CodeAttachEventStatement attachEvent = new CodeAttachEventStatement(ctrlRefExpr, eventEntry.Name, newDelegate); attachEvent.LinePragma = linePragma; statements.Add(attachEvent); } } // If a control field is declared, we need to return it at the end of the method. if (fControlFieldDeclared) statements.Add(new CodeMethodReturnStatement(ctrlRefExpr)); } /* * Build the template's method to extract values from contained controls */ protected void BuildExtractMethod(ControlBuilder builder) { BindableTemplateBuilder bindableTemplateBuilder = builder as BindableTemplateBuilder; // This will get called if Bind is in a non-bindable template. We should just skip the Extract method. if (bindableTemplateBuilder != null && bindableTemplateBuilder.HasTwoWayBoundProperties) { // Get the name of the databinding method string methodName = ExtractMethodName(builder); const string tableVarName = "__table"; const string containerVarName = "__container"; // Same linePragma in the entire method CodeLinePragma linePragma = CreateCodeLinePragma(builder); CodeMemberMethod method = new CodeMemberMethod(); AddDebuggerNonUserCodeAttribute(method); method.Name = methodName; method.Attributes &= ~MemberAttributes.AccessMask; method.Attributes |= MemberAttributes.Public; method.ReturnType = new CodeTypeReference(typeof(IOrderedDictionary)); _sourceDataClass.Members.Add(method); /// Variable declarations need to go at the top for CodeDom compliance. CodeStatementCollection topLevelStatements = method.Statements; CodeStatementCollection statements = new CodeStatementCollection(); // add a container control parameter method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Control), containerVarName)); // OrderedDictionary table; CodeVariableDeclarationStatement tableDecl = new CodeVariableDeclarationStatement(typeof(OrderedDictionary), tableVarName); topLevelStatements.Add(tableDecl); // table = new OrderedDictionary(); CodeObjectCreateExpression newTableExpression = new CodeObjectCreateExpression(typeof(OrderedDictionary)); CodeAssignStatement newTableAssign = new CodeAssignStatement(new CodeVariableReferenceExpression(tableVarName), newTableExpression); newTableAssign.LinePragma = linePragma; statements.Add(newTableAssign); BuildExtractStatementsRecursive(bindableTemplateBuilder.SubBuilders, statements, topLevelStatements, linePragma, tableVarName, containerVarName); // return table; CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(new CodeVariableReferenceExpression(tableVarName)); statements.Add(returnStatement); // add all the non-variable declaration statements to the bottom of the method method.Statements.AddRange(statements); } } private void BuildExtractStatementsRecursive(ArrayList subBuilders, CodeStatementCollection statements, CodeStatementCollection topLevelStatements, CodeLinePragma linePragma, string tableVarName, string containerVarName) { foreach (object subBuilderObject in subBuilders) { ControlBuilder controlBuilder = subBuilderObject as ControlBuilder; if (controlBuilder != null) { // Used to deal with the device filter conditionals CodeStatementCollection currentStatements = null; CodeStatementCollection nextStatements = statements; PropertyEntry previous = null; string previousControlName = null; bool newControl = true; foreach (BoundPropertyEntry entry in controlBuilder.BoundPropertyEntries) { // Skip all entries that are not two-way if (!entry.TwoWayBound) continue; // Reset the "previous" Property Entry if we're not looking at the same control. // If we don't do this, Two controls that have conditionals on the same named property will have // their conditionals incorrectly merged. if (String.Compare(previousControlName, entry.ControlID, StringComparison.Ordinal) != 0) { previous = null; newControl = true; } else { newControl = false; } previousControlName = entry.ControlID; currentStatements = nextStatements; HandleDeviceFilterConditional(ref previous, entry, statements, ref currentStatements, out nextStatements); // Only declare the variable if it hasn't already been declared by a previous filter // or property binding on the same control. if (newControl) { // {{controlType}} {{controlID}}; // eg. TextBox t1; CodeVariableDeclarationStatement controlDecl = new CodeVariableDeclarationStatement(entry.ControlType, entry.ControlID); topLevelStatements.Add(controlDecl); // {{controlID}} = ({{controlType}})container.FindControl("{{controlID}}"); // eg. t1 = (TextBox)container.FindControl("t1"); CodeMethodInvokeExpression findControlCallExpression = new CodeMethodInvokeExpression( new CodeVariableReferenceExpression(containerVarName), "FindControl"); string findControlParameter = entry.ControlID; findControlCallExpression.Parameters.Add(new CodePrimitiveExpression(findControlParameter)); CodeCastExpression castExpression = new CodeCastExpression(entry.ControlType, findControlCallExpression); CodeAssignStatement findControlAssign = new CodeAssignStatement(new CodeVariableReferenceExpression(entry.ControlID), castExpression); findControlAssign.LinePragma = linePragma; topLevelStatements.Add(findControlAssign); } // if ({{controlID}} != null) // table["{{fieldName}}"] = {{controlID}}.{{propertyName}}); // eg. if (t1 != null) // eg. table["field"] = t1.Text); CodeConditionStatement ifStatement = new CodeConditionStatement(); CodeBinaryOperatorExpression ensureControlExpression = new CodeBinaryOperatorExpression(); ensureControlExpression.Operator = CodeBinaryOperatorType.IdentityInequality; ensureControlExpression.Left = new CodeVariableReferenceExpression(entry.ControlID); ensureControlExpression.Right = new CodePrimitiveExpression(null); ifStatement.Condition = ensureControlExpression; string fieldParameter = entry.FieldName; CodeIndexerExpression tableIndexer = new CodeIndexerExpression(new CodeVariableReferenceExpression(tableVarName), new CodePrimitiveExpression(fieldParameter)); // VJ# does not support automatic boxing of value types, so passing a simple type, say a bool, into a method // expecting an object will give a compiler error. We are working around this issue by adding special code for // VJ# that will cast the expression for boxing. When the VJ# team adds implicit boxing of value types, we // should remove this code. VSWhidbey 269028 CodeExpression controlPropertyExpression = CodeDomUtility.BuildPropertyReferenceExpression(new CodeVariableReferenceExpression(entry.ControlID), entry.Name); if (_usingVJSCompiler) { controlPropertyExpression = CodeDomUtility.BuildJSharpCastExpression(entry.Type, controlPropertyExpression); } CodeAssignStatement tableIndexAssign = new CodeAssignStatement(tableIndexer, controlPropertyExpression); ifStatement.TrueStatements.Add(tableIndexAssign); ifStatement.LinePragma = linePragma; currentStatements.Add(ifStatement); } if (controlBuilder.SubBuilders.Count > 0) { BuildExtractStatementsRecursive(controlBuilder.SubBuilders, statements, topLevelStatements, linePragma, tableVarName, containerVarName); } // Dev10 bug 525267 // When a control defines a DefaultProperty in its ParseChildren attribute, its subBuilders are appended // to the DefaultProperty's subbuilders, and the DefaultProperty itself is added // as a ComplexProperty or a TemplateProperty (and not as a suBbuilder). Thus we // also need to go through these properties as well. ArrayList list = new ArrayList(); AddEntryBuildersToList(controlBuilder.ComplexPropertyEntries, list); AddEntryBuildersToList(controlBuilder.TemplatePropertyEntries, list); if (list.Count > 0) { BuildExtractStatementsRecursive(list, statements, topLevelStatements, linePragma, tableVarName, containerVarName); } } } } private void AddEntryBuildersToList(ICollection entries, ArrayList list) { if (entries == null || list == null) { return; } foreach (BuilderPropertyEntry entry in entries) { if (entry.Builder != null) { TemplatePropertyEntry templatePropertyEntry = entry as TemplatePropertyEntry; // Only add template entries that have TemplateInstance.Single if (templatePropertyEntry != null && templatePropertyEntry.IsMultiple) { continue; } list.Add(entry.Builder); } } } /* * Build the member field's declaration for a control */ private void BuildFieldDeclaration(ControlBuilder builder) { // Do not generate member field for content controls. if (builder is System.Web.UI.WebControls.ContentBuilderInternal) { return; } bool hideExistingMember = false; // If we're using a non-default base class if (Parser.BaseType != null) { // Check if it has a non-private field or property that has a name that // matches the id of the control. Type memberType = Util.GetNonPrivateFieldType(Parser.BaseType, builder.ID); // Couldn't find a field, try a property (ASURT 45039) // if (memberType == null) memberType = Util.GetNonPrivatePropertyType(Parser.BaseType, builder.ID); if (memberType != null) { if (!memberType.IsAssignableFrom(builder.ControlType)) { if (!(typeof(Control)).IsAssignableFrom(memberType)) { // If it's not a control, it's probably an unrelated member, // and we should just hide it (VSWhidbey 217135) hideExistingMember = true; } else { throw new HttpParseException(SR.GetString(SR.Base_class_field_with_type_different_from_type_of_control, builder.ID, memberType.FullName, builder.ControlType.FullName), null, builder.VirtualPath, null, builder.Line); } } else { // Don't build the declaration, since the base class already declares it return; } } } // Add the field. Make it protected if the ID was declared, and private if it was generated CodeMemberField field = new CodeMemberField(CodeDomUtility.BuildGlobalCodeTypeReference( builder.DeclareType), builder.ID); field.Attributes &= ~MemberAttributes.AccessMask; // If we need to hide an existing member, use 'new' (VSWhidbey 217135) if (hideExistingMember) field.Attributes |= MemberAttributes.New; field.LinePragma = CreateCodeLinePragma(builder); field.Attributes |= MemberAttributes.Family; // Set WithEvents in the UserData, so that the field will be // declared as WithEvents in VB (VSWhidbey 156623). // But only do this if it's a Control, otherwise it may not have // any events (VSWhidbey 283274). if (typeof(Control).IsAssignableFrom(builder.DeclareType)) { field.UserData["WithEvents"] = true; } _intermediateClass.Members.Add(field); } private string GetExpressionBuilderMethodName(string eventName, ControlBuilder builder) { return "__" + eventName + builder.ID; } /* * Return the name of a databinding method */ private string BindingMethodName(ControlBuilder builder) { return "__DataBind" + builder.ID; } protected CodeMemberMethod BuildPropertyBindingMethod(ControlBuilder builder, bool fControlSkin) { // VSWhidbey 275175: Create the tempObjectVariable "__o" only when it's used. bool tempObjectVariableDeclared = false; if (builder is DataBoundLiteralControlBuilder) { // Get the name of the databinding method string methodName = BindingMethodName(builder); // Same linePragma in the entire method CodeLinePragma linePragma = CreateCodeLinePragma(builder); CodeMemberMethod method = new CodeMemberMethod(); method.Name = methodName; method.Attributes &= ~MemberAttributes.AccessMask; method.Attributes |= MemberAttributes.Public; method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender")); method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(EventArgs), "e")); CodeStatementCollection topMethodStatements = new CodeStatementCollection(); CodeStatementCollection otherMethodStatements = new CodeStatementCollection(); // {{controlType}} target; CodeVariableDeclarationStatement targetDecl = new CodeVariableDeclarationStatement(builder.ControlType, "target"); Type bindingContainerType = builder.BindingContainerType; CodeVariableDeclarationStatement containerDecl = new CodeVariableDeclarationStatement(bindingContainerType, "Container"); topMethodStatements.Add(containerDecl); topMethodStatements.Add(targetDecl); // target = ({{controlType}}) sender; CodeAssignStatement setTarget = new CodeAssignStatement(new CodeVariableReferenceExpression(targetDecl.Name), new CodeCastExpression(builder.ControlType, new CodeArgumentReferenceExpression("sender"))); setTarget.LinePragma = linePragma; otherMethodStatements.Add(setTarget); // {{containerType}} Container = ({{containerType}}) target.BindingContainer; CodeAssignStatement setContainer = new CodeAssignStatement(new CodeVariableReferenceExpression(containerDecl.Name), new CodeCastExpression(bindingContainerType, new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("target"), "BindingContainer"))); setContainer.LinePragma = linePragma; otherMethodStatements.Add(setContainer); DataBindingExpressionBuilder.GenerateItemTypeExpressions(builder, topMethodStatements, otherMethodStatements, linePragma, "Item"); //Generate code for BindItem as well at design time in addition to Item for intellisense. //When you are in designer and as you type to set an attribute value to a data binding expression, since the control's end tag is not yet //typed in, we will still have a DataBoundLiteralControl (instead of a ControlBuilder) in that scenario and the below code takes care of that scenario. if (_designerMode) { DataBindingExpressionBuilder.GenerateItemTypeExpressions(builder, topMethodStatements, otherMethodStatements, linePragma, "BindItem"); } // If it's a DataBoundLiteralControl, call SetDataBoundString for each // of the databinding expressions int i = -1; foreach (object child in builder.SubBuilders) { i++; // Ignore it if it's null if (child == null) continue; // Only deal with the databinding expressions here, which have odd index if (i % 2 == 0) { Debug.Assert(child is string, "child is string"); continue; } CodeBlockBuilder codeBlock = (CodeBlockBuilder) child; Debug.Assert(codeBlock.BlockType == CodeBlockType.DataBinding); // In designer mode, generate a much simpler assignment to make // the code simpler (since it doesn't actually need to run). if (_designerMode) { tempObjectVariableDeclared = GenerateSimpleAssignmentAtDesignTime(tempObjectVariableDeclared, topMethodStatements, otherMethodStatements, codeBlock.Content, CreateCodeLinePragma(codeBlock)); continue; } CodeExpression expr = new CodeSnippetExpression(codeBlock.Content.Trim()); if (codeBlock.IsEncoded) { // HttpUtility.HtmlEncode({{codeExpr}})); expr = new CodeMethodInvokeExpression( new CodeMethodReferenceExpression( new CodeTypeReferenceExpression(typeof(HttpUtility)), "HtmlEncode"), expr); } else { // System.Convert.ToString({{codeExpr}}); expr = CodeDomUtility.GenerateConvertToString(expr); } // target.SetDataBoundString(3, {{codeExpr}}); {{codeExpr}} is one of the above in if-else depending on IsEncoded property. CodeMethodInvokeExpression methCallExpression = new CodeMethodInvokeExpression( new CodeVariableReferenceExpression("target"), "SetDataBoundString"); methCallExpression.Parameters.Add(new CodePrimitiveExpression(i/2)); methCallExpression.Parameters.Add(expr); CodeStatement setDataBoundStringCall = new CodeExpressionStatement(methCallExpression); setDataBoundStringCall.LinePragma = CreateCodeLinePragma(codeBlock); otherMethodStatements.Add(setDataBoundStringCall); } foreach (CodeStatement stmt in topMethodStatements) { method.Statements.Add(stmt); } foreach (CodeStatement stmt in otherMethodStatements) { method.Statements.Add(stmt); } _sourceDataClass.Members.Add(method); return method; } else { EventInfo eventInfo = DataBindingExpressionBuilder.Event; // Same linePragma in the entire method CodeLinePragma linePragma = CreateCodeLinePragma(builder); CodeMemberMethod method = null; CodeStatementCollection topStatements = null; CodeStatementCollection otherStatements = null; // Used to deal with the device filter conditionals CodeStatementCollection currentStmts; CodeStatementCollection nextStmts = null; PropertyEntry previous = null; bool isBindableTemplateBuilder = builder is BindableTemplateBuilder; bool firstEntry = true; bool hasTempObject = false; foreach (BoundPropertyEntry entry in builder.BoundPropertyEntries) { // Skip two-way entries if it's a BindableTemplateBuilder or the two way entry is read only if (entry.TwoWayBound && (isBindableTemplateBuilder || entry.ReadOnlyProperty)) continue; // We only care about databinding entries here if (!entry.IsDataBindingEntry) continue; if (firstEntry) { firstEntry = false; method = new CodeMemberMethod(); topStatements = new CodeStatementCollection(); otherStatements = new CodeStatementCollection(); // Get the name of the databinding method string methodName = GetExpressionBuilderMethodName(eventInfo.Name, builder); method.Name = methodName; method.Attributes &= ~MemberAttributes.AccessMask; method.Attributes |= MemberAttributes.Public; if (_designerMode) { ApplyEditorBrowsableCustomAttribute(method); } Type eventHandlerType = eventInfo.EventHandlerType; MethodInfo mi = eventHandlerType.GetMethod("Invoke"); ParameterInfo[] paramInfos = mi.GetParameters(); foreach (ParameterInfo pi in paramInfos) { method.Parameters.Add(new CodeParameterDeclarationExpression(pi.ParameterType, pi.Name)); } nextStmts = otherStatements; DataBindingExpressionBuilder.BuildExpressionSetup(builder, topStatements, otherStatements, linePragma, entry.TwoWayBound, _designerMode); _sourceDataClass.Members.Add(method); } currentStmts = nextStmts; HandleDeviceFilterConditional(ref previous, entry, otherStatements, ref currentStmts, out nextStmts); // In designer mode, generate a much simpler assignment to make // the code simpler (since it doesn't actually need to run). if (_designerMode) { int generatedColumn = tempObjectVariable.Length + BaseCodeDomTreeGenerator.GetGeneratedColumnOffset(_codeDomProvider); CodeLinePragma codeLinePragma = CreateCodeLinePragma(virtualPath: builder.PageVirtualPath, lineNumber: entry.Line, column: entry.Column, generatedColumn: generatedColumn, codeLength: entry.Expression.Length); tempObjectVariableDeclared = GenerateSimpleAssignmentAtDesignTime(tempObjectVariableDeclared, topStatements, otherStatements, entry.Expression, codeLinePragma); continue; } if (entry.TwoWayBound) { Debug.Assert(!entry.ReadOnlyProperty, "We should not attempt to build a data binding handler if the two way entry is read only."); Debug.Assert(!entry.UseSetAttribute, "Two-way binding is not supported on expandos - this should have been prevented in ControlBuilder"); DataBindingExpressionBuilder.BuildEvalExpression(entry.FieldName, entry.FormatString, entry.Name, entry.Type, builder, topStatements, currentStmts, linePragma, entry.IsEncoded, ref hasTempObject); } else { DataBindingExpressionBuilder.BuildExpressionStatic(entry, builder, null, topStatements, currentStmts, linePragma, entry.IsEncoded, ref hasTempObject); } } if (topStatements != null) { foreach (CodeStatement stmt in topStatements) { method.Statements.Add(stmt); } } if (otherStatements != null) { foreach (CodeStatement stmt in otherStatements) { method.Statements.Add(stmt); } } return method; } } /* * Build the data tree for a control's render method */ internal void BuildRenderMethod(ControlBuilder builder, bool fTemplate) { CodeMemberMethod method = new CodeMemberMethod(); method.Attributes = MemberAttributes.Private | MemberAttributes.Final; method.Name = "__Render" + builder.ID; if (_designerMode) { ApplyEditorBrowsableCustomAttribute(method); } method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(HtmlTextWriter), renderMethodParameterName)); method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(Control), "parameterContainer")); _sourceDataClass.Members.Add(method); // VSWhidbey 275175: Create the tempObjectVariable "__o" only when it's used. bool tempObjectVariableDeclared = false; // Process the children if any if (builder.SubBuilders != null) { IEnumerator en = builder.SubBuilders.GetEnumerator(); // Index that the control will have in its parent's Controls // collection. // int controlIndex = 0; for (int i=0; en.MoveNext(); i++) { object child = en.Current; CodeLinePragma linePragma = null; if (child is ControlBuilder) { linePragma = CreateCodeLinePragma((ControlBuilder)child); } if (child is string) { if (_designerMode) continue; AddOutputWriteStringStatement(method.Statements, (string)child); } else if (child is CodeBlockBuilder) { CodeBlockBuilder codeBlockBuilder = (CodeBlockBuilder)child; if (codeBlockBuilder.BlockType == CodeBlockType.Expression || codeBlockBuilder.BlockType == CodeBlockType.EncodedExpression) { string codeExpression = codeBlockBuilder.Content; // In designer mode, generate a much simpler assignment to make // the code simpler (since it doesn't actually need to run). if (_designerMode) { tempObjectVariableDeclared = GenerateSimpleAssignmentAtDesignTime(tempObjectVariableDeclared, method.Statements, method.Statements, codeExpression, linePragma); continue; } // The purpose of the following logic is to improve the debugging experience. // Basically, we gain control on the formatting of the generated line // that calls output.Write, in order to try to make the call line up // with the <%= ... %> block. It's not always perfect, but it does a decent job // and is always better than the v1 behavior. // Get the Write() statement codedom tree CodeStatement outputWrite = GetOutputWriteStatement( new CodeSnippetExpression(codeExpression), codeBlockBuilder.BlockType == CodeBlockType.EncodedExpression /*encode*/); // Use codedom to generate the statement as a string in the target language TextWriter w = new StringWriter(CultureInfo.InvariantCulture); _codeDomProvider.GenerateCodeFromStatement(outputWrite, w, null /*CodeGeneratorOptions*/); string outputWriteString = w.ToString(); // The '+3' is used to make sure the generated code is positioned properly to match // the location of user code (due to the <%= %> separators). outputWriteString = outputWriteString.PadLeft( codeBlockBuilder.Column + codeExpression.Length + 3); // We can then use this string as a snippet statement CodeSnippetStatement lit = new CodeSnippetStatement(outputWriteString); lit.LinePragma = linePragma; method.Statements.Add(lit); } else { // It's a <% ... %> block Debug.Assert(codeBlockBuilder.BlockType == CodeBlockType.Code); // Pad the code block so its generated offset matches the aspx string code = codeBlockBuilder.Content; code = code.PadLeft(code.Length + codeBlockBuilder.Column - 1); CodeSnippetStatement lit = new CodeSnippetStatement(code); lit.LinePragma = linePragma; method.Statements.Add(lit); } } else if (child is CodeStatementBuilder) { if (_designerMode) continue; CodeStatementBuilder statementBuilder = (CodeStatementBuilder)child; CodeStatement statement = statementBuilder.BuildStatement(new CodeArgumentReferenceExpression(renderMethodParameterName)); method.Statements.Add(statement); } else if (child is ControlBuilder) { if (_designerMode) continue; // parameterContainer.Controls['controlIndex++'].RenderControl(output) CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(); CodeExpressionStatement methodCall = new CodeExpressionStatement(methodInvoke); methodInvoke.Method.TargetObject = new CodeIndexerExpression(new CodePropertyReferenceExpression(new CodeArgumentReferenceExpression("parameterContainer"), "Controls"), new CodeExpression[] { new CodePrimitiveExpression(controlIndex++), }); methodInvoke.Method.MethodName = "RenderControl"; // Don't generate a line pragma on the RenderControl call, as it degrades the // debugging experience (VSWhidbey 482416) methodInvoke.Parameters.Add(new CodeArgumentReferenceExpression(renderMethodParameterName)); method.Statements.Add(methodCall); } } } } private bool GenerateSimpleAssignmentAtDesignTime(bool tempObjectVariableDeclared, CodeStatementCollection topMethodStatements, CodeStatementCollection otherMethodStatements, string content, CodeLinePragma linePragma) { // In designer mode, add an object variable used for simplified code expression generation if (!tempObjectVariableDeclared) { tempObjectVariableDeclared = true; // object __o; topMethodStatements.Add(new CodeVariableDeclarationStatement( typeof(object), tempObjectVariable)); } // e.g. __o = ; CodeStatement simpleAssignment = new CodeAssignStatement( new CodeVariableReferenceExpression(tempObjectVariable), new CodeSnippetExpression(content)); simpleAssignment.LinePragma = linePragma; otherMethodStatements.Add(simpleAssignment); return tempObjectVariableDeclared; } protected virtual void BuildSourceDataTreeFromBuilder(ControlBuilder builder, bool fInTemplate, bool topLevelControlInTemplate, PropertyEntry pse) { // Don't do anything for Code blocks if (builder is CodeBlockBuilder || builder is CodeStatementBuilder) return; // Is the current builder for a template? bool fTemplate = (builder is TemplateBuilder); // For the control name in the compiled code, we use the // ID if one is available (but don't use the ID inside a template) // Otherwise, we generate a unique name. if (builder.ID == null || fInTemplate) { // Increase the control count to generate unique ID's _controlCount++; builder.ID = "__control" + _controlCount.ToString(NumberFormatInfo.InvariantInfo); builder.IsGeneratedID = true; } // Process the children if (builder.SubBuilders != null) { foreach (object child in builder.SubBuilders) { if (child is ControlBuilder) { // Do not treat it as top level control in template if the control is at top-level of a file. bool isTopLevelCtrlInTemplate = fTemplate && typeof(Control).IsAssignableFrom(((ControlBuilder)child).ControlType) && !(builder is RootBuilder); BuildSourceDataTreeFromBuilder((ControlBuilder)child, fInTemplate, isTopLevelCtrlInTemplate, null); } } } foreach (TemplatePropertyEntry entry in builder.TemplatePropertyEntries) { bool inTemplate = true; // If the template container does not allow multiple instances, // treat the controls as if not in templates. if (entry.PropertyInfo != null) { inTemplate = entry.IsMultiple; } BuildSourceDataTreeFromBuilder(((TemplatePropertyEntry)entry).Builder, inTemplate, false /*topLevelControlInTemplate*/, entry); } foreach (ComplexPropertyEntry entry in builder.ComplexPropertyEntries) { // Don't create a build method for inner property strings if (!(entry.Builder is StringPropertyBuilder)) { BuildSourceDataTreeFromBuilder(((ComplexPropertyEntry)entry).Builder, fInTemplate, false /*topLevelControlInTemplate*/, entry); } } // Build a field declaration for the control if ID is defined on the control. // (Not a generated ID) if (!builder.IsGeneratedID) BuildFieldDeclaration(builder); CodeMemberMethod buildMethod = null; CodeMemberMethod dataBindingMethod = null; // Skip the rest if we're only generating the intermediate class if (_sourceDataClass != null) { if (!_designerMode) { // Build a Build method for the control buildMethod = BuildBuildMethod(builder, fTemplate, fInTemplate, topLevelControlInTemplate, pse, false); } // Build a Render method for the control, unless it has no code if (builder.HasAspCode) { BuildRenderMethod(builder, fTemplate); } // Build a method to extract values from the template BuildExtractMethod(builder); // Build a property binding method for the control dataBindingMethod = BuildPropertyBindingMethod(builder, false); } // Give the ControlBuilder a chance to look at and modify the tree builder.ProcessGeneratedCode(_codeCompileUnit, _intermediateClass, _sourceDataClass, buildMethod, dataBindingMethod); if (Parser.ControlBuilderInterceptor != null) { Parser.ControlBuilderInterceptor.OnProcessGeneratedCode(builder, _codeCompileUnit, _intermediateClass, _sourceDataClass, buildMethod, dataBindingMethod, builder.AdditionalState); } // Give the ParseRecorder a chance to look at and modify the tree Parser.ParseRecorders.ProcessGeneratedCode(builder, _codeCompileUnit, _intermediateClass, _sourceDataClass, buildMethod, dataBindingMethod); } internal virtual CodeExpression BuildStringPropertyExpression(PropertyEntry pse) { string value = String.Empty; if (pse is SimplePropertyEntry) { value = (string)((SimplePropertyEntry)pse).Value; } else { Debug.Assert(pse is ComplexPropertyEntry); ComplexPropertyEntry cpe = (ComplexPropertyEntry)pse; value = (string)((StringPropertyBuilder)cpe.Builder).BuildObject(); } return CodeDomUtility.GenerateExpressionForValue(pse.PropertyInfo, value, typeof(string)); } protected virtual CodeAssignStatement BuildTemplatePropertyStatement(CodeExpression ctrlRefExpr) { // e.g. __ctrl.TemplateControl = this; CodeAssignStatement assign = new CodeAssignStatement(); assign.Left = new CodePropertyReferenceExpression(ctrlRefExpr, "TemplateControl"); assign.Right = new CodeThisReferenceExpression(); return assign; } /* * Return the name of an extract method */ private string ExtractMethodName(ControlBuilder builder) { return extractTemplateValuesMethodPrefix + builder.ID; } private Type GetCtrlTypeForBuilder(ControlBuilder builder, bool fTemplate) { if (builder is RootBuilder && builder.ControlType != null) return builder.ControlType; if (fTemplate) return typeof(Control); return builder.ControlType; } protected string GetMethodNameForBuilder(string prefix, ControlBuilder builder) { if (builder is RootBuilder) { return prefix + "Tree"; } else { return prefix + builder.ID; } } /* * Helper method to generate the device filter conditionals. e.g. * if (this.TestDeviceFilter("FilterName")) { * // ... * } * else { * // ... * } */ private void HandleDeviceFilterConditional( ref PropertyEntry previous, PropertyEntry current, CodeStatementCollection topStmts, ref CodeStatementCollection currentStmts, out CodeStatementCollection nextStmts) { bool sameAsPrevious = (previous != null) && StringUtil.EqualsIgnoreCase(previous.Name, current.Name); if (current.Filter.Length != 0) { if (!sameAsPrevious) { // If the current property entry is not the same as the previous entries, // we need to start a new block of code currentStmts = topStmts; previous = null; } CodeConditionStatement ifStmt = new CodeConditionStatement(); CodeMethodInvokeExpression methCallExpression = new CodeMethodInvokeExpression( new CodeThisReferenceExpression(), "TestDeviceFilter"); methCallExpression.Parameters.Add(new CodePrimitiveExpression(current.Filter)); ifStmt.Condition = methCallExpression; currentStmts.Add(ifStmt); // The current entry needs to go in the 'if' clause currentStmts = ifStmt.TrueStatements; // The next entry will tentatively go in the 'else' clause, unless it is // for a different property (which we would catch next time around) nextStmts = ifStmt.FalseStatements; previous = current; } else { // If we're switching to a new property, we need to add to the top-level statements (not the false block of an if) if (!sameAsPrevious) { currentStmts = topStmts; } nextStmts = topStmts; previous = null; } } protected virtual bool UseResourceLiteralString(string s) { // If the string is long enough, and the compiler supports it, use a UTF8 resource // string for performance return PageParser.EnableLongStringsAsResources && s.Length >= minLongLiteralStringLength && _codeDomProvider.Supports(GeneratorSupport.Win32Resources); } } }