diff --git a/src/Framework/Framework/Controls/CompositeControl.cs b/src/Framework/Framework/Controls/CompositeControl.cs index fd8e7664df..cf8b774555 100644 --- a/src/Framework/Framework/Controls/CompositeControl.cs +++ b/src/Framework/Framework/Controls/CompositeControl.cs @@ -85,7 +85,18 @@ Func compileGetter(IControlAttr DefaultControlResolver.InitType(controlType.BaseType.NotNull()); - var method = controlType.GetMethod("GetContents", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + MethodInfo? method; + try + { + method = controlType.GetMethod("GetContents", BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + } + catch (AmbiguousMatchException ex) + { + var allMethods = controlType.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy).Where(m => m.Name == "GetContents").ToArray(); + + var message = $"Could not initialize control {controlType.FullName}, multiple GetContents methods are present: {allMethods.Take(3).Select(m => ReflectionUtils.FormatMethodInfo(m, stripNamespace: true)).StringJoin(", ")}{(allMethods.Length > 3 ? ", ..." : "")}"; + throw new Exception(message, ex); + } if (method == null) throw new Exception($"Could not initialize control {controlType.FullName}, could not find (single) GetContents method"); if (!(typeof(DotvvmControl).IsAssignableFrom(method.ReturnType) || typeof(IEnumerable).IsAssignableFrom(method.ReturnType))) diff --git a/src/Tests/ControlTests/CompositeControlTests.cs b/src/Tests/ControlTests/CompositeControlTests.cs index 96b281bbe1..d963d7a96b 100644 --- a/src/Tests/ControlTests/CompositeControlTests.cs +++ b/src/Tests/ControlTests/CompositeControlTests.cs @@ -17,6 +17,7 @@ using DotVVM.Framework.ViewModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using DotVVM.Framework.Hosting; +using System.ComponentModel; namespace DotVVM.Framework.Tests.ControlTests { @@ -322,6 +323,18 @@ public async Task MenuRepeater() check.CheckString(r.FormattedHtml, fileExtension: "html"); } + [TestMethod] + public async Task CompositeControlInheritance() + { + var r = await cth.RunPage(typeof(BasicTestViewModel), """ + + + """ + ); + + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + public class BasicTestViewModel: DotvvmViewModelBase { [Bind(Name = "int")] @@ -689,4 +702,35 @@ public static DotvvmControl GetContents(ValueOrBinding testStatu return icon; } } + + public class TestBaseCompositeControl: CompositeControl + { + public static DotvvmControl GetContents( + ValueOrBinding text, + [DefaultValue(1)] + ValueOrBinding number + ) + { + return new HtmlGenericControl("div") { + Children = { + new Literal(text), new Literal(number) + } + }; + } + } + + public class TestDerivedCompositeControl: TestBaseCompositeControl + { + public static DotvvmControl GetContents( + ValueOrBinding text, + ValueOrBinding anotherNumber // declare another property, type of existing property cannot be changed + ) + { + return new HtmlGenericControl("p") { + Children = { + new Literal(text), new Literal(anotherNumber) + } + }; + } + } } diff --git a/src/Tests/ControlTests/testoutputs/CompositeControlTests.CompositeControlInheritance.html b/src/Tests/ControlTests/testoutputs/CompositeControlTests.CompositeControlInheritance.html new file mode 100644 index 0000000000..7ae651e073 --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/CompositeControlTests.CompositeControlInheritance.html @@ -0,0 +1,7 @@ + + + +
Text1
+

SecondText1.1

+ +