您能举例说明如何从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);
}
}
答案 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)跟踪应用程序的异常时。它会淹没您的异常报告。