Search code examples
c#asp.net-mvcasp.net-mvc-2lambda

Creating a typed ModelState.AddModelError()


In a controller I can perform DB lookups etc and add some error message associated with a model property:

public ActionResult CreateJob(CreateJobModel viewModel)
{
    var call = FindCall(viewModel.CallNumber);

    if (call == null)
    {
        ModelState.AddModelError("CallNumber", "Idiot User!");
    }
}

I don't like that CallNumber is a string, when ideally it should refer directly to viewModel.CallNumber, and if I change the name of that property, it should be changed too.

How can I achieve this?

I'd imagine the code would end up something like this, which would take a property access expression:

AddModelFieldError(() => viewModel.CallNumber, "Idiot User!");

But I'm not sure how to create a method like that, or in the case where it's a sub/inner-property that needs the error message.


Solution

  • I would write my own generic extension method:

    public static class ModelStateDictionaryHelper
    {
        public static void AddModelError<TViewModel>(
            this ModelStateDictionary me,
            Expression<Func<TViewModel, object>> lambdaExpression, string error)
        {            
            me.AddModelError(GetPropertyName(lambdaExpression), error);
        }
    
        private static string GetPropertyName(Expression lambdaExpression)
        {
            IList<string> list = new List<string>();
            var e = lambdaExpression;
    
            while (true)
            {
                switch (e.NodeType)
                {
                    case ExpressionType.Lambda:
                        e = ((LambdaExpression)e).Body;
                        break;
                    case ExpressionType.MemberAccess:
                        var propertyInfo = ((MemberExpression)e).Member as PropertyInfo;
                        var prop = propertyInfo != null
                                          ? propertyInfo.Name
                                          : null;
                        list.Add(prop);
    
                        var memberExpression = (MemberExpression)e;
                        if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
                        {
                            var parameter = GetParameterExpression(memberExpression.Expression);
                            if (parameter != null)
                            {
                                e = Expression.Lambda(memberExpression.Expression, parameter);
                                break;
                            }
                        }
                        return string.Join(".", list.Reverse());
                    default:
                        return null;
                }
            }
        }
    
        private static ParameterExpression GetParameterExpression(Expression expression)
        {
            while (expression.NodeType == ExpressionType.MemberAccess)
            {
                expression = ((MemberExpression)expression).Expression;
            }
            return expression.NodeType == ExpressionType.Parameter ? (ParameterExpression)expression : null;
        }
    }
    

    and the usage:

    ModelState.AddModelError<CreateJobModel>(x => x.CallNumber, 
                                                  "some kind attention");
    

    It looks a bit differently from the version you've asked about, but I hope it can be acceptable alternative.