diff --git a/src/AutoUI/Core/AutoUIContext.cs b/src/AutoUI/Core/AutoUIContext.cs index 81883d0c9c..8ef7368875 100644 --- a/src/AutoUI/Core/AutoUIContext.cs +++ b/src/AutoUI/Core/AutoUIContext.cs @@ -13,6 +13,7 @@ using DotVVM.Framework.Binding.Expressions; using DotVVM.Framework.Binding.Properties; using DotVVM.Framework.Compilation.ControlTree; +using DotVVM.Framework.Utils; using DotVVM.Framework.ViewModel.Validation; using Microsoft.Extensions.DependencyInjection; @@ -57,9 +58,8 @@ public ValidationAttribute[] GetPropertyValidators(PropertyDisplayMetadata prope return Array.Empty(); return GetPropertyValidators(property.PropertyInfo); } - - public IValueBinding CreateValueBinding(PropertyDisplayMetadata property) + public IStaticValueBinding CreateValueBinding(PropertyDisplayMetadata property) { if (property.ValueBinding is not null) return property.ValueBinding; @@ -68,10 +68,12 @@ public IValueBinding CreateValueBinding(PropertyDisplayMetadata property) throw new ArgumentException("property.PropertyInfo is null => cannot create value binding for this property"); var s = this.BindingService; - return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { property.PropertyInfo, DataContextStack }, () => { + var serverOnly = this.DataContextStack.ServerSideOnly; + return s.Cache.CreateCachedBinding("AutoUI-Value", new object?[] { BoxingUtils.Box(serverOnly), property.PropertyInfo, DataContextStack }, () => { var _this = Expression.Parameter(DataContextStack.DataContextType, "_this").AddParameterAnnotation(new BindingParameterAnnotation(DataContextStack)); var expr = Expression.Property(_this, property.PropertyInfo); - return (IValueBinding)BindingService.CreateBinding(typeof(ValueBindingExpression<>), new object[] { + var bindingType = serverOnly ? typeof(ResourceBindingExpression<>) : typeof(ValueBindingExpression<>); + return (IStaticValueBinding)BindingService.CreateBinding(bindingType, new object[] { DataContextStack, new ParsedExpressionBindingProperty(expr) }); diff --git a/src/AutoUI/Core/Controls/AutoEditor.cs b/src/AutoUI/Core/Controls/AutoEditor.cs index c208fc9a5f..97c613a801 100644 --- a/src/AutoUI/Core/Controls/AutoEditor.cs +++ b/src/AutoUI/Core/Controls/AutoEditor.cs @@ -63,7 +63,7 @@ public sealed record Props /// /// Gets or sets the viewmodel property for which the editor should be generated. /// - public IValueBinding? Property { get; init; } + public IStaticValueBinding? Property { get; init; } /// /// Gets or sets the command that will be triggered when the value in the editor is changed. diff --git a/src/AutoUI/Core/Controls/AutoGridViewColumn.cs b/src/AutoUI/Core/Controls/AutoGridViewColumn.cs index 8df9519525..0e60ccb07b 100644 --- a/src/AutoUI/Core/Controls/AutoGridViewColumn.cs +++ b/src/AutoUI/Core/Controls/AutoGridViewColumn.cs @@ -14,14 +14,14 @@ namespace DotVVM.AutoUI.Controls [ControlMarkupOptions(PrimaryName = "GridViewColumn")] public class AutoGridViewColumn : GridViewColumn { - [MarkupOptions(AllowHardCodedValue = false, Required = true)] - public IValueBinding? Property + [MarkupOptions(AllowHardCodedValue = false, AllowResourceBinding = true, Required = true)] + public IStaticValueBinding? Property { - get { return (IValueBinding?)GetValue(PropertyProperty); } + get { return (IStaticValueBinding?)GetValue(PropertyProperty); } set { SetValue(PropertyProperty, value); } } public static readonly DotvvmProperty PropertyProperty = - DotvvmProperty.Register(nameof(Property)); + DotvvmProperty.Register(nameof(Property)); public static DotvvmCapabilityProperty PropsProperty = @@ -93,7 +93,7 @@ private static GridViewColumn CreateColumn(AutoUIContext context, Props props, M [DotvvmControlCapability] public sealed record Props { - public IValueBinding? Property { get; init; } + public IStaticValueBinding? Property { get; init; } public ValueOrBinding IsEditable { get; init; } = new(true); public ValueOrBinding? HeaderText { get; init; } public ITemplate? HeaderTemplate { get; init; } diff --git a/src/AutoUI/Core/Controls/BootstrapForm.cs b/src/AutoUI/Core/Controls/BootstrapForm.cs index e685d311f5..5511d0f5c9 100644 --- a/src/AutoUI/Core/Controls/BootstrapForm.cs +++ b/src/AutoUI/Core/Controls/BootstrapForm.cs @@ -121,7 +121,6 @@ public DotvvmControl GetContents(FieldProps props) Label? labelElement; HtmlGenericControl controlElement; var formGroup = InitializeFormGroup(property, context, props, out labelElement, out controlElement); - // create the validator InitializeValidation(controlElement, labelElement, property, context); diff --git a/src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs b/src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs index b64e3fea9f..d904da9b5e 100644 --- a/src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs +++ b/src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs @@ -18,7 +18,7 @@ public record PropertyDisplayMetadata public string Name { get; init; } public Type Type { get; init; } - public IValueBinding? ValueBinding { get; init; } + public IStaticValueBinding? ValueBinding { get; init; } public LocalizableString? DisplayName { get; set; } public LocalizableString? Placeholder { get; set; } diff --git a/src/Framework/Framework/Binding/BindingProperties.cs b/src/Framework/Framework/Binding/BindingProperties.cs index 3509284ae4..43bd8d3028 100644 --- a/src/Framework/Framework/Binding/BindingProperties.cs +++ b/src/Framework/Framework/Binding/BindingProperties.cs @@ -234,5 +234,5 @@ public sealed record IsNullOrEmptyBindingExpression(IBinding Binding); /// Contains the same binding as this binding but converted to a string. public sealed record ExpectedAsStringBindingExpression(IBinding Binding); /// Contains references to the .NET properties referenced in the binding. MainProperty is the property on the root node (modulo conversions and simple expressions). - public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding UnwrappedBindingExpression); + public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding? UnwrappedBindingExpression); } diff --git a/src/Framework/Framework/Compilation/Binding/GeneralBindingPropertyResolvers.cs b/src/Framework/Framework/Compilation/Binding/GeneralBindingPropertyResolvers.cs index 3b51eb45c3..12a5e11419 100644 --- a/src/Framework/Framework/Compilation/Binding/GeneralBindingPropertyResolvers.cs +++ b/src/Framework/Framework/Compilation/Binding/GeneralBindingPropertyResolvers.cs @@ -468,7 +468,7 @@ public IsMoreThanZeroBindingProperty IsMoreThanZero(ParsedExpressionBindingPrope )); } - public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProperties(IValueBinding binding, ParsedExpressionBindingProperty expression) + public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProperties(IStaticValueBinding binding, ParsedExpressionBindingProperty expression) { var allProperties = new List(); var expr = expression.Expression; @@ -483,7 +483,7 @@ public ReferencedViewModelPropertiesBindingProperty GetReferencedViewModelProper expr = ExpressionHelper.UnwrapPassthroughOperations(expr); var mainProperty = (expr as MemberExpression)?.Member as PropertyInfo; - var unwrappedBinding = binding.DeriveBinding(expr); + var unwrappedBinding = (binding as IValueBinding)?.DeriveBinding(expr); return new( mainProperty, diff --git a/src/Framework/Framework/Controls/Validator.cs b/src/Framework/Framework/Controls/Validator.cs index a074b58a05..ea7140ee4c 100644 --- a/src/Framework/Framework/Controls/Validator.cs +++ b/src/Framework/Framework/Controls/Validator.cs @@ -7,6 +7,7 @@ using DotVVM.Framework.Configuration; using DotVVM.Framework.Binding.Properties; using System.Text.Json; +using DotVVM.Framework.Utils; namespace DotVVM.Framework.Controls { @@ -90,7 +91,7 @@ private static void AddValidatedValue(IHtmlWriter writer, IDotvvmRequestContext if (binding is not null) { var referencedPropertyExpressions = binding.GetProperty(); - var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression; + var unwrappedPropertyExpression = referencedPropertyExpressions.UnwrappedBindingExpression.NotNull(); // We were able to unwrap the the provided expression writer.AddKnockoutDataBind(validationDataBindName, control, unwrappedPropertyExpression); diff --git a/src/Tests/ControlTests/ResourceDataContextTests.cs b/src/Tests/ControlTests/ResourceDataContextTests.cs index dffc76b6d2..4a7665dfe0 100644 --- a/src/Tests/ControlTests/ResourceDataContextTests.cs +++ b/src/Tests/ControlTests/ResourceDataContextTests.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using DotVVM.Framework.Testing; using DotVVM.Framework.Compilation.Styles; +using DotVVM.AutoUI; namespace DotVVM.Framework.Tests.ControlTests { @@ -20,6 +21,8 @@ public class ResourceDataContextTests static readonly ControlTestHelper cth = new ControlTestHelper(config: config => { _ = Controls.Repeater.RenderAsNamedTemplateProperty; config.Styles.Register().SetProperty(r => r.RenderAsNamedTemplate, false, StyleOverrideOptions.Ignore); + }, services: s => { + s.AddAutoUI(); }); OutputChecker check = new OutputChecker("testoutputs"); @@ -177,6 +180,70 @@ public async Task GridView() check.CheckString(r.FormattedHtml, fileExtension: "html"); } + [TestMethod] + public async Task EmptyData() + { + var r = await cth.RunPage(typeof(TestViewModel), @" + + + + + {{resource: Name}} + + + + + + + + {{resource: Name}} + + + + + + + empty data template + + This would be here if any data was present + + + + + empty data template + + This would be here if any data was present + + + + dot:EmptyData + + + + dot:EmptyData + + + + not shown + + + "); + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + + [TestMethod] + public async Task AutoGrid() + { + var r = await cth.RunPage(typeof(TestViewModel), @" + + + + + + "); + check.CheckString(r.FormattedHtml, fileExtension: "html"); + } + public class TestViewModel: DotvvmViewModelBase { @@ -207,11 +274,15 @@ public class TestViewModel: DotvvmViewModelBase public UploadedFilesCollection Files { get; set; } = new UploadedFilesCollection(); + public GridViewDataSet EmptyDataSet { get; set; } = new(); + public CustomerData[] EmptyArray { get; set; } = []; + public record CustomerData( int Id, [property: Required] string Name, // software for running MLM 😂 + [property: Display(AutoGenerateField = false)] List NextLevelCustomers ); diff --git a/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.AutoGrid.html b/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.AutoGrid.html new file mode 100644 index 0000000000..cdf8145a5a --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.AutoGrid.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + +
+ Id + + Name +
+ 1 + + One +
+ 2 + + Two +
+ + diff --git a/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.EmptyData.html b/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.EmptyData.html new file mode 100644 index 0000000000..0ba865eeff --- /dev/null +++ b/src/Tests/ControlTests/testoutputs/ResourceDataContextTests.EmptyData.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + +
+ Id + + Name +
+ + empty data template + +
+
+ empty data template +
+ dot:EmptyData +
+ dot:EmptyData +
+ + diff --git a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json index bcb06f5f89..5e8af402a2 100644 --- a/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json +++ b/src/Tests/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json @@ -162,7 +162,7 @@ "fromCapability": "Props" }, "Property": { - "type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework", + "type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework", "onlyBindings": true, "fromCapability": "Props" }, @@ -212,7 +212,7 @@ "fromCapability": "Props" }, "Property": { - "type": "DotVVM.Framework.Binding.Expressions.IValueBinding, DotVVM.Framework", + "type": "DotVVM.Framework.Binding.Expressions.IStaticValueBinding, DotVVM.Framework", "required": true, "onlyBindings": true }