Forráskód Böngészése

[asp.net] Fix for bug #633756. Children processed as properties must be instantiated before using.

When a server-side control processes its children (in the markup) as properties, it needs to instantiate
an object of the property type and assign it to the appropriate property of the control. Failing to do so
will cause markup which references properties to fail in certain cases (for instance when the control's
property type is a class and it is not instantiated by the control's constructor).
Marek Habersack 15 éve
szülő
commit
e5ec9150cb

+ 81 - 38
mcs/class/System.Web/System.Web.Compilation/TemplateControlCompiler.cs

@@ -161,12 +161,35 @@ namespace System.Web.Compilation
 			invoke.Parameters.Add (expr);
 			builder.MethodStatements.Add (AddLinePragma (invoke, builder));
 		}
+
+		CodeStatement CreateControlVariable (Type type, ControlBuilder builder, CodeMemberMethod method, CodeTypeReference ctrlTypeRef)
+		{
+			CodeObjectCreateExpression newExpr = new CodeObjectCreateExpression (ctrlTypeRef);
+
+			object [] atts = type != null ? type.GetCustomAttributes (typeof (ConstructorNeedsTagAttribute), true) : null;
+			if (atts != null && atts.Length > 0) {
+				ConstructorNeedsTagAttribute att = (ConstructorNeedsTagAttribute) atts [0];
+				if (att.NeedsTag)
+					newExpr.Parameters.Add (new CodePrimitiveExpression (builder.TagName));
+			} else if (builder is DataBindingBuilder) {
+				newExpr.Parameters.Add (new CodePrimitiveExpression (0));
+				newExpr.Parameters.Add (new CodePrimitiveExpression (1));
+			}
+
+			method.Statements.Add (new CodeVariableDeclarationStatement (ctrlTypeRef, "__ctrl"));
+			CodeAssignStatement assign = new CodeAssignStatement ();
+			assign.Left = ctrlVar;
+			assign.Right = newExpr;
+
+			return assign;
+		}
 		
 		void InitMethod (ControlBuilder builder, bool isTemplate, bool childrenAsProperties)
 		{
 			currentLocation = builder.Location;
 			bool inBuildControlTree = builder is RootBuilder;
 			string tailname = (inBuildControlTree ? "Tree" : ("_" + builder.ID));
+			bool isProperty = builder.IsProperty;
 			CodeMemberMethod method = new CodeMemberMethod ();
 			builder.Method = method;
 			builder.MethodStatements = method.Statements;
@@ -174,7 +197,7 @@ namespace System.Web.Compilation
 			method.Name = "__BuildControl" + tailname;
 			method.Attributes = MemberAttributes.Private | MemberAttributes.Final;
 			Type type = builder.ControlType;
-
+			
 			/* in the case this is the __BuildControlTree
 			 * method, allow subclasses to insert control
 			 * specific code. */
@@ -199,51 +222,54 @@ namespace System.Web.Compilation
 				mainClass.Members.Add (renderMethod);
 			}
 			
-			if (childrenAsProperties || builder.ControlType == null) {
+			if (childrenAsProperties || type == null) {
 				string typeString;
-				if (builder is RootBuilder)
+				bool isGlobal = true;
+				bool returnsControl;
+
+				if (builder is RootBuilder) {
 					typeString = parser.ClassName;
-				else {
-					if (builder.ControlType != null && builder.IsProperty &&
-					    !typeof (ITemplate).IsAssignableFrom (builder.ControlType))
-						typeString = builder.ControlType.FullName;
-					else 
+					isGlobal = false;
+					returnsControl = false;
+				} else {
+					returnsControl = builder.PropertyBuilderShouldReturnValue;
+					if (type != null && builder.IsProperty && !typeof (ITemplate).IsAssignableFrom (type)) {
+						typeString = type.FullName;
+						isGlobal = !type.IsPrimitive;
+					} else 
 						typeString = "System.Web.UI.Control";
 					ProcessTemplateChildren (builder);
 				}
+				CodeTypeReference ctrlTypeRef = new CodeTypeReference (typeString);
+				if (isGlobal)
+					ctrlTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
+				
+				if (returnsControl) {
+					method.ReturnType = ctrlTypeRef;
 
-				method.Parameters.Add (new CodeParameterDeclarationExpression (typeString, "__ctrl"));
+					// $controlType _ctrl = new $controlType ($parameters);
+					//
+					method.Statements.Add (CreateControlVariable (type, builder, method, ctrlTypeRef));
+				} else
+					method.Parameters.Add (new CodeParameterDeclarationExpression (typeString, "__ctrl"));
 			} else {
+				CodeTypeReference ctrlTypeRef = new CodeTypeReference (type.FullName);
+				if (!type.IsPrimitive)
+					ctrlTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
 				
 				if (typeof (Control).IsAssignableFrom (type))
-					method.ReturnType = new CodeTypeReference (typeof (Control));
+					method.ReturnType = ctrlTypeRef;
 
-				// _ctrl = new $controlType ($parameters);
+				// $controlType _ctrl = new $controlType ($parameters);
 				//
-				CodeObjectCreateExpression newExpr = new CodeObjectCreateExpression (type);
-
-				object [] atts = type.GetCustomAttributes (typeof (ConstructorNeedsTagAttribute), true);
-				if (atts != null && atts.Length > 0) {
-					ConstructorNeedsTagAttribute att = (ConstructorNeedsTagAttribute) atts [0];
-					if (att.NeedsTag)
-						newExpr.Parameters.Add (new CodePrimitiveExpression (builder.TagName));
-				} else if (builder is DataBindingBuilder) {
-					newExpr.Parameters.Add (new CodePrimitiveExpression (0));
-					newExpr.Parameters.Add (new CodePrimitiveExpression (1));
-				}
-
-				method.Statements.Add (new CodeVariableDeclarationStatement (builder.ControlType, "__ctrl"));
-				CodeAssignStatement assign = new CodeAssignStatement ();
-				assign.Left = ctrlVar;
-				assign.Right = newExpr;
-				method.Statements.Add (AddLinePragma (assign, builder));
+				method.Statements.Add (AddLinePragma (CreateControlVariable (type, builder, method, ctrlTypeRef), builder));
 								
 				// this.$builderID = _ctrl;
 				//
 				CodeFieldReferenceExpression builderID = new CodeFieldReferenceExpression ();
 				builderID.TargetObject = thisRef;
 				builderID.FieldName = builder.ID;
-				assign = new CodeAssignStatement ();
+				CodeAssignStatement assign = new CodeAssignStatement ();
 				assign.Left = builderID;
 				assign.Right = ctrlVar;
 				method.Statements.Add (AddLinePragma (assign, builder));
@@ -1154,7 +1180,8 @@ namespace System.Web.Compilation
 		{
 			if (parent == null || child == null)
 				return;
-			
+
+			CodeStatementCollection methodStatements = parent.MethodStatements;
 			CodeMethodReferenceExpression m = new CodeMethodReferenceExpression (thisRef, child.Method.Name);
 			CodeMethodInvokeExpression expr = new CodeMethodInvokeExpression (m);
 
@@ -1192,26 +1219,42 @@ namespace System.Web.Compilation
 				else
 					parms.Add (new CodePrimitiveExpression (null));
 #endif
-				parent.MethodStatements.Add (AddLinePragma (build, parent));
+				methodStatements.Add (AddLinePragma (build, parent));
 				if (parent.HasAspCode)
 					AddRenderControl (parent);
 				return;
 			}
                                 
 			if (child.IsProperty || parent.ChildrenAsProperties) {
-				expr.Parameters.Add (new CodeFieldReferenceExpression (ctrlVar, child.TagName));
-				parent.MethodStatements.Add (AddLinePragma (expr, parent));
+				if (!child.PropertyBuilderShouldReturnValue) {
+					expr.Parameters.Add (new CodeFieldReferenceExpression (ctrlVar, child.TagName));
+					parent.MethodStatements.Add (AddLinePragma (expr, parent));
+				} else {
+					string localVarName = parent.GetNextLocalVariableName ("__ctrl");
+					methodStatements.Add (new CodeVariableDeclarationStatement (child.Method.ReturnType, localVarName));
+					CodeVariableReferenceExpression localVarRef = new CodeVariableReferenceExpression (localVarName);
+					CodeAssignStatement assign = new CodeAssignStatement ();
+					assign.Left = localVarRef;
+					assign.Right = expr;
+					methodStatements.Add (AddLinePragma (assign, parent));
+
+					assign = new CodeAssignStatement ();
+					assign.Left = new CodeFieldReferenceExpression (ctrlVar, child.TagName);
+					assign.Right = localVarRef;
+					methodStatements.Add (AddLinePragma (assign, parent));
+				}
+				
 				return;
 			}
 
-			parent.MethodStatements.Add (AddLinePragma (expr, parent));
+			methodStatements.Add (AddLinePragma (expr, parent));
 			CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, child.ID);
 			if (parent.ControlType == null || typeof (IParserAccessor).IsAssignableFrom (parent.ControlType))
 				AddParsedSubObjectStmt (parent, field);
 			else {
 				CodeMethodInvokeExpression invoke = new CodeMethodInvokeExpression (ctrlVar, "Add");
 				invoke.Parameters.Add (field);
-				parent.MethodStatements.Add (AddLinePragma (invoke, parent));
+				methodStatements.Add (AddLinePragma (invoke, parent));
 			}
 				
 			if (parent.HasAspCode)
@@ -1446,7 +1489,7 @@ namespace System.Web.Compilation
 		protected void CreateControlTree (ControlBuilder builder, bool inTemplate, bool childrenAsProperties)
 		{
 			EnsureID (builder);
-			bool isTemplate = (typeof (TemplateBuilder).IsAssignableFrom (builder.GetType ()));
+			bool isTemplate = builder.IsTemplate;
 			
 			if (!isTemplate && !inTemplate) {
 				CreateField (builder, true);
@@ -1560,8 +1603,8 @@ namespace System.Web.Compilation
 			
 			if ((!isTemplate || builder is RootBuilder) && !String.IsNullOrEmpty (builder.GetAttribute ("meta:resourcekey")))
 				CreateAssignStatementFromAttribute (builder, "meta:resourcekey");
-
-			if (!childrenAsProperties && typeof (Control).IsAssignableFrom (builder.ControlType))
+			
+			if ((childrenAsProperties && builder.PropertyBuilderShouldReturnValue) || (!childrenAsProperties && typeof (Control).IsAssignableFrom (builder.ControlType)))
 				builder.Method.Statements.Add (new CodeMethodReturnStatement (ctrlVar));
 
 			builder.ProcessGeneratedCode (CompileUnit, BaseType, DerivedType, builder.Method, builder.DataBindingMethod);

+ 29 - 1
mcs/class/System.Web/System.Web.UI/ControlBuilder.cs

@@ -80,8 +80,12 @@ namespace System.Web.UI {
 		CodeMemberMethod renderMethod;
 		int renderIndex;
 		bool isProperty;
+		bool isPropertyWritable;
 		ILocation location;
 		ArrayList otherTags;
+
+		int localVariableCount = 0;
+		bool? isTemplate;
 		
 		public ControlBuilder ()
 		{
@@ -169,6 +173,10 @@ namespace System.Web.UI {
 			get { return isProperty; }
 		}
 
+		internal bool IsPropertyWritable {
+			get { return isPropertyWritable; }
+		}
+		
 		internal ILocation Location {
 			get { return location; }
 			set { location = new _Location (value); }
@@ -235,6 +243,19 @@ namespace System.Web.UI {
 				return typeof (INamingContainer).IsAssignableFrom (type);
 			}
 		}
+
+		internal bool IsTemplate {
+			get {
+				if (isTemplate == null)
+					isTemplate = (typeof (TemplateBuilder).IsAssignableFrom (GetType ()));
+
+				return isTemplate.Value;
+			}
+		}
+		
+		internal bool PropertyBuilderShouldReturnValue {
+			get { return isProperty && isPropertyWritable && RenderMethod == null && !IsTemplate && !(this is CollectionBuilder) && !(this is RootBuilder); }
+		}
 		
 		ControlBuilder MyNamingContainer {
 			get {
@@ -537,7 +558,6 @@ namespace System.Web.UI {
 								    int line,
 								    string sourceFileName)
 		{
-
 			Type tagType = MapTagType (type);
 			ControlBuilder builder;
 			object [] atts = tagType.GetCustomAttributes (typeof (ControlBuilderAttribute), true);
@@ -597,6 +617,7 @@ namespace System.Web.UI {
 				builder = CreateBuilderFromType (parser, parentBuilder, propType, prop.Name,
 								 null, atts, line, fileName);
 				builder.isProperty = true;
+				builder.isPropertyWritable = prop.CanWrite;
 				if (idx >= 0)
 					builder.originalTagName = propName;
 				return builder;
@@ -606,6 +627,7 @@ namespace System.Web.UI {
 			builder.fileName = fileName;
 			builder.line = line;
 			builder.isProperty = true;
+			builder.isPropertyWritable = prop.CanWrite;
 			if (idx >= 0)
 				builder.originalTagName = propName;
 			return builder;
@@ -679,6 +701,12 @@ namespace System.Web.UI {
 			return "_bctrl_" + nextID++;
 		}
 
+		internal string GetNextLocalVariableName (string baseName)
+		{
+			localVariableCount++;
+			return baseName + localVariableCount.ToString ();
+		}
+		
 		internal virtual ControlBuilder CreateSubBuilder (string tagid,
 								  IDictionary atts,
 								  Type childType,

+ 1 - 0
mcs/class/System.Web/System.Web_standalone_test.dll.sources

@@ -20,3 +20,4 @@ Test/standalone-tests/GridViewShowHeaderWhenEmpty.cs
 Test/standalone-tests/GridViewSortingStyles.cs
 Test/standalone-tests/SiteMapPathRendering.cs
 Test/standalone-tests/PageParserDefaultTypeProperties.cs
+Test/standalone-tests/ChildrenAsProperties.cs

+ 63 - 0
mcs/class/System.Web/Test/standalone-tests/ChildrenAsProperties.cs

@@ -0,0 +1,63 @@
+//
+// Authors:
+//   Marek Habersack ([email protected])
+//
+// (C) 2010 Novell, Inc http://novell.com/
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Web.Util;
+
+using StandAloneRunnerSupport;
+using StandAloneTests;
+
+using NUnit.Framework;
+
+namespace StandAloneTests.ChildrenAsProperties
+{
+	[TestCase ("ChildrenAsProperties", "PagesSection.ChildrenAsProperties")]
+	public sealed class Test_01 : ITestCase
+	{
+		public string PhysicalPath {
+			get { return Path.Combine (Consts.BasePhysicalDir, "ChildrenAsProperties"); }
+		}
+		
+		public string VirtualPath  {
+			get { return "/"; }
+		}
+
+		public bool SetUp (List <TestRunItem> runItems)
+		{
+			runItems.Add (new TestRunItem ("default.aspx", Default_Aspx));
+			return true;
+		}
+		
+		void Default_Aspx (string result, TestRunItem runItem)
+		{
+			string originalHtml = "<div>12345snap test snap</div>\n<div>123454444</div>\n";
+			Helpers.ExtractAndCompareCodeFromHtml (result, originalHtml, "#A1");
+		}
+	}
+}

+ 21 - 0
mcs/class/System.Web/Test/standalone/ChildrenAsProperties/default.aspx

@@ -0,0 +1,21 @@
+<%@ Page Language="C#" CodeFile="default.aspx.cs" Inherits="testwebemailcontrols.Default" %>
+
+<%@ Register Src="test.ascx" TagName="test" TagPrefix="tester"  %>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head runat="server">
+	<title>Default</title>
+</head>
+<body>
+	<form id="form1" runat="server">
+	<!--START--><%= AppDomain.CurrentDomain.GetData ("BEGIN_CODE_MARKER") %><tester:test id="testertest" runat="server" stringSlam="string">
+		<slam State="4444" Text="snap test snap"></slam>
+		<stringBuilderSlam Length="0"/>
+		<dateTimeSlam/>
+		<intSlam/>
+	</tester:test><%= AppDomain.CurrentDomain.GetData ("END_CODE_MARKER") %><!--END-->
+		<asp:Button id="button1" runat="server" Text="Click me!" OnClick="button1Clicked" />
+	</form>
+</body>
+</html>

+ 14 - 0
mcs/class/System.Web/Test/standalone/ChildrenAsProperties/default.aspx.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Web;
+using System.Web.UI;
+
+namespace testwebemailcontrols
+{
+	public partial class Default : System.Web.UI.Page
+	{
+		public virtual void button1Clicked (object sender, EventArgs args)
+		{
+			button1.Text = "You clicked me";
+		}
+	}
+}

+ 3 - 0
mcs/class/System.Web/Test/standalone/ChildrenAsProperties/test.ascx

@@ -0,0 +1,3 @@
+<%@ Control Language="C#" CodeFile="test.ascx.cs" Inherits="testwebemailcontrols.test" %>
+<div>12345<%= slam.Text %></div>
+<div>12345<%= slam.State %></div>

+ 65 - 0
mcs/class/System.Web/Test/standalone/ChildrenAsProperties/test.ascx.cs

@@ -0,0 +1,65 @@
+
+using System;
+using System.Text;
+using System.Web;
+using System.Web.UI;
+using System.ComponentModel;
+
+namespace testwebemailcontrols
+{
+	public partial class test : System.Web.UI.UserControl
+	{
+        private ctlItem _slam;
+        StringBuilder _stringBuilderSlam;
+        string _stringSlam;
+        int _intSlam;
+        DateTime _dateTimeSlam;
+        
+        [PersistenceModeAttribute(PersistenceMode.InnerProperty)]
+        public ctlItem slam
+        {
+            get { return _slam; }
+            set { _slam = value; }
+        }
+
+	public StringBuilder stringBuilderSlam {
+	    get { return _stringBuilderSlam; }
+	    set { _stringBuilderSlam = value; }
+	}
+    
+	public string stringSlam {
+	    get { return _stringSlam; }
+	    set { _stringSlam = value; }
+	}
+	
+	public int intSlam {
+	    get { return _intSlam; }
+	    set { _intSlam = value; }
+	}
+
+	public DateTime dateTimeSlam {
+	    get { return _dateTimeSlam; }
+	    set { _dateTimeSlam = value; }
+	}
+	
+        public class ctlItem
+        {
+            string _Text = "123";
+            string _state = "345";
+
+            public string State
+            {
+                get { return _state; }
+                set { _state = value; }
+            }
+
+            [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
+            public string Text
+            {
+                get { return _Text; }
+                set { _Text = value; }
+            }
+        }
+	}
+}
+

+ 7 - 0
mcs/class/System.Web/Test/standalone/ChildrenAsProperties/web.config

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+	<system.web>
+		<compilation debug="true" />
+		<customErrors mode="RemoteOnly" />
+	</system.web>
+</configuration>