Skip to content

Commit

Permalink
Merge pull request #1879 from riganti/fix/GridView-FormatString-resou…
Browse files Browse the repository at this point in the history
…rce-binding

Fix resource bindings in GridViewColumn.FormatString
  • Loading branch information
tomasherceg authored Nov 3, 2024
2 parents a546262 + 7e9239e commit a5be62b
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public class CurrentCollectionIndexExtensionParameter : BindingExtensionParamete
}

internal static int GetIndex(DotvvmBindableObject c) =>
(c.GetAllAncestors(true, false)
(c.NotNull("control is null, is the binding executed in the right data context?")
.GetAllAncestors(true, false)
.OfType<DataItemContainer>()
.FirstOrDefault() ?? throw new DotvvmControlException(c, "Could not find ancestor DataItemContainer that stores the current collection index."))
.DataItemIndex ?? throw new DotvvmControlException(c, "Nearest DataItemContainer does have the collection index specified.");
Expand Down Expand Up @@ -141,18 +142,25 @@ public class InjectedServiceExtensionParameter : BindingExtensionParameter
public InjectedServiceExtensionParameter(string identifier, ITypeDescriptor type)
: base(identifier, type, inherit: true) { }

private static MethodInfo ResolveStaticCommandServiceMethod = typeof(InjectedServiceExtensionParameter).GetMethod(nameof(ResolveStaticCommandService), BindingFlags.NonPublic | BindingFlags.Static)!;

public override Expression GetServerEquivalent(Expression controlParameter)
{
var type = ResolvedTypeDescriptor.ToSystemType(this.ParameterType);
var expr = ExpressionUtils.Replace((DotvvmBindableObject c) => ResolveStaticCommandService(c, type), controlParameter);
return Expression.Convert(expr, type);
return Expression.Call(
ResolveStaticCommandServiceMethod.MakeGenericMethod(type),
controlParameter
);
}

private object ResolveStaticCommandService(DotvvmBindableObject c, Type type)
private static T ResolveStaticCommandService<T>(DotvvmBindableObject control)
{
var context = (IDotvvmRequestContext)c.GetValue(Internal.RequestContextProperty, true).NotNull();
if (control is null)
throw new ArgumentNullException(nameof(control), "control is null, is the binding executed in the right data context?");
var context = (IDotvvmRequestContext)control.GetValue(Internal.RequestContextProperty, true)
.NotNull("Current control does not not have the Internal.RequestContextProperty property");
#pragma warning disable CS0618
return context.Services.GetRequiredService<IStaticCommandServiceLoader>().GetStaticCommandService(type, context);
return (T)context.Services.GetRequiredService<IStaticCommandServiceLoader>().GetStaticCommandService(typeof(T), context);
#pragma warning restore CS0618
}

Expand Down Expand Up @@ -229,7 +237,7 @@ public override Expression GetServerEquivalent(Expression controlParameter)

internal static ClaimsPrincipal? GetUser(DotvvmBindableObject control)
{
var context = control.GetValue(Internal.RequestContextProperty) as IDotvvmRequestContext;
var context = control.NotNull("control is null, is the binding executed in the right data context?").GetValue(Internal.RequestContextProperty) as IDotvvmRequestContext;
return context?.HttpContext?.User ?? new ClaimsPrincipal();
}

Expand Down
9 changes: 9 additions & 0 deletions src/Framework/Framework/Controls/DotvvmBindableObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -447,5 +447,14 @@ protected internal void CopyProperty(DotvvmProperty sourceProperty, DotvvmBindab
throw new DotvvmControlException(this, $"Value of {sourceProperty.FullName} couldn't be copied to targetProperty: {targetProperty.FullName}, because {sourceProperty.FullName} is not set.");
}
}

// TODO: make public in next major version
internal void CopyPropertyRaw(DotvvmProperty sourceProperty, DotvvmBindableObject target, DotvvmProperty targetProperty)
{
if (IsPropertySet(sourceProperty))
{
target.SetValueRaw(targetProperty, GetValueRaw(sourceProperty));
}
}
}
}
8 changes: 4 additions & 4 deletions src/Framework/Framework/Controls/GridViewTextColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ public static readonly DotvvmProperty ValidatorPlacementProperty
public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container)
{
var literal = new Literal();
literal.FormatString = FormatString;

CopyProperty(UITests.NameProperty, literal, UITests.NameProperty);
CopyPropertyRaw(FormatStringProperty, literal, Literal.FormatStringProperty);
CopyPropertyRaw(UITests.NameProperty, literal, UITests.NameProperty);

literal.SetBinding(Literal.TextProperty, ValueBinding);
Validator.Place(literal, container.Children, ValueBinding, ValidatorPlacement);
Expand All @@ -95,10 +95,10 @@ public override void CreateEditControls(IDotvvmRequestContext context, DotvvmCon
}

var textBox = new TextBox();
textBox.FormatString = FormatString;

CopyPropertyRaw(FormatStringProperty, textBox, TextBox.FormatStringProperty);
textBox.SetBinding(TextBox.TextProperty, ValueBinding);
textBox.SetBinding(TextBox.ChangedProperty, ChangedBinding);
CopyPropertyRaw(ChangedBindingProperty, textBox, TextBox.ChangedProperty);
Validator.Place(textBox, container.Children, ValueBinding, ValidatorPlacement);
container.Children.Add(textBox);
}
Expand Down
15 changes: 13 additions & 2 deletions src/Framework/Framework/Utils/FunctionalExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

Expand Down Expand Up @@ -100,9 +101,19 @@ public static int FindIndex<T>(this IEnumerable<T> enumerable, Func<T, bool> pre
return -1;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T NotNull<T>([NotNull] this T? target, string message = "Unexpected null value.")
where T : class =>
target ?? throw new Exception(message);
where T : class
{
if (target is null)
NotNullThrowHelper(message);
return target;
}

[MethodImpl(MethodImplOptions.NoInlining)]
[DoesNotReturn]
private static void NotNullThrowHelper(string message) =>
throw new Exception(message);

public static SortedDictionary<K, V> ToSorted<K, V>(this IDictionary<K, V> d, IComparer<K>? c = null)
where K: notnull =>
Expand Down
22 changes: 22 additions & 0 deletions src/Tests/Binding/BindingCompilationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,28 @@ public void Error_DifferentDataContext()
);
}

[TestMethod]
public void Error_MissingDataContext_ExtensionParameter()
{
var type = DataContextStack.Create(typeof(string), parent: DataContextStack.Create(typeof(TestViewModel), extensionParameters: [ new InjectedServiceExtensionParameter("config", ResolvedTypeDescriptor.Create(typeof(DotvvmConfiguration)))]));
var control = new PlaceHolder();
var context = DotvvmTestHelper.CreateContext();
control.SetDataContextType(type.Parent);
control.DataContext = new TestViewModel();
control.SetValue(Internal.RequestContextProperty, context);

var nested = new PlaceHolder();
control.Children.Add(nested);

var exception = XAssert.ThrowsAny<Exception>(() => ExecuteBinding("config.ApplicationPhysicalPath", type, nested));
XAssert.Contains("data context", exception.Message);

// check that the error goes away when the data context is set properly
nested.SetDataContextType(type);
nested.DataContext = "test";
Assert.AreEqual(".", ExecuteBinding("config.ApplicationPhysicalPath", type, nested));
}

[TestMethod]
public void NullableIntAssignment()
{
Expand Down
13 changes: 13 additions & 0 deletions src/Tests/ControlTests/GridViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,24 @@ public async Task SortedChangedStaticCommand()
check.CheckString(r.FormattedHtml, fileExtension: "html");
}

[TestMethod]
public async Task GridViewColumn_FormatString_ResourceBinding()
{
var r = await cth.RunPage(typeof(BasicTestViewModel), """
<dot:GridView DataSource={value: Customers} RenderSettings.Mode=Server InlineEditing=true>
<dot:GridViewTextColumn HeaderText="global format" ValueBinding={value: Id} FormatString={resource: _root.FormatString} />
<dot:GridViewTextColumn HeaderText="local format" ValueBinding={value: Id} FormatString={resource: Enabled ? "0" : "000000"} />
</dot:GridView>
""");
check.CheckString(r.FormattedHtml, fileExtension: "html");
}
public class BasicTestViewModel: DotvvmViewModelBase
{
[Bind(Name = "int")]
public int Integer { get; set; } = 10000000;

public string FormatString { get; set; } = "00.00";

public GridViewDataSet<CustomerData> Customers { get; set; } = new GridViewDataSet<CustomerData>() {
RowEditOptions = {
EditRowId = 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<html>
<head></head>
<body>

<!-- ko if: Customers()?.Items()?.length -->
<table data-bind="dotvvm-gridviewdataset: { dataSet: Customers(), mapping: {} }">
<thead>
<tr>
<th>
<span>global format</span>
</th>
<th>
<span>local format</span>
</th>
</tr>
</thead>
<tbody data-bind="dotvvm-SSR-foreach: {data:Customers()?.Items}">
<!-- ko dotvvm-SSR-item: 0 -->
<tr>
<td>
<input data-bind="dotvvm-textbox-text: Id" data-dotvvm-format="00.00" data-dotvvm-value-type="number" type="text">
</td>
<td>
<input data-bind="dotvvm-textbox-text: Id" data-dotvvm-format="000000" data-dotvvm-value-type="number" type="text">
</td>
</tr>
<!-- /ko -->
<!-- ko dotvvm-SSR-item: 1 -->
<tr>
<td>
<span data-bind="text: dotvvm.globalize.formatString(&quot;00.00&quot;, Id, &quot;int32&quot;)">02.00</span>
</td>
<td>
<span data-bind="text: dotvvm.globalize.formatString(&quot;000000&quot;, Id, &quot;int32&quot;)">000002</span>
</td>
</tr>
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
</body>
</html>

0 comments on commit a5be62b

Please sign in to comment.