Skip to content

Commit a5be62b

Browse files
authored
Merge pull request #1879 from riganti/fix/GridView-FormatString-resource-binding
Fix resource bindings in GridViewColumn.FormatString
2 parents a546262 + 7e9239e commit a5be62b

File tree

7 files changed

+118
-13
lines changed

7 files changed

+118
-13
lines changed

src/Framework/Framework/Compilation/ControlTree/BindingExtensionParameter.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public class CurrentCollectionIndexExtensionParameter : BindingExtensionParamete
7777
}
7878

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

145+
private static MethodInfo ResolveStaticCommandServiceMethod = typeof(InjectedServiceExtensionParameter).GetMethod(nameof(ResolveStaticCommandService), BindingFlags.NonPublic | BindingFlags.Static)!;
146+
144147
public override Expression GetServerEquivalent(Expression controlParameter)
145148
{
146149
var type = ResolvedTypeDescriptor.ToSystemType(this.ParameterType);
147-
var expr = ExpressionUtils.Replace((DotvvmBindableObject c) => ResolveStaticCommandService(c, type), controlParameter);
148-
return Expression.Convert(expr, type);
150+
return Expression.Call(
151+
ResolveStaticCommandServiceMethod.MakeGenericMethod(type),
152+
controlParameter
153+
);
149154
}
150155

151-
private object ResolveStaticCommandService(DotvvmBindableObject c, Type type)
156+
private static T ResolveStaticCommandService<T>(DotvvmBindableObject control)
152157
{
153-
var context = (IDotvvmRequestContext)c.GetValue(Internal.RequestContextProperty, true).NotNull();
158+
if (control is null)
159+
throw new ArgumentNullException(nameof(control), "control is null, is the binding executed in the right data context?");
160+
var context = (IDotvvmRequestContext)control.GetValue(Internal.RequestContextProperty, true)
161+
.NotNull("Current control does not not have the Internal.RequestContextProperty property");
154162
#pragma warning disable CS0618
155-
return context.Services.GetRequiredService<IStaticCommandServiceLoader>().GetStaticCommandService(type, context);
163+
return (T)context.Services.GetRequiredService<IStaticCommandServiceLoader>().GetStaticCommandService(typeof(T), context);
156164
#pragma warning restore CS0618
157165
}
158166

@@ -229,7 +237,7 @@ public override Expression GetServerEquivalent(Expression controlParameter)
229237

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

src/Framework/Framework/Controls/DotvvmBindableObject.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,5 +447,14 @@ protected internal void CopyProperty(DotvvmProperty sourceProperty, DotvvmBindab
447447
throw new DotvvmControlException(this, $"Value of {sourceProperty.FullName} couldn't be copied to targetProperty: {targetProperty.FullName}, because {sourceProperty.FullName} is not set.");
448448
}
449449
}
450+
451+
// TODO: make public in next major version
452+
internal void CopyPropertyRaw(DotvvmProperty sourceProperty, DotvvmBindableObject target, DotvvmProperty targetProperty)
453+
{
454+
if (IsPropertySet(sourceProperty))
455+
{
456+
target.SetValueRaw(targetProperty, GetValueRaw(sourceProperty));
457+
}
458+
}
450459
}
451460
}

src/Framework/Framework/Controls/GridViewTextColumn.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ public static readonly DotvvmProperty ValidatorPlacementProperty
7777
public override void CreateControls(IDotvvmRequestContext context, DotvvmControl container)
7878
{
7979
var literal = new Literal();
80-
literal.FormatString = FormatString;
8180

82-
CopyProperty(UITests.NameProperty, literal, UITests.NameProperty);
81+
CopyPropertyRaw(FormatStringProperty, literal, Literal.FormatStringProperty);
82+
CopyPropertyRaw(UITests.NameProperty, literal, UITests.NameProperty);
8383

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

9797
var textBox = new TextBox();
98-
textBox.FormatString = FormatString;
9998

99+
CopyPropertyRaw(FormatStringProperty, textBox, TextBox.FormatStringProperty);
100100
textBox.SetBinding(TextBox.TextProperty, ValueBinding);
101-
textBox.SetBinding(TextBox.ChangedProperty, ChangedBinding);
101+
CopyPropertyRaw(ChangedBindingProperty, textBox, TextBox.ChangedProperty);
102102
Validator.Place(textBox, container.Children, ValueBinding, ValidatorPlacement);
103103
container.Children.Add(textBox);
104104
}

src/Framework/Framework/Utils/FunctionalExtensions.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Linq;
55
using System.Reflection;
6+
using System.Runtime.CompilerServices;
67
using System.Text;
78
using System.Threading.Tasks;
89

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

104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
103105
public static T NotNull<T>([NotNull] this T? target, string message = "Unexpected null value.")
104-
where T : class =>
105-
target ?? throw new Exception(message);
106+
where T : class
107+
{
108+
if (target is null)
109+
NotNullThrowHelper(message);
110+
return target;
111+
}
112+
113+
[MethodImpl(MethodImplOptions.NoInlining)]
114+
[DoesNotReturn]
115+
private static void NotNullThrowHelper(string message) =>
116+
throw new Exception(message);
106117

107118
public static SortedDictionary<K, V> ToSorted<K, V>(this IDictionary<K, V> d, IComparer<K>? c = null)
108119
where K: notnull =>

src/Tests/Binding/BindingCompilationTests.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,28 @@ public void Error_DifferentDataContext()
12071207
);
12081208
}
12091209

1210+
[TestMethod]
1211+
public void Error_MissingDataContext_ExtensionParameter()
1212+
{
1213+
var type = DataContextStack.Create(typeof(string), parent: DataContextStack.Create(typeof(TestViewModel), extensionParameters: [ new InjectedServiceExtensionParameter("config", ResolvedTypeDescriptor.Create(typeof(DotvvmConfiguration)))]));
1214+
var control = new PlaceHolder();
1215+
var context = DotvvmTestHelper.CreateContext();
1216+
control.SetDataContextType(type.Parent);
1217+
control.DataContext = new TestViewModel();
1218+
control.SetValue(Internal.RequestContextProperty, context);
1219+
1220+
var nested = new PlaceHolder();
1221+
control.Children.Add(nested);
1222+
1223+
var exception = XAssert.ThrowsAny<Exception>(() => ExecuteBinding("config.ApplicationPhysicalPath", type, nested));
1224+
XAssert.Contains("data context", exception.Message);
1225+
1226+
// check that the error goes away when the data context is set properly
1227+
nested.SetDataContextType(type);
1228+
nested.DataContext = "test";
1229+
Assert.AreEqual(".", ExecuteBinding("config.ApplicationPhysicalPath", type, nested));
1230+
}
1231+
12101232
[TestMethod]
12111233
public void NullableIntAssignment()
12121234
{

src/Tests/ControlTests/GridViewTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,24 @@ public async Task SortedChangedStaticCommand()
169169
check.CheckString(r.FormattedHtml, fileExtension: "html");
170170
}
171171

172+
[TestMethod]
173+
public async Task GridViewColumn_FormatString_ResourceBinding()
174+
{
175+
var r = await cth.RunPage(typeof(BasicTestViewModel), """
176+
<dot:GridView DataSource={value: Customers} RenderSettings.Mode=Server InlineEditing=true>
177+
<dot:GridViewTextColumn HeaderText="global format" ValueBinding={value: Id} FormatString={resource: _root.FormatString} />
178+
<dot:GridViewTextColumn HeaderText="local format" ValueBinding={value: Id} FormatString={resource: Enabled ? "0" : "000000"} />
179+
</dot:GridView>
180+
""");
181+
check.CheckString(r.FormattedHtml, fileExtension: "html");
182+
}
172183
public class BasicTestViewModel: DotvvmViewModelBase
173184
{
174185
[Bind(Name = "int")]
175186
public int Integer { get; set; } = 10000000;
176187

188+
public string FormatString { get; set; } = "00.00";
189+
177190
public GridViewDataSet<CustomerData> Customers { get; set; } = new GridViewDataSet<CustomerData>() {
178191
RowEditOptions = {
179192
EditRowId = 1,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<html>
2+
<head></head>
3+
<body>
4+
5+
<!-- ko if: Customers()?.Items()?.length -->
6+
<table data-bind="dotvvm-gridviewdataset: { dataSet: Customers(), mapping: {} }">
7+
<thead>
8+
<tr>
9+
<th>
10+
<span>global format</span>
11+
</th>
12+
<th>
13+
<span>local format</span>
14+
</th>
15+
</tr>
16+
</thead>
17+
<tbody data-bind="dotvvm-SSR-foreach: {data:Customers()?.Items}">
18+
<!-- ko dotvvm-SSR-item: 0 -->
19+
<tr>
20+
<td>
21+
<input data-bind="dotvvm-textbox-text: Id" data-dotvvm-format="00.00" data-dotvvm-value-type="number" type="text">
22+
</td>
23+
<td>
24+
<input data-bind="dotvvm-textbox-text: Id" data-dotvvm-format="000000" data-dotvvm-value-type="number" type="text">
25+
</td>
26+
</tr>
27+
<!-- /ko -->
28+
<!-- ko dotvvm-SSR-item: 1 -->
29+
<tr>
30+
<td>
31+
<span data-bind="text: dotvvm.globalize.formatString(&quot;00.00&quot;, Id, &quot;int32&quot;)">02.00</span>
32+
</td>
33+
<td>
34+
<span data-bind="text: dotvvm.globalize.formatString(&quot;000000&quot;, Id, &quot;int32&quot;)">000002</span>
35+
</td>
36+
</tr>
37+
<!-- /ko -->
38+
</tbody>
39+
</table>
40+
<!-- /ko -->
41+
</body>
42+
</html>

0 commit comments

Comments
 (0)