The Problem
After utilizing the ClosedXML library for Excel parsing, my fellow developer, Sean Killeen and I hit a snag:
We were using FluentValidation with custom validators to test records in an Excel worksheet. Except, whenever an error was triggered, instead of returning the validation failures, it was throwing an exception regarding some functionality we weren’t even using; it was throwing about XLColor or some other object irrelevant to what were validating. Weird.
Debugging and dropping a break point in the code showed that everything was working as expected, even up to the return statement! So one morning I declared my intent to solve the bug once and for all.
I realized the issue must be with serializing the response object — if everything is good up until that point, something is going wrong in the serializer. So I got the source to Newtonsoft JSON serializer and started stepping through.
Aha! Obviously when an object gets serialized to JSON, the serializer must recursively go through each child object. It turns out, Newtonsoft checks if the object is already in a collection of objects that are already serialized. This requires it to use the contains method of the collection, which in turn calls equals on every object within.
The way FluentValidation works is that it will return to you the attempted value that failed validation. Upon trying to serialize the cell that failed the validation, it was serializing all the properties of the XLCell, hence why the XLColor object was attempting serialization.
Turns out, ClosedXML doesn’t implement .Equals correctly for most of its classes. That is, if you perform the check on an object of a different type, it throws rather than simply returning false. I confirmed the expected behavior of built-in complex objects in C# should be to return false, not cause a failure. When the serializer would attempt to call contains on the XLColor object it was being compared to objects of other types already in the collection, causing it to throw.
The Solution
So I checked out their repository and started making updates, plus unit test coverage on each class to confirm proper functionality. Unfortunately, my laptop croaked shortly thereafter so I haven’t had a chance to check-in my changes and open source contribution to the project yet. Perhaps when I have a little downtime soon.
Our stop-gap solution was to pass the serializer a filter to ignore the AttemptedValue property of the FluentValidation failure, since we weren’t using it for anything in this specific scenario anyway.
Conclusion
While this was a headache that required delving into several other codebases, it was a good reminder that something as trivial as the implementation of .Equals should be paid attention to by any developer, and that a unit test to cover that scenario is not necessary a trivial case that can be ignored.