2013-05-03 3 views
1

Можете ли вы привести пример того, как возвращать ошибки проверки из класса сервиса, используемого в веб-приложении. Что вы думаете об этом подходе ниже?Как вернуть ошибки проверки из методов класса обслуживания?

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

Редко можно найти код C# с goto – Fendy

+0

Fendy, вы знаете, как его переписать без goto и все же избежать дубликатов кода? –

+0

Ответ Vimel Stan - это чистая реализация без goto. – Fendy

ответ

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

+0

Не очень хорошая практика использования исключений для управления потоком кода. –

+0

Vimal, спасибо за ваше предложение. Но ошибки проверки не являются исключительными, я считаю, что ... –

+0

@FranciscoAfonso Если вы посмотрите на реализацию для RoleProviders и MembershipProviders, это шаблон, за которым они следуют. –

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 в качестве параметра. Использование параметра здесь заключается в том, чтобы избежать столкновения, когда вы хотите вернуть объект.

EDIT:

Там, кажется, некоторые дискуссии о броске исключение или нет во время проверки. Если это вопрос производительности, я не могу спорить, потому что я не эксперт в этом; и во время некоторых простых тестов, которые я делаю, я не нахожу никаких проблем с производительностью. Я объясню в разделе читаемости, почему я предпочитаю 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. Он может заполнить ваш отчет об исключениях.

+0

Ошибки, такие как «Пароль недействителен», «Ваша учетная запись отключена», «Вы превысили максимальное количество попыток входа», не являются исключительными, это просто ошибки проверки, а не исключения. –

+0

'System.FormatException' также является ошибкой проверки , но .Net рассматривал его как исключение. Просто рассмотрите исключение как «общий способ показать ошибку проверки». Это даст стандартизированное использование клиенту, особенно при создании компонента API. – Fendy

+0

с исключениями там будет также больше кода клиента - try/catch, плюс забывание размещения блоков try/catch приведет к критическим ошибкам во время выполнения. Моя модель (с использованием состояния модели var) не имеет этих двух проблем. Итак, почему вам это не нравится? –