Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/AutoUI/Core/AutoUIContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -57,9 +58,8 @@ public ValidationAttribute[] GetPropertyValidators(PropertyDisplayMetadata prope
return Array.Empty<ValidationAttribute>();
return GetPropertyValidators(property.PropertyInfo);
}


public IValueBinding CreateValueBinding(PropertyDisplayMetadata property)
public IStaticValueBinding CreateValueBinding(PropertyDisplayMetadata property)
{
if (property.ValueBinding is not null)
return property.ValueBinding;
Expand All @@ -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)
});
Expand Down
2 changes: 1 addition & 1 deletion src/AutoUI/Core/Controls/AutoEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public sealed record Props
/// <summary>
/// Gets or sets the viewmodel property for which the editor should be generated.
/// </summary>
public IValueBinding? Property { get; init; }
public IStaticValueBinding? Property { get; init; }

/// <summary>
/// Gets or sets the command that will be triggered when the value in the editor is changed.
Expand Down
10 changes: 5 additions & 5 deletions src/AutoUI/Core/Controls/AutoGridViewColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IValueBinding, AutoGridViewColumn>(nameof(Property));
DotvvmProperty.Register<IStaticValueBinding, AutoGridViewColumn>(nameof(Property));


public static DotvvmCapabilityProperty PropsProperty =
Expand Down Expand Up @@ -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<bool> IsEditable { get; init; } = new(true);
public ValueOrBinding<string>? HeaderText { get; init; }
public ITemplate? HeaderTemplate { get; init; }
Expand Down
1 change: 0 additions & 1 deletion src/AutoUI/Core/Controls/BootstrapForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/AutoUI/Core/Metadata/PropertyDisplayMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public override DotvvmControl CreateControl(PropertyDisplayMetadata property, Au
.SetCapability(props.Html)
.SetProperty(c => c.Enabled, props.Enabled)
.SetProperty(c => c.SelectionChanged, props.Changed)
.SetProperty(c => c.SelectedValue, (IValueBinding)context.CreateValueBinding(property));
.SetProperty(c => c.SelectedValue, props.Property);

if (isNullable)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public override bool CanHandleProperty(PropertyDisplayMetadata property, AutoUIC
protected override GridViewColumn CreateColumnCore(PropertyDisplayMetadata property, AutoGridViewColumn.Props props, AutoUIContext context)
{
var column = new GridViewCheckBoxColumn();
column.SetBinding(GridViewCheckBoxColumn.ValueBindingProperty, context.CreateValueBinding(property));
column.SetBinding(GridViewCheckBoxColumn.ValueBindingProperty, props.Property);
return column;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/DynamicData/DynamicData/DataContextStackHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ public static DataContextStack CreateChildStack(this DataContextStack dataContex
dataContextStack.BindingPropertyResolvers);
}
}
}
}
2 changes: 1 addition & 1 deletion src/Framework/Core/Controls/GenericGridViewDataSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class GenericGridViewDataSet<
/// <summary>
/// Gets or sets the items for the current page.
/// </summary>
public IList<T> Items { get; set; } = new List<T>();
public virtual IList<T> Items { get; set; } = new List<T>();

/// <summary>
/// Gets or sets whether the data should be refreshed. This property is set to true automatically
Expand Down
27 changes: 27 additions & 0 deletions src/Framework/Core/Controls/ServerSideGridViewDataSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using DotVVM.Framework.ViewModel;

namespace DotVVM.Framework.Controls
{
/// <summary>
/// Represents a collection of items with paging and sorting which keeps the Items collection server-side (Bind(Direction.None)) and only transfers the necessary metadata (page index, sort direction).
/// </summary>
/// <typeparam name="T">The type of the elements in the collection.</typeparam>
public class ServerSideGridViewDataSet<T>()
: GenericGridViewDataSet<T, NoFilteringOptions, SortingOptions, PagingOptions, NoRowInsertOptions, NoRowEditOptions>(new(), new(), new(), new(), new())
{

[Bind(Direction.None)]
public override IList<T> Items { get => base.Items; set => base.Items = value; }
// return specialized dataset options
public new GridViewDataSetOptions GetOptions()
{
return new GridViewDataSetOptions {
FilteringOptions = FilteringOptions,
SortingOptions = SortingOptions,
PagingOptions = PagingOptions
};
}
}
}
52 changes: 39 additions & 13 deletions src/Framework/Framework/Binding/BindingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,29 @@ internal static (int stepsUp, DotvvmBindableObject target) FindDataContextTarget
var changes = 0;
foreach (var a in control.GetAllAncestors(includingThis: true))
{
if (bindingContext.Equals(a.GetDataContextType(inherit: false)))
var ancestorContext = a.GetDataContextType(inherit: false);
if (bindingContext.Equals(ancestorContext))
return (changes, a);

if (a.properties.Contains(DotvvmBindableObject.DataContextProperty)) changes++;
// count only client-side data contexts (DataContext={resource:} is skipped in JS)
if (a.properties.TryGet(DotvvmBindableObject.DataContextProperty, out var ancestorRuntimeContext))
{
if (a.properties.TryGet(Internal.IsServerOnlyDataContextProperty, out var isServerOnly) && isServerOnly != null)
{
if (isServerOnly is false)
changes++;
}
else
{
// we only count bindings which are not value bindings as client-side
// scalar values (null or otherwise) are used by Repeater to the collection elements
// (since this logic got inlined into many other ItemsControls and is painful to debug, let's not change it)
if (ancestorRuntimeContext is null ||
ancestorRuntimeContext is IValueBinding ||
ancestorRuntimeContext is not IBinding)
changes++;
}
}
}

// try to get the real objects, to see which is wrong
Expand Down Expand Up @@ -361,10 +380,12 @@ public static Func<TParam, TResult> Cache<TParam, TResult>(this Func<TParam, TRe
return f => cache.GetOrAdd(f, func);
}

public static IValueBinding GetThisBinding(this DotvvmBindableObject obj)
public static IStaticValueBinding GetThisBinding(this DotvvmBindableObject obj)
{
var dataContext = obj.GetValueBinding(DotvvmBindableObject.DataContextProperty);
return (IValueBinding)dataContext!.GetProperty<ThisBindingProperty>().binding;
var dataContext = (IStaticValueBinding?)obj.GetBinding(DotvvmBindableObject.DataContextProperty);
if (dataContext is null)
throw new InvalidOperationException("DataContext must be set to a binding to allow creation of a {value: _this} binding");
return (IStaticValueBinding)dataContext!.GetProperty<ThisBindingProperty>().binding;
}

private static readonly ConditionalWeakTable<Expression, BindingParameterAnnotation> _expressionAnnotations =
Expand Down Expand Up @@ -403,7 +424,7 @@ public static TControl SetDataContextTypeFromDataSource<TControl>(this TControl
return dataContextType;
}

var (childType, extensionParameters, addLayer) = ApplyDataContextChange(dataContextType, property.DataContextChangeAttributes, obj, property);
var (childType, extensionParameters, addLayer, serverOnly) = ApplyDataContextChange(dataContextType, property.DataContextChangeAttributes, obj, property);

if (!addLayer)
{
Expand All @@ -412,7 +433,7 @@ public static TControl SetDataContextTypeFromDataSource<TControl>(this TControl
}

if (childType is null) return null; // childType is null in case there is some error in processing (e.g. enumerable was expected).
else return DataContextStack.Create(childType, dataContextType, extensionParameters: extensionParameters.ToArray());
else return DataContextStack.Create(childType, dataContextType, extensionParameters: extensionParameters.ToArray(), serverSideOnly: serverOnly);
}

/// <summary> Return the expected data context type for this property. Returns null if the type is unknown. </summary>
Expand All @@ -432,7 +453,7 @@ public static DataContextStack GetDataContextType(this DotvvmProperty property,
return dataContextType;
}

var (childType, extensionParameters, addLayer) = ApplyDataContextChange(dataContextType, property.DataContextChangeAttributes, obj, property);
var (childType, extensionParameters, addLayer, serverOnly) = ApplyDataContextChange(dataContextType, property.DataContextChangeAttributes, obj, property);

if (!addLayer)
{
Expand All @@ -443,14 +464,16 @@ public static DataContextStack GetDataContextType(this DotvvmProperty property,
if (childType is null)
childType = typeof(UnknownTypeSentinel);

return DataContextStack.Create(childType, dataContextType, extensionParameters: extensionParameters.ToArray());
return DataContextStack.Create(childType, dataContextType, extensionParameters: extensionParameters.ToArray(), serverSideOnly: serverOnly);
}

public static (Type? type, List<BindingExtensionParameter> extensionParameters, bool addLayer) ApplyDataContextChange(DataContextStack dataContext, DataContextChangeAttribute[] attributes, ResolvedControl control, DotvvmProperty? property)
public static (Type? type, List<BindingExtensionParameter> extensionParameters, bool addLayer, bool serverOnly) ApplyDataContextChange(DataContextStack dataContext, DataContextChangeAttribute[] attributes, ResolvedControl control, DotvvmProperty? property)
{
var type = ResolvedTypeDescriptor.Create(dataContext.DataContextType);
var extensionParameters = new List<BindingExtensionParameter>();
var addLayer = false;
var serverOnly = dataContext.ServerSideOnly;

foreach (var attribute in attributes.OrderBy(a => a.Order))
{
if (type == null) break;
Expand All @@ -459,17 +482,19 @@ public static (Type? type, List<BindingExtensionParameter> extensionParameters,
{
addLayer = true;
type = attribute.GetChildDataContextType(type, dataContext, control, property);
serverOnly = attribute.IsServerSideOnly(dataContext, control, property) ?? serverOnly;
}
}
return (ResolvedTypeDescriptor.ToSystemType(type), extensionParameters, addLayer);
return (ResolvedTypeDescriptor.ToSystemType(type), extensionParameters, addLayer, serverOnly);
}


private static (Type? childType, List<BindingExtensionParameter> extensionParameters, bool addLayer) ApplyDataContextChange(DataContextStack dataContextType, DataContextChangeAttribute[] attributes, DotvvmBindableObject obj, DotvvmProperty property)
private static (Type? childType, List<BindingExtensionParameter> extensionParameters, bool addLayer, bool serverOnly) ApplyDataContextChange(DataContextStack dataContextType, DataContextChangeAttribute[] attributes, DotvvmBindableObject obj, DotvvmProperty property)
{
Type? type = dataContextType.DataContextType;
var extensionParameters = new List<BindingExtensionParameter>();
var addLayer = false;
var serverOnly = dataContextType.ServerSideOnly;

foreach (var attribute in attributes.OrderBy(a => a.Order))
{
Expand All @@ -479,10 +504,11 @@ private static (Type? childType, List<BindingExtensionParameter> extensionParame
{
addLayer = true;
type = attribute.GetChildDataContextType(type, dataContextType, obj, property);
serverOnly = attribute.IsServerSideOnly(dataContextType, obj, property) ?? serverOnly;
}
}

return (type, extensionParameters, addLayer);
return (type, extensionParameters, addLayer, serverOnly);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Binding/BindingProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,5 @@ public sealed record IsNullOrEmptyBindingExpression(IBinding Binding);
/// <summary> Contains the same binding as this binding but converted to a string. </summary>
public sealed record ExpectedAsStringBindingExpression(IBinding Binding);
/// <summary> Contains references to the .NET properties referenced in the binding. MainProperty is the property on the root node (modulo conversions and simple expressions). </summary>
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding UnwrappedBindingExpression);
public sealed record ReferencedViewModelPropertiesBindingProperty(PropertyInfo? MainProperty, PropertyInfo[] OtherProperties, IValueBinding? UnwrappedBindingExpression);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ static string GetMessage(IBinding binding, Type[] properties, string? message, E

var introMsg =
isRequiredProperty ?
$"Could not initialize binding as it is missing a required property {properties[0].Name}" :
$"Could not initialize binding '{binding}' as it is missing a required property {properties[0].Name}" :
$"Unable to get property {properties[0].Name}";

var pathMsg = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ public class ConstantDataContextChangeAttribute : DataContextChangeAttribute
public Type Type { get; }

public override int Order { get; }
public bool? ServerSideOnly { get; }

public ConstantDataContextChangeAttribute(Type type, int order = 0)
public ConstantDataContextChangeAttribute(Type type, int order = 0, bool? serverSideOnly = null)
{
Type = type;
Order = order;
ServerSideOnly = serverSideOnly;
}

public override ITypeDescriptor? GetChildDataContextType(ITypeDescriptor dataContext, IDataContextStack controlContextStack, IAbstractControl control, IPropertyDescriptor? property = null)
Expand All @@ -31,5 +33,10 @@ public ConstantDataContextChangeAttribute(Type type, int order = 0)
{
return Type;
}

public override bool? IsServerSideOnly(IDataContextStack controlContextStack, IAbstractControl control, IPropertyDescriptor? property = null) =>
ServerSideOnly;
public override bool? IsServerSideOnly(DataContextStack controlContextStack, DotvvmBindableObject control, DotvvmProperty? property = null) =>
ServerSideOnly;
}
}
Loading
Loading