1

您能给我一个示例,说明如何从 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);
    }
}
4

2 回答 2

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. 这被称为单一责任原则

于 2013-05-03T07:53:25.027 回答
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以及何时不喜欢。

更喜欢:

在 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)跟踪应用程序的异常时。它可能会淹没您的异常报告。

于 2013-05-03T07:53:52.330 回答