1

Can you give me an example of how to return validation errors from a service class used in a web application. What do you think about this approach below?

using System;
using System.Linq;
using System.Web.Mvc;

using App.Data;
using App.Security;

public interface IMembershipService
{
    bool ValidateUser(string userName, string password, ModelStateDictionary model = null);
}

public class MembershipService : IMembershipService
{
    private DatabaseContext db;

    public MembershipService(DatabaseContext db)
    {
        this.db = db;
    }

    public bool ValidateUser(string userName, string password, ModelStateDictionary model)
    {
        if (string.IsNullOrWhiteSpace(userName) || userName.Length > 128 ||
            string.IsNullOrWhiteSpace(password) || password.Length > 256)
        {
            TryAddModelError(model, "Username or password provided is incorrect.");
            return false;
        }

        var user = this.db.Users.SingleOrDefault(u => u.UserName == userName);

        if (user == null || !PasswordHash.Validate(password, user.PasswordHash, user.PasswordSalt))
        {
            TryAddModelError(model, "Username or password provided is incorrect.");
            return false;
        }

        if (!user.IsApproved)
        {
            TryAddModelError(model, "Your account is suspended.");
            return false;
        }

        user.LastLoginDate = DateTime.UtcNow;
        this.db.SaveChanges();

        return true;
    }

    private static void TryAddModelError(ModelStateDictionary model, string errorMessage)
    {
        if (model != null)
        {
            model.AddModelError(string.Empty, errorMessage);
        }
    }
}

Usage sample:

[Authorize]
public class AccountController : Controller
{
    private readonly IMembershipService membershipService;

    public AccountController(IMembershipService membershipService)
    {
        this.membershipService = membershipService;
    }

    [HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
    public Login(LoginModel model, string returnUrl)
    {
        if (ModelState.IsValid && this.membershipService.ValidateUser(
            model.UserName, model.Password, modelState: ModelState))
        {
            FormsAuthentication.SetAuthCookie(userName, true);
            return RedirectToLocal(returnUrl);
        }

        return View(model);
    }
}
Grief Coder
  • 6,508
  • 9
  • 38
  • 51
  • 1
    It is rare to found a C# code with goto – Fendy May 03 '13 at 07:49
  • Fendy, do you know how to rewrite it without goto and still avoid code duplicates? – Grief Coder May 03 '13 at 07:50
  • Vimel Stan's answer is a clean implementation one without goto. – Fendy May 03 '13 at 07:58
  • Fendy, the problem with Vimel's answer is that validation errors are not exceptions and also there will be duplicate lines of code this way (the ones with error messages) – Grief Coder May 03 '13 at 08:09
  • Well, you has a strange point of view of DRY. those two may like a duplicated code, but each of it does **different** thing. The principal of DRY is not to repeat/develop same operation twice, not the code if the operation are different. – Fendy May 03 '13 at 09:16
  • Fendy, If two lines are identical, how come they do different things? (the lines with error message "Username or password is incorrect.") – Grief Coder May 03 '13 at 09:29

2 Answers2

2

Try this instead:

public bool ValidateUser(string userName, string password)
{
    if (string.IsNullOrWhiteSpace(userName) || userName.Length > 128 ||
    string.IsNullOrWhiteSpace(password) || password.Length > 256)
    {
        throw new ProviderException("Username and password are required");
    }

    var user = this.db.Users.SingleOrDefault(u => u.UserName == userName);

    if (user == null || !PasswordHash.Validate(password, user.PasswordHash, user.PasswordSalt))
    {
        throw new ProviderException("Incorrect password or username");
    }

    return true;
}

Usage of Membership service:

...
try
{
    var result = membership.ValidateUser(userName, password);
    ...
}
catch (ProviderException e)
{
    model.AddModelError(string.Empty, e.Message);
}
...

This way, your MembershipService is only responsible for validation and the ValidateUser method validates the username and password. What is done with the validation result is up to the user of the MembershipService. This is referred to as the Single Responsibility Principle

Vimal Stan
  • 2,007
  • 1
  • 12
  • 14
  • 1
    Its not very good practice to use exceptions to control code flow. – Francisco Afonso May 03 '13 at 07:56
  • 1
    Vimal, thanks for your suggestion. But validation errors are not exceptional I believe... – Grief Coder May 03 '13 at 07:56
  • @FranciscoAfonso If you look at the implementation for RoleProviders and MembershipProviders, this is the pattern they follow. – Vimal Stan May 03 '13 at 07:57
  • You could return a string. Empty/Null on success. – Francisco Afonso May 03 '13 at 07:57
  • The problem with returning a string is that there could be more than 1 validation error after method is called. – Grief Coder May 03 '13 at 07:59
  • @FranciscoAfonso Could also return false :) Many ways of solving the same thing, I just provided the code that's similar to a standard implementation. – Vimal Stan May 03 '13 at 07:59
  • Yes, and i think you did well, actually im gonna +1 this answer. Just launched the exception topic so that the OP can think about that too. – Francisco Afonso May 03 '13 at 08:01
  • Setting a Message for an Exception is not the equivalent of returning a string. The message only provides a description of the error state. – Vimal Stan May 03 '13 at 08:02
  • @FranciscoAfonso Please add a link to the topic in the comments, would be easy to track in future. – Vimal Stan May 03 '13 at 08:03
  • Yes but you can return null or empty on success and check the return. Thats one way to do it. – Francisco Afonso May 03 '13 at 08:03
  • Also keep in mind, that there might be more than one validation error after a single method call, each of them containing an error message string and field name in the model if any – Grief Coder May 03 '13 at 08:03
  • @GriefCoder you can return an Enumerable of string errors and empty on success. – Francisco Afonso May 03 '13 at 08:04
  • Francisco, instead of true/false ? – Grief Coder May 03 '13 at 08:05
  • @GriefCoder yes. Your validation method can return an empty collection on success or a collection with string errors on failure. The caller just need to check the return count()>0 – Francisco Afonso May 03 '13 at 08:08
  • You'd want to use `Any()` instead of `Count() > 0`, it's more performant. The disadvantage of this approach would be that it's mixing up your validation code along with your other code. – Vimal Stan May 03 '13 at 08:10
  • @FranciscoAfonso Isn't it better to return void, but create a custom exception with collection of error strings? I think it is cleaner this way. – Fendy May 03 '13 at 08:13
  • @VimalStan check this: http://stackoverflow.com/questions/305092/which-method-performs-better-any-vs-count-0 -- for collections count()>0 is faster. But that's true for enumerable. Fendy, that would do it but throwing and catching an exception is quite expensive regarding cpu performance so its not a really good practice to use them to control this kind of situations. – Francisco Afonso May 03 '13 at 08:16
0

Short answer is to throw an exception (usually InvalidOperationException).

Long answer, you can create a custom exception to hold the informations you need. Example:

public class CustomValidationErrorException : Exception
{
    public string Id { get; set; } // Custom Id for the operation
    public IEnumerable<string> Messages { get; set; } // Multiple validation error message
}

Regarding to the requirement, you also can create a custom validation class and ref as a parameter. The parameter usage here is to avoid collision when you want to return an object.

EDIT:

There seems some discussion about throwing exception or not during validation. If it is performance matter, I cannot argue, because I'm not expert in that; and during some simple tests that I do, I don't find any performance matter. I will explain in a readability section instead why I prefer Exceptions and when it isn't.

Prefer:

In API side of view, the code will be easier to read. Consider this service example:

public interface ILoginService{
  void Login(string userName, string password);
}

Really simple and readable, and from usage point of view

public void LoginServiceConsumer(ILoginService service){
  //declare username and password
  service.Login(userName, password);
  // do what after login, if throw exception then this operation will ensure to be cancelled automatically
}

No question from my view with the implementation instead. Now, consider returning error messages:

public interface ILoginService{
  IEnumerable<string> Login(string userName, string password);
}

And from usage point of view

public void LoginServiceConsumer(ILoginService service){
  //declare username and password
  IEnumerable<string> validationResult = service.Login(userName, password);
  if(validationResult.Any())
  // do the next operation
}

Without proper documentation or good knowledge to use the object, now it popped up a question. What is the IEnumerable<string> that it returned? Is it only return the error message? Will it return "success" as a message when the validation is ok?

Another downside of this technique is, when you returning an object as execution result, but need to validate it first:

public MyObject Convert(MySource source){
   if(!source.Valid){
     //give error message here
   }
}

How can you do it? One way is to use ref to the message results and assigning it.

public MyObject Convert(MySource source, ref List<string> errorMessage){
   if(!source.Valid){
     errorMessage.Add("The source is not valid");
     return null;
   }
}

But from usage point of view:

public void ConvertConsumer(MySource source){
  List<string> errorMessage = new List<string>(); //you should create a new list
  Convert(MySource source, ref errorMessage); //then pass it
}

First, it required a new list. Second, will the user know that it can return null when the validation is bad? Last thing is the same question, What is the IEnumerable<string> that it set? Is it only set the error message? Will it set "success" as a message when the validation is ok?

Not Prefer:

One condition that I not prefer using exception as validation is when your application is tracked for its exceptions using third party, such as AVICode. It can flood your exception report.

Fendy
  • 4,565
  • 1
  • 20
  • 25
  • Errors like "Password is invalid", "Your account is disabled", "You exceeded the max number of login attempts" are not exceptional, they are just validation errors, not exceptions.. – Grief Coder May 03 '13 at 07:59
  • `System.FormatException` are also validation error, but .Net treated it as an exception. Just consider the exception as a "general way to show validation error". It will give a standardized usage to the client, especially when you create an API component. – Fendy May 03 '13 at 08:08
  • with exceptions there gonna be also more client code - try/catch, plus forgetting placing try/catch blocks would lead to critical errors at run-time. My pattern (with using model state var) does not have these two problems. So, why don't you like it? – Grief Coder May 03 '13 at 08:52
  • I've updated my answer to express why I don't prefer it. It is not I don't like it, I just don't prefer it. – Fendy May 03 '13 at 09:13