views:

202

answers:

4

Hello,

How do I disable Model validation for a single Action in a Controller ? Or can I do it per model by registering the model type at startup somewhere ?

I want the ModelBinder to bind to the model, but afterwards it should not perform the model validation.

The reason why i dont want validation to happen is because i am trying to move the logic from the controllers to a service layer which will be responsible for validating the models as i cant assume that models being passed to a service contains valid data.

As I understand this is the recommend approach (to not have logic in the controllers), so I find it a bit strange that i cant seem to find anything about how the model validation can be disabled (per action or per model type).

Please notice that I dont want to disable model validation for the entire webapplication (by removing the validation providers), and i dont want to disable the input validation that checks for malicious code being posted.


UPDATE

I am using .Net 4.0 and MVC 3 Preview 1

A: 

I use [ ValidateInput( false )]

Not sure if this prevents model validation, or only IIS submit validation.

Bob Housedorf
It doesnt prevent the model from being validated. As far as i know it is used to disable the check for malicious input (like html tags or javascript etc.)
MartinF
+1  A: 

I would recommend you perform validation in both places rather than trying to turn off validation in the UI. I understand your point that the service cannot assume that it's being passed valid data - that is a valid concern and is the reason your service should have validation. But you should also have validation in your UI. This is also nice because you can have client-side validation to prevent user errors and give you users a better experience.

Steve Michelotti
Thanks for your reply. I have clientside UI validation. I use the attributes in the DataAnnotations namespace for my models.My service layer adds the validation errors to the Modelstate (if in a web context). There is no reason to perform the same validation twice. :)
MartinF
Basically my service layer follows this approach http://www.asp.net/mvc/tutorials/validating-with-a-service-layer--cs It works fine, but the example doesnt use the DataAnnotations attributes for validation, which are automatically validated in MVC (and what i am trying to prevent).
MartinF
That link doesn't have a date as to when it was written but my guess is that it's pretty old. When MVC was first released, there was some confusion around *where* validation logic should go (controller, etc.). But that story has been solidified in the last year or so with the recommended best practice typically being that UI validation should happen in the model binder which is why that's now the default in MVC 2.
Steve Michelotti
If you are set on doing it this way, then it could be accomplish by inheriting the DataAnnotationModelBinder and then override the OnModelUpdated() method and inside just do ModelState.Clear(). I'm not saying I would do this though. :)
Steve Michelotti
I assume you are talking about the DefaultModelBinder (as there is no DataAnnotationsModelBinder - there is a DataAnnotationsModelValidator though). Doing a ModelState.Clear() inside the ModelBinder will affect the entire WebApplication which i dont want to, i want it to keep working the traditional way, but be able to disable the model validation either per action or per model type. I could of course make a big switch or if clause and add every single Model type that i dont want validated - but that would be pretty ugly i guess - but maybe it is the best option! :)
MartinF
I can see that the OnModelUpdated method gets a ControllerContext and a ModelBindingContext. The best option would be if it is somehow possible to figure out which Action is being called and then check if it has a Custom attribute (ValidateModel etc) attached to it to prevent it from validating the model.Another option is to register a ModelBinder that doesnt do the validation, and register every single type - something like ModelBinders.Binders[typeof(MyModelType)] = new NonValidatingModelBinder(); Thanks for replying. There is a couple of options that i can try to work on. :)
MartinF
Yes, I meant to say "DefaultModelBinder". I would use your second approach: ModelBinders.Binders[typeof(MyModelType)] = new NonValidatingModelBinder();. That way it doesn't affect your application globally.
Steve Michelotti
Just to give a small update (if it has any interest to you), it is possible to attach attributes to parameters (like the BindAttribute), so it should be easy to check if the parameter/model have an attribute attached in the OnModelUpdated and just disable the validation if it have. So when i dont need model validation i can just disable it by doing something like public ActionResult ActionMethodName ([DisableModelValidation] MyModelType model) {...} :)
MartinF
+1  A: 

Unfortunately there seems to be no easy way to disable the model validation happening in the ModelBinder except for registering every single model type you don’t want to validate (including nested complex types) with a specific ModelBinder. It can be done with the following code:

ModelBinders.Binders[typeof(MyModelType)] = new NonValidatingModelBinder();

Creating a SkipValidationAttribute that can be attached to action methods or action method parameters doesn’t seem possible as it should be implemented in the ControllerActionInvoker, but there is no way of letting the ModelBinder know that it should do any validation in the SetProperty() and OnModelUpdated methods when calling BindModel() in the GetParameterValue() method.

MartinF
I made the " SkipValidationAttribute" approach work, by creating my own impl of ControllerActionInvoker which gets the attribute from the method parameter and if it is declared it adds a key to the modelstate injected into the ModelBinder (BindModel method), and remove it again when the model is bound. Then i can check in the ModelBinder if the modelstate contains the key and if it does it skips validation, if not just performs validation like it normally would.
MartinF
A: 

I definitely dislike this addition in the 2.0 version, because, as you stated in your question, validation makes more sense in the Service layer. In this way you can reuse it in other non-web applications, and test it more easily without having to mock the auto-validation mechanism.

Validation in Controller layer is pointless because in this part you can only verify model data and not business rules. For example, think of a service responsible of adding new comments and a user that wants to post a new one, the data in the comment he/she is posting may be valid, but what happens if the user is banned to comment because of misbehavior in the past? You should do some validation in the Service layer to ensure this is not happening, and if it does, throwing an exception. In short, validation must be done in the Service layer.

I use xVal as my Validation framework because it's compatible with the DataAnnotationModel, allows me to place validation wherever I want and performs client-side validation without extra-effort, even remote-client side. This is how I use it at the beginning of each of my services, for example, a login service:

public void SignIn(Login login) {
    var loginErrors = DataAnnotationsValidationRunner.GetErrors(login);

    // Model validation: Empty fields?
    if (loginErrors.Any())
        throw new RulesException(loginErrors);

    // Business validation: Does the user exist? Is the password correct?
    var user = this._userRepository.GetUserByEmail(login.Email);

    if (user == null || user.Password != login.Password)

        throw new RulesException(null, "Username or password invalids");

    // Other login stuff...
}

Simple, web-independent and easy... then, in the Controller:

public RedirectResult Login(Login login) {

    // Login the user

    try {

        this._authenticationRepository.SignIn(login);

    } catch (RulesException e) {

        e.AddModelStateErrors(base.ModelState, "Login");

    }

    // Redirect

    if (base.ModelState.IsValid)
        return base.Redirect(base.Url.Action("Home"));
    else return base.Redirect(base.Url.Action("Login"));
}
kazuoua