diff --git a/src-blazor/BlazorAppServer/BlazorAppServer.csproj b/src-blazor/BlazorAppServer/BlazorAppServer.csproj
index 298131d6..02873fea 100644
--- a/src-blazor/BlazorAppServer/BlazorAppServer.csproj
+++ b/src-blazor/BlazorAppServer/BlazorAppServer.csproj
@@ -17,9 +17,7 @@
-
-
diff --git a/src/System.Linq.Dynamic.Core/Compatibility/TypeExtensions.cs b/src/System.Linq.Dynamic.Core/Compatibility/TypeExtensions.cs
index 5cc01f02..b7ce2fbc 100644
--- a/src/System.Linq.Dynamic.Core/Compatibility/TypeExtensions.cs
+++ b/src/System.Linq.Dynamic.Core/Compatibility/TypeExtensions.cs
@@ -1,4 +1,4 @@
-#if NETSTANDARD1_3
+#if NETSTANDARD1_3 || UAP10_0
using System.Linq;
namespace System.Reflection;
diff --git a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs
index 0dc61889..75264a4e 100644
--- a/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs
+++ b/src/System.Linq.Dynamic.Core/DynamicClassFactory.cs
@@ -1,8 +1,9 @@
-#if !(UAP10_0)
+#if !UAP10_0
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Validation;
using System.Reflection;
using System.Reflection.Emit;
@@ -27,12 +28,6 @@ public static class DynamicClassFactory
private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes)!;
-#if UAP10_0 || NETSTANDARD
- private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!;
-#else
- private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
-#endif
-
private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!;
#if UAP10_0 || NETSTANDARD
private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(string)])!;
@@ -419,7 +414,7 @@ private static Type EmitType(IList properties, bool createParam
ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString);
ilgeneratorToString.Emit(OpCodes.Pop);
ilgeneratorToString.Emit(OpCodes.Ldloc_0);
- ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString);
+ ilgeneratorToString.Emit(OpCodes.Callvirt, PredefinedMethodsHelper.ObjectToString);
ilgeneratorToString.Emit(OpCodes.Ret);
EmitEqualityOperators(typeBuilder, equals);
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
index eb8e52f5..b298848f 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
@@ -1840,9 +1840,9 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
case 1:
var method = (MethodInfo)methodBase!;
- if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!))
+ if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !PredefinedMethodsHelper.IsPredefinedMethod(_parsingConfig, method))
{
- throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!));
+ throw ParseError(errorPos, Res.MethodIsInaccessible, id, TypeHelper.GetTypeName(method.DeclaringType!));
}
MethodInfo methodToCall;
diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedMethodsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedMethodsHelper.cs
new file mode 100644
index 00000000..a3b6d85e
--- /dev/null
+++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedMethodsHelper.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Linq.Dynamic.Core.Validation;
+using System.Reflection;
+
+namespace System.Linq.Dynamic.Core.Parser;
+
+internal static class PredefinedMethodsHelper
+{
+ internal static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
+ internal static readonly MethodInfo ObjectInstanceEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Instance | BindingFlags.Public, null, [typeof(object)], null)!;
+ internal static readonly MethodInfo ObjectStaticEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;
+ internal static readonly MethodInfo ObjectStaticReferenceEquals = typeof(object).GetMethod(nameof(ReferenceEquals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;
+
+
+ private static readonly HashSet ObjectToStringAndObjectEquals =
+ [
+ ObjectToString,
+ ObjectInstanceEquals,
+ ObjectStaticEquals,
+ ObjectStaticReferenceEquals
+ ];
+
+ public static bool IsPredefinedMethod(ParsingConfig config, MemberInfo member)
+ {
+ Check.NotNull(config);
+ Check.NotNull(member);
+
+ return config.AllowEqualsAndToStringMethodsOnObject && ObjectToStringAndObjectEquals.Contains(member);
+ }
+}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs
index 66bb53fd..f13ee4ef 100644
--- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs
+++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs
@@ -312,4 +312,11 @@ public IQueryableAnalyzer QueryableAnalyzer
/// Default value is false.
///
public bool RestrictOrderByToPropertyOrField { get; set; }
+
+ ///
+ /// When set to true, the parser will allow the use of the Equals(object obj), Equals(object objA, object objB), ReferenceEquals(object objA, object objB) and ToString() methods on the type.
+ ///
+ /// Default value is false.
+ ///
+ public bool AllowEqualsAndToStringMethodsOnObject { get; set; }
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs
index 2bbb2d7f..d2831c24 100644
--- a/src/System.Linq.Dynamic.Core/Res.cs
+++ b/src/System.Linq.Dynamic.Core/Res.cs
@@ -50,7 +50,7 @@ internal static class Res
public const string InvalidStringLength = "String '{0}' should have at least {1} characters.";
public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments";
public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value";
- public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible";
+ public const string MethodIsInaccessible = "Method '{0}' on type '{1}' is not accessible.";
public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers.";
public const string MissingAsClause = "Expression is missing an 'as' clause";
public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";
diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs
index c020cb94..100603aa 100644
--- a/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs
@@ -281,11 +281,15 @@ public void DynamicClassArray_Issue593_Fails()
isValid.Should().BeFalse(); // This should actually be true, but fails. For solution see Issue593_Solution1 and Issue593_Solution2.
}
- // [SkipIfGitHubActions]
- [Fact(Skip = "867")]
+ [SkipIfGitHubActions]
public void DynamicClassArray_Issue593_Solution1()
{
// Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
var field = new
{
Name = "firstName",
@@ -308,7 +312,7 @@ public void DynamicClassArray_Issue593_Solution1()
var query = dynamicClasses.AsQueryable();
// Act
- var isValid = query.Any("firstName.ToString() eq \"firstValue\"");
+ var isValid = query.Any(config, "firstName.ToString() eq \"firstValue\"");
// Assert
isValid.Should().BeTrue();
diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
index f56283c0..a65012b0 100644
--- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
@@ -1058,13 +1058,23 @@ public void DynamicExpressionParser_ParseLambda_StringLiteral_QuotationMark()
Assert.Equal(expectedRightValue, rightValue);
}
- [Fact(Skip = "867")]
+ [Fact]
public void DynamicExpressionParser_ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression()
{
+ // Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
+ // Act
var expression = DynamicExpressionParser.ParseLambda(
+ config,
typeof(Tuple),
typeof(string),
"it.ToString()");
+
+ // Assert
Assert.Equal(typeof(string), expression.ReturnType);
}
@@ -1147,7 +1157,7 @@ public void DynamicExpressionParser_ParseLambda_CustomMethod_WhenClassDoesNotHav
Action action = () => DynamicExpressionParser.ParseLambda(typeof(CustomClassWithMethod), null, expression);
// Assert
- action.Should().Throw().WithMessage("Methods on type 'CustomClassWithMethod' are not accessible");
+ action.Should().Throw().WithMessage("Method 'GetAge' on type 'CustomClassWithMethod' is not accessible.");
}
// [Fact]
diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs
index 5770152b..9369d143 100644
--- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs
@@ -346,7 +346,8 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT
[Theory]
[InlineData("it.MainCompany.Name != null", "(company.MainCompany.Name != null)")]
[InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")]
- // [InlineData("Company.Equals(null, null)", "Equals(null, null)")] issue 867
+ [InlineData("Company.Equals(null, null)", "Equals(null, null)")]
+ [InlineData("Equals(null)", "company.Equals(null)")]
[InlineData("MainCompany.Name", "company.MainCompany.Name")]
[InlineData("Name", "company.Name")]
[InlineData("company.Name", "company.Name")]
@@ -357,7 +358,8 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expres
var config = new ParsingConfig
{
IsCaseSensitive = true,
- CustomTypeProvider = _dynamicTypeProviderMock.Object
+ CustomTypeProvider = _dynamicTypeProviderMock.Object,
+ AllowEqualsAndToStringMethodsOnObject = true
};
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") };
var sut = new ExpressionParser(parameters, expression, null, config);
diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs
index c4b318e1..eb9edd73 100644
--- a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs
@@ -7,16 +7,93 @@ namespace System.Linq.Dynamic.Core.Tests.Parser;
public class MethodFinderTest
{
- [Fact(Skip = "867")]
- public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual()
+ [Fact]
+ public void Method_ToString_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
{
+ // Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
Expression> expr = x => x.ToString();
-
+
var selector = "ToString()";
var prm = Parameter(typeof(int?));
- var parser = new ExpressionParser([prm], selector, [], ParsingConfig.Default);
- var expr1 = parser.Parse(null);
-
- Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType);
+ var parser = new ExpressionParser([prm], selector, [], config);
+
+ // Act
+ var expression = parser.Parse(null);
+
+ // Assert
+ Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
+ }
+
+ [Fact]
+ public void Method_Equals1_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
+ {
+ // Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
+ Expression> expr = x => x.Equals("a");
+
+ var selector = "Equals(\"a\")";
+ var prm = Parameter(typeof(int?));
+ var parser = new ExpressionParser([prm], selector, [], config);
+
+ // Act
+ var expression = parser.Parse(null);
+
+ // Assert
+ Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
+ }
+
+ [Fact]
+ public void Method_Equals2_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
+ {
+ // Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
+ // ReSharper disable once RedundantNameQualifier
+ Expression> expr = x => object.Equals("a", "b");
+
+ var selector = "object.Equals(\"a\", \"b\")";
+ var prm = Parameter(typeof(int?));
+ var parser = new ExpressionParser([prm], selector, [], config);
+
+ // Act
+ var expression = parser.Parse(null);
+
+ // Assert
+ Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
+ }
+
+ [Fact]
+ public void Method_ReferenceEquals_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
+ {
+ // Arrange
+ var config = new ParsingConfig
+ {
+ AllowEqualsAndToStringMethodsOnObject = true
+ };
+
+ // ReSharper disable once RedundantNameQualifier
+ Expression> expr = x => object.ReferenceEquals("a", "b");
+
+ var selector = "object.ReferenceEquals(\"a\", \"b\")";
+ var prm = Parameter(typeof(int?));
+ var parser = new ExpressionParser([prm], selector, [], config);
+
+ // Act
+ var expression = parser.Parse(null);
+
+ // Assert
+ Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
}
}
\ No newline at end of file
diff --git a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs
index 787124e3..ac95f023 100644
--- a/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs
@@ -31,7 +31,7 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test1()
Action action = () => baseQuery.OrderBy(predicate);
// Assert
- action.Should().Throw().WithMessage("Methods on type 'Object' are not accessible");
+ action.Should().Throw().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
}
[Fact]
@@ -48,19 +48,19 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test2()
);
// Assert
- action.Should().Throw().WithMessage($"Methods on type 'Object' are not accessible");
+ action.Should().Throw().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
}
[Theory]
- [InlineData(typeof(FileStream), "Close()", "Stream")]
- [InlineData(typeof(Assembly), "GetName().Name.ToString()", "Assembly")]
- public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsException(Type itType, string expression, string type)
+ [InlineData(typeof(FileStream), "Close()", "Method 'Close' on type 'Stream' is not accessible.")]
+ [InlineData(typeof(Assembly), "GetName().Name.ToString()", "Method 'GetName' on type 'Assembly' is not accessible.")]
+ public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsException(Type itType, string expression, string errorMessage)
{
// Act
Action action = () => DynamicExpressionParser.ParseLambda(itType, null, expression);
// Assert
- action.Should().Throw().WithMessage($"Methods on type '{type}' are not accessible");
+ action.Should().Throw().WithMessage(errorMessage);
}
[Theory]
@@ -79,7 +79,7 @@ public void UsingSystemReflectionAssembly_ThrowsException(string selector)
Action action = () => queryable.Select(selector);
// Assert
- action.Should().Throw().WithMessage("Methods on type 'Object' are not accessible");
+ action.Should().Throw().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
}
[Theory]