I'm attempting to implement validation rules into my app using the System.Linq.Dynamic.Core package, but I'm running into weird behavior. My idea is to have the ability to create and manage these validation rules from within the app itself. So, I have a ValidationRule
object in my database defined as:
public class ValidationRule {
public string Description { get; set; }
public string ErrorMessage { get; set; }
public short Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
The NewExpression
and OldExpression
properties contain the expressions I want to pass to the dynamic version of Any()
. The only important parts of the ValidationRule
object are the Name
, ErrorMessage
, NewExpression
, and OldExpression
, so I project them into a DTO:
public class ValidationRuleDto {
public string ErrorMessage { get; set; }
public string Name { get; set; }
public string NewExpression { get; set; }
public string OldExpression { get; set; }
}
This DTO is then passed to a ValidationRuleHandler
to evaluate an old and a new instance of the object being validated with the expressions from the DTO. The ValidationRuleHandler
looks like this:
public static class ValidationRuleHandler {
private static readonly ParsingConfig _parsingConfig = new() {
AreContextKeywordsEnabled = false
};
public static ICollection<string> Validate(
dynamic oldObject,
dynamic newObject,
IEnumerable<ValidationRuleDto> rules) => rules.Select(
_ => {
var oldResult = new[] {
oldObject
}.AsQueryable().Any(_parsingConfig, _.OldExpression);
var newResult = new[] {
newObject
}.AsQueryable().Any(_parsingConfig, _.NewExpression);
Debug.WriteLine($"Old => {oldObject.StageText} => {_.OldExpression} => {oldResult}");
Debug.WriteLine($"New => {newObject.StageText} => {_.NewExpression} => {newResult}");
return !(oldResult && newResult)
? null
: $"[{_.Name}] {_.ErrorMessage}";
}).Where(
_ => _ != null).ToHashSet();
}
For the old and new objects being passed to Validate()
I project the object into an old snapshot (from the database as it is) and new snapshot (from the database but with the changes applied) DTOs. In the Validate()
I add each of the objects to a single item collection and convert them to an IQueryable
so I can use the dynamic Any()
. My thought process here is that the expressions for the rule just need to evaluate to a true/false result, and since Any()
returns true/false if the expression's conditions passed, it seemed the most appropriate way to go.
The problem that I'm running into is that the results I expect are not happening when running the app. For reference, the app is an ASP.NET MVC 5 (5.2.9) app targetting .NET Framework 4.8. However, when using LINQPad (5.46.00) to test the ValidationRuleHandler
the results are correct. For example here's the output of the Debug
statements from the app when it's processing three validation rules that apply to the user:
And here's the LINQPad result for the exact same validation rules and snapshot objects (exact same values):
As you can see, when ValidationRuleHandler
is called from LINQPad it evaluates the expressions correctly, but when processed from the app, it's wrong.
I can't figure out why it's behaving this way. Thinking through it, I can't see a problem, and LINQPad behaves as expected, but the app is not and I don't know what else to do. If anyone has any suggestions, I'd appreciate them. I'm also open to alternative suggestions to accomplish the same goal if anyone has had to deal with something similar before.
So, after many hours of trying different solutions to see where exactly it was failing, nothing worked, until I changed the expression to use Equals()
. Instantly worked after that, and I'm not sure why this: (StageText.Equals("Closed"))
; works and this: (StageText == "Closed")
; doesn't.
I'm still baffled why the original way worked when I tested it in LINQPad but not in the app. @Stef also confirmed it works, so I'm confused. Maybe it has something to do with the snapshot values being pulled from the database, or the validation rule expressions also being pulled from the database?
Anyway, it works with Equals()
so I'm rolling with it.