Skip to content

Commit

Permalink
Improve the error message when ExtensionParameters are executed in in…
Browse files Browse the repository at this point in the history
…valid contexts

When the data context stack is shallower than expected,
it might receive null control.
  • Loading branch information
exyi committed Oct 27, 2024
1 parent 37d6a89 commit 7e9239e
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 9 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
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

0 comments on commit 7e9239e

Please sign in to comment.