Asp.NET MVC - Validate a Model Outside of the Controller
Asked Answered
W

4

19

I am building an ASP.NET MVC project and going for the following Architecture:

  • A Core project that has models, validation, dto, logic, etc.
  • A ServiceStack API project that acts as my REST API
  • An ASP.NET MVC Web project that is the UI

So, let's say I want to add a user. I define a NewUserInputModel in the Core project. I give it some data annotations such as [Required]. After doing this, the Web project will perform client side validation based on those annotations.

My question is about server side validation. I want to validate the NewUserInputModel using the same rules that are being used on the client side, and I want to run that validation weather the NewUserInputModel comes in from the API or the Web project.

I realize I could call ModelState.IsValid from a Controller in the Web project - but I want to call that validation from the Core project so that all validation logic lives in Core. This way, no matter how this model gets to the Core logic, I always call the same validation. I don't want to leak a System.Web reference into my Core project.

Is this a reasonable design? I think it is - but if something about it smells, I'd be happy to hear it.

Thanks in advance for any help.

Worrell answered 19/11, 2013 at 12:57 Comment(0)
G
4

I usually keep my view models in the Web project and do the input validation in the controller using the ModelState property. If this succeeds, I map them to domain models (which live in the Core layer) and send those to my services in the Service (could also be Core) layer. The service layer validates business rules and if it succeeds it calls a repository to perform the desired action and returns the result of the operation to the controller.

Keeping your view models in the Web project also allows you to use MVC validation attributes such as RemoteAttribute.

I'm not saying that your design smells, but I do think it's good to separate presentation logic from your Core layer.

Goncourt answered 19/11, 2013 at 13:31 Comment(0)
B
33

I consider your approach to be good. Mapping one set of models to another could bring some bugs.

The code you are looking for is:

using System.ComponentModel.DataAnnotations;

var context = new ValidationContext(model, serviceProvider: null, items: null);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, context, results);
if (!isValid)
    throw new Exception("Model is not valid because " + string.Join(", ", results.Select( s => s.ErrorMessage).ToArray()));

For details see http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationcontext.aspx or http://odetocode.com/blogs/scott/archive/2011/06/29/manual-validation-with-data-annotations.aspx

Bibby answered 26/2, 2014 at 19:41 Comment(1)
Note that TryValidateObject() also has a version with a 4th parameter valdateAllProperties, that, when set to true, causes the validator to process attributes besides the RequiredAttribute.Rondelle
G
4

I usually keep my view models in the Web project and do the input validation in the controller using the ModelState property. If this succeeds, I map them to domain models (which live in the Core layer) and send those to my services in the Service (could also be Core) layer. The service layer validates business rules and if it succeeds it calls a repository to perform the desired action and returns the result of the operation to the controller.

Keeping your view models in the Web project also allows you to use MVC validation attributes such as RemoteAttribute.

I'm not saying that your design smells, but I do think it's good to separate presentation logic from your Core layer.

Goncourt answered 19/11, 2013 at 13:31 Comment(0)
S
1

My approach to this by fast thinking is to do something like that. In business layer create

public class ValidationMessage
{
    public ValidationMessage(string message, ValidationMessageType messageType)
    {
        Message = message;
        MessageType = messageType;
    }

    public string Message { get; private set; }
    public ValidationMessageType MessageType { get; private set; }
}

public enum ValidationMessageType
{
    Info,
    Warning,
    Error,
}

Now having a service example

public interface ISomeService
{
    List<ValidationMessage> Edit(SomeModel item);
}

Now in the controller you call the service and pass the validation messages to the view. It may be necessary to use ViewBag and pass the whole list. On the view you can highlight Info, Warning, Error. For example you could return some success message with Info type, or nothing at all and in the controller create success message.

Generally this approach is more coding, but more flexibility. Ideally service would validate everything, but for smaller projects to avoid validation duplication, as Henk Mollema stated in his answer, you could do user input validation through ViewModel and in service just validate just critical business rules.

ValidationMessageType may be an overkill as well, so it's possible to return just List from the service as error list and the empty list would mean success.

Sparker answered 3/8, 2015 at 14:53 Comment(0)
S
0

Making a mixed Validation could be also useful, so mixing DataAnnotations with custom validations logic

This may be helpful http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2

Sparker answered 3/8, 2015 at 15:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.