Dependency Injection or Service Locator inside ViewModel

Topics: Core, Writing modules, Writing themes
Developer
Jan 3, 2012 at 1:13 PM
Edited Jan 3, 2012 at 2:03 PM

I am writing a simple Logon viewmodel class that will implement IValidatableObject so that it can check with the IMembershipService if the specified username / password combination exists (and if not, it will yield a ValidationError).

My question is: what is the recommended way to get an instance of IMembershipService inside a ViewModel?
The ViewModel gets instantiated by the modelbinder during a form post:

[Themed, HttpPost] 
public ActionResult Logon(LogonVM model) { ... }

The LogonVM class looks like this:

public sealed class LogonVM : IValidatableObject
    {
        [Required]
        public string UserName { get; set; }

        [Required, DataType(DataType.Password)]
        public string Password { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var membershipService = ???;
            var user = _membershipService.ValidateUser(UserName, Password);
            if (user == null)
            {
                yield return new ValidationResult("The username or e-mail or password provided is incorrect.");
            }
        }
    }

Does Orchard provide a ServiceLocator class, or does Orchard use a ModelBinder that supports constructor dependency injection? I did find this line in OrchardStarter:

//MvcServiceLocator.SetCurrent(hostContainer);

However, it has been commented out and I can't find a model binder implementation that uses constructor injection.

I could of course move the username / password validation to the controller as is done in Orchard.Users, but I think I prefer to keep the validation inside the ViewModel class.

Jan 3, 2012 at 2:37 PM

Have you tried the constructor injection method? Should take 5-10 minutes to determine if that works for ViewModels or not. 

Developer
Jan 3, 2012 at 4:36 PM

@TheMonarch, I tried using constructor injection, but that doesn't work: I get an exception from the modelbinder stating that it requires a parameterless constructor.

Coordinator
Jan 3, 2012 at 4:45 PM

There is no recommendation on how to do not recommended stuff !

You might not want to do that, can you explain why you need this service in the ViewModel ?

Maybe you might prefer to REsolve a service from the View itself, using something like WorkContext.Resolve<YouService>()

Jan 3, 2012 at 4:53 PM

Unrelated, but for my own curiosity: 

Is the public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) method something you call yourself or is that an ASP.NET MVC convention for doing custom validation? I'm looking for a way to add some custom validations to my registration ViewModel and wasn't sure what the best approach would be.

Developer
Jan 3, 2012 at 5:02 PM
Edited Jan 3, 2012 at 5:07 PM

@sebastienros The sole reason I want access to a service from within my ViewModel is to keep validation in one place.
For example, the ViewModel has a UserName property with a [Required] attribute, which is used during model validation by the model binder. Another thing that needs to be validated is the username / password combination. Since there are already some validation artifcats in the view model ([Required]UserName, [Required]Password), I tought I might as well do the username/validation checking as well from within the View Model (by implementing the IValidatableObject.Validate method).

That way, my controller's action method only has to check if there are any ModelValidation errors and decide wether to re-display the view or sign in the user and redirect to another page.

The same could go for signing up a new user: in order to validate the unicity of the specified username, I could do it from within the ViewModel by implementing IValidatableObject (so that the modelbinder will not only invoke each DataAnnotated property, but also invoke the Validate method).
All validation is thus inside the view model, instead of some validation concerns on the properties and some inside of the controller.

I do understand though that a ViewModel should have no dependencies on services: it should be a simple DTO constructed by the controller or modelbinder, with no dependencies on external services. Therefore I do not prefer constructor injection, but using some sort of ServiceLocator just inside the Validate method of my view model seems quite practical :)

If anyone has strong ideas on why this is really bad, I will be happy to hear those.

Developer
Jan 3, 2012 at 5:06 PM
Edited Jan 3, 2012 at 8:34 PM
TheMonarch wrote:

Unrelated, but for my own curiosity: 

Is the public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) method something you call yourself or is that an ASP.NET MVC convention for doing custom validation? I'm looking for a way to add some custom validations to my registration ViewModel and wasn't sure what the best approach would be.


It is called by something other than yourself. In the case of MVC, it is called by the DefaultModelBinder. You simply implement IValidatableObject in one of your view models, say MyViewModel. Then whenever you use MyViewModel as a parameter type in one of your actions, the DefaultModelBinder will recognize that the type implements IValidatableObject, runs the Validate method, and adds a model error for each yielded ValidationResult.

Jan 3, 2012 at 8:00 PM

Thanks -- that worked well for me. 

Jan 4, 2012 at 7:13 AM

Can you post your solution?

Jan 4, 2012 at 4:29 PM
Znowman wrote:

Can you post your solution?

If you're asking me, my question was not directly related to the OP's, sorry about that. I just wanted to know about the Validate() method -- if it was part of MVC or Orchard (i'm learning both as I go). 

Feb 12, 2013 at 8:24 AM
ViewModels are just POCOs(with some instance level logic, like type or format validation), they should not use any Services.