如何从服务类方法返回验证错误?

时间:2013-05-03 07:43:53

标签: c# asp.net asp.net-mvc design-patterns

您能举例说明如何从Web应用程序中使用的服务类返回验证错误。您对此方法有何看法?

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);
        }
    }
}

使用示例:

[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);
    }
}

2 个答案:

答案 0 :(得分:2)

请改为尝试:

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;
}

会员服务的使用:

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

这样,您的MembershipService仅负责验证,ValidateUser方法验证用户名和密码。验证结果的作用取决于MembershipService的用户。这被称为Single Responsibility Principle

答案 1 :(得分:0)

简短回答是抛出异常(通常是InvalidOperationException)。

答案很长,您可以创建自定义异常来保存所需的信息。例如:

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

关于要求,您还可以创建自定义验证类并将ref作为参数。这里的参数用法是为了避免在想要返回对象时发生冲突。

修改

在验证过程中似乎有一些关于抛出异常的讨论。如果是性能问题,我不能争辩,因为我不是那么专家;在我做的一些简单测试中,我没有发现任何性能问题。我将在可读性部分进行解释,而不是为什么我更喜欢Exceptions,何时不是。{/ p>

<强>体型:

在API方面,代码将更易于阅读。考虑一下这个服务示例:

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

非常简单易读,从使用的角度来看

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
}

我对此实施的看法毫无疑问。现在,考虑返回错误消息:

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

从使用的角度来看

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

如果没有正确的文档或使用该对象的良好知识,现在它会弹出一个问题。它返回的IEnumerable<string>是什么? 是否会返回错误消息?当验证正常时,它会将“成功”作为消息返回吗?

这种技术的另一个缺点是,当您将对象作为执行结果返回时,但需要首先验证它:

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

你怎么能这样做?一种方法是使用ref来显示消息结果并分配它。

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

但从使用的角度来看:

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
}

首先,它需要一个新列表。其次,用户是否知道在验证错误时它可以返回null?最后一件事是同一个问题,它设定的IEnumerable<string>是什么? 是否设置了错误消息?当验证成功时,它会将“成功”设置为消息吗?

不首选:

我不喜欢将异常用作验证的一个条件是当使用第三方(例如AVICode)跟踪应用程序的异常时。它会淹没您的异常报告。