14

I am developing a client-side and server-side validation for a certain viewModel property.

In the .cshtml file I put this:

@Html.DropDownListFor(model => model.EntityType.ParentId, Model.ParentTypeList, "")
@Html.ValidationMessageFor(model => model.EntityType.ParentId)

In the Controller for the business validation

catch (BusinessException e)
{
    ModelState.AddModelError("EntityType.ParentId", Messages.CircularReference);
}

The above works as expected: if an exception is caught, the message appears next to the dropdownlist.

However, I find that this way is not very elegant. In the cshtml, I use a method to generate all the required information about the validation. In the controller, I must know the exact Key string and use it.

Isn't there a better way of doing this?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • This link provides some additional insight and a way of not referencing the key directly as a string but as an expression: http://stackoverflow.com/questions/11090445/how-to-get-a-modelstate-key-of-an-item-in-a-list heres another resource that provide a wrapper method for the Expression key http://stackoverflow.com/questions/8793799/get-reference-to-object-from-c-sharp-expression – Luke Baughan Oct 02 '12 at 10:02

3 Answers3

25

You could write an extension method that will take a lambda expression for the key instead of a string:

public static class ModelStateExtensions
{
    public static void AddModelError<TModel, TProperty>(
        this ModelStateDictionary modelState, 
        Expression<Func<TModel, TProperty>> ex, 
        string message
    )
    {
        var key = ExpressionHelper.GetExpressionText(ex);
        modelState.AddModelError(key, message);
    }
}

and then use this method:

catch (BusinessException e)
{
    ModelState.AddModelError<MyViewModel, int>(
        x => x.EntityType.ParentId, 
        Messages.CircularReference
    );
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Why it's not in the library? – UserControl Jan 30 '14 at 12:05
  • @UserControl, because not everything can be part of the framework. In order for a feature to be part of the framework in needs, to be written, tested and documented. Sometimes you need to make a decision of what features will make it into the release, otherwise you will never be able to release :-) So simply Microsoft didn't find the time to implement, test and document this feature. That's why it's not part of the library. – Darin Dimitrov Jan 30 '14 at 12:08
  • Is there a way to make TModel and TProperty implicit? To avoid having to type out `` – Jason Oct 02 '15 at 19:02
  • @Jason, you don't have to, it's automatically inferred. – Shimmy Weitzhandler Oct 07 '15 at 05:26
  • @Shimmy Could you explain how? For me, the IDE complains it cannot infer the types from the lambda expression alone. And it's right. Without providing at least the TModel type, there's not enough context for any type inference. – Tom Lint Oct 27 '15 at 08:37
  • @TomLint oops you're right. Type inference isn't support on lambda expressions. – Shimmy Weitzhandler Oct 28 '15 at 02:19
  • 1
    you can simplify this with the signature: `public static void AddModelError(this ModelStateDictionary modelState, Expression> ex, string message)` – Spongman Jun 09 '17 at 18:59
4

I follow @Darin Dimitrov solution but i want to avoid <MyViewModel, int> so I used some different way but for that you need MyViewModel object variable.

public static class ModelStateExtensions
{
    public static void AddModelError<TModel, TProperty>(this TModel source,        
                                                    Expression<Func<TModel, TProperty>> ex, 
                                                    string message,
                                                    ModelStateDictionary modelState)
    {
        var key = System.Web.Mvc.ExpressionHelper.GetExpressionText(ex);
        modelState.AddModelError(key, message);
    }
}

How to Use:

catch (BusinessException e)
{
    objMyViewModel.AddModelError(x => x.EntityType.ParentId, 
                                 Messages.CircularReference,
                                 ModelState);
}
Community
  • 1
  • 1
Rikin Patel
  • 8,848
  • 7
  • 70
  • 78
  • 4
    For me, more intuitive would be extension method signature of: `AddModelError(this ModelStateDictionary modelState, TModel model, Expression> ex, string message)`. That way you can use it like `ModelState.AddModelError(model, x => x.Email, "Invalid email"));` – benmccallum Mar 24 '16 at 04:56
-1

You want the validation to happen at both the client and server side and also you looking for an elegant solution then why can try creating a custom ValidationAttribute.

VJAI
  • 32,167
  • 23
  • 102
  • 164