I'm trying to validate my Unity MonoBehavior
in order to make it more obvious when scripts aren't set up properly. I was aware of FluentValidation from other C# work I've done, so I set up Nuget for Unity and installed it.
The problem is that when a GameObject on a MonoBehavior is not initialized, the expression for FluentValidation throws an error instead of returning null due to Unity's behavior overrides.
I've attempted to fix this by creating an extension method which will catch any exceptions thrown in the expression and return null instead. I'm unfamiliar with building expressions using the Expression class, but I'm unsure what to do. Running this causes the following error to appear in the Editor Console:
InvalidOperationException: No coercion operator is defined between types 'System.Func`2[DialogController,UnityEngine.GameObject]' and 'UnityEngine.GameObject'.
System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow (System.Linq.Expressions.ExpressionType coercionType, System.Linq.Expressions.Expression expression, System.Type convertToType) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Convert (System.Linq.Expressions.Expression expression, System.Type type, System.Reflection.MethodInfo method) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Convert (System.Linq.Expressions.Expression expression, System.Type type) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
ValidationExtensions.SafeRuleFor[T,TProperty] (FluentValidation.AbstractValidator`1[T] validator, System.Linq.Expressions.Expression`1[TDelegate] expression) (at Assets/Scripts/ValidationExtensions.cs:12)
DialogControllerValidation.WhenAsync (System.Func`3[T1,T2,TResult] predicate, System.Action action) (at Assets/Scripts/DialogControllerValidation.cs:10)
DialogController.OnValidate () (at Assets/Scripts/DialogController.cs:170)
Here's my MonoBehavior:
public class DialogController: MonoBehavior {
public GameObject content;
private void OnValidate()
{
new DialogControllerValidation().ValidateAndThrow(this);
}
}
Here's my Validator class:
public class DialogControllerValidation : AbstractValidator<DialogController>
{
public DialogControllerValidation()
{
this.SafeRuleFor(x => x.content).NotNull();
}
}
Here's my extension method:
public static class ValidationExtensions
{
public static IRuleBuilderInitial<T, TProperty> SafeRuleFor<T, TProperty>(this AbstractValidator<T> validator,
Expression<Func<T, TProperty>> expression)
{
var tryExpression = Expression.TryCatch(
Expression.Block(typeof(TProperty), Expression.Convert(expression, typeof(TProperty))),
Expression.Catch(typeof(TProperty), Expression.Constant(null))
);
return validator.RuleFor(Expression.Lambda<Func<T, TProperty>>(
tryExpression,
Expression.Parameter(typeof(T), "t")
)
);
}
}
Edit: If I replace Expression.Convert(expression, typeof(TProperty))
with expression
, i.e.:
public static class ValidationExtensions
{
public static IRuleBuilderInitial<T, TProperty> SafeRuleFor<T, TProperty>(this AbstractValidator<T> validator,
Expression<Func<T, TProperty>> expression)
{
var tryExpression = Expression.TryCatch(
Expression.Block(typeof(TProperty), expression),
Expression.Catch(typeof(TProperty), Expression.Constant(null))
);
return validator.RuleFor(Expression.Lambda<Func<T, TProperty>>(
tryExpression,
Expression.Parameter(typeof(T), "t")
)
);
}
}
Then the error I get is
ArgumentException: Argument types do not match
System.Linq.Expressions.Expression.BlockCore (System.Type type, System.Collections.ObjectModel.ReadOnlyCollection`1[T] variables, System.Collections.ObjectModel.ReadOnlyCollection`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Collections.Generic.IEnumerable`1[T] variables, System.Collections.Generic.IEnumerable`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Collections.Generic.IEnumerable`1[T] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
System.Linq.Expressions.Expression.Block (System.Type type, System.Linq.Expressions.Expression[] expressions) (at <351e49e2a5bf4fd6beabb458ce2255f3>:0)
ValidationExtensions.SafeRuleFor[T,TProperty] (FluentValidation.AbstractValidator`1[T] validator, System.Linq.Expressions.Expression`1[TDelegate] expression) (at Assets/Scripts/ValidationExtensions.cs:12)
DialogControllerValidation.WhenAsync (System.Func`3[T1,T2,TResult] predicate, System.Action action) (at Assets/Scripts/DialogControllerValidation.cs:10)
DialogController.OnValidate () (at Assets/Scripts/DialogController.cs:170)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr, Boolean&)