Хотя вы сказали, что не хотите генерировать исключения, я обнаружил, что выбрасывать исключения очень эффективно.Особенно, если у вас есть несколько подсистем, проверяющих систему, использование одного единственного типа исключения в качестве фасада будет очень эффективным.
Что вы можете сделать, это создать собственное исключение (то есть с именем ValidationException
), которое будетгенерируется, когда ваш VAL-модуль сообщает об ошибке или бизнес-правила сообщают об ошибках.На уровне представления вы должны поймать это ValidationException
и сообщить этот точный тип исключения удобным для пользователя способом.
Использование одного типа исключения для обеих подсистем проверки позволяет вамсообщать об ошибках согласованным образом, а также гарантирует, что ошибки проверки не останутся незамеченными, когда вы (или другой разработчик) забудете обработать ошибки проверки.Обработка ошибок должна быть очень явной ИМО.Когда вы позволяете методам возвращать список ошибок, их легко забыть обработать.При создании пользовательского исключения легко добавить свойство к этому типу исключения, которое содержит список ошибок проверки.Такой список ошибок легко извлекается из VAB.Я не знаю, какую систему проверки вы используете для проверки ваших бизнес-правил, но не так сложно извлечь из нее список кодов ошибок.
Самый простой способ справиться с этим в пользовательском интерфейсе - этоконечно, используя try
- catch
:
var businessCommand = new CreateNewMerchantCommand();
businessCommand.Name = "Wikki";
// etc
try
{
businessCommand.Execute();
}
catch (ValidationException ex)
{
UIValidationHelper.ReportValidationErrors(ex.Errors);
}
Конечно, наличие этих операторов try
- catch
повсеместно уродливо, но по крайней мере этот код легко следовать,В зависимости от того, как вы структурировали свой бизнес-уровень и какую технологию пользовательского интерфейса вы используете, есть более красивые решения, которые вы можете использовать.Например, вы можете обернуть фактическую операцию, которая может завершиться с ошибкой, следующим образом:
var businessCommand = new CreateNewMerchantCommand();
businessCommand.Name = "Wikki";
// etc
UIValidationHelper.ExecuteOrDisplayErrors(() =>
{
businessCommand.Execute();
});
UIValidationHelper
будет выглядеть так:
public static class UIValidationHelper
{
public static void ExecuteOrDisplayErrors (Action action)
{
try
{
action();
}
catch (ValidationException ex)
{
// Show the errors in your UI technology
ShowErrorMessage(ex.Errors);
}
}
}
Другой вариант, которыйЯ использовал себя в прошлом, расширяя бизнес-уровень событиями.Чтобы это работало, вам нужна такая конструкция, как команды, как я использую в своих примерах.При использовании событий пользовательский интерфейс может выглядеть следующим образом:
var businessCommand = new CreateNewMerchantCommand();
businessCommand.Name = "Wikki";
// etc
businessCommand.ValidationErrorOccurred +=
UIValidationHelper.DisplayValidationErrors;
businessCommand.Execute();
В этом примере статический метод подключается к событию ValidationErrorOccurred
экземпляра команды.Хитрость заключается в том, чтобы позволить Execute
методу этой команды перехватить ValidationException
s и направить их в ValidationErrorOccurred
, когда она будет зарегистрирована.Когда никакой метод не зарегистрирован, это исключение должно пузыриться в стеке вызовов, потому что необработанное исключение проверки, конечно, не должно остаться незамеченным.
Хотя, конечно, это можно сделать непосредственно с вашего бизнес-уровня, это сделает ваш бизнес-уровень зависимым от конкретной технологии проверки.Кроме того, этот метод позволяет клиентам выбирать обработку ошибок проверки любым способом, которым они хотят, или решают вообще их не обрабатывать (например, если вы не ожидаете появления ошибок проверки в конкретном случае использования).
При использовании веб-форм ASP.NET метод DisplayValidationErrors
UIValidationHelper
может выглядеть следующим образом:
public static class UIValidationHelper
{
public static void DisplayValidationErrors(
object sender, ValidationErrorEventArgs e)
{
Page page = GetValidPageFromCurrentHttpContext();
var summary = GetValidationSummaryFromPage()
foreach (var result in e.Error.Results)
{
summary.Controls.Add(new CustomValidator
{
ErrorMessage = result.Message,
IsValid = false
});
}
}
private static Page GetValidPageFromCurrentHttpContext()
{
return (Page)HttpContext.Current.CurrentHandler;
}
private ValidationSummary GetValidationSummaryFromPage(Page page)
{
return page.Controls.OfType<ValidationSummary>().First();
}
}
Этот статический вспомогательный метод внедряет сообщения об ошибках в ValidationSummary
контроль на странице.Ожидается, что страница будет содержать элемент управления ValidationSummary
на корневом уровне страницы.Можно легко создать более гибкое решение.
Последнее, что я хотел бы показать вам, - как будет выглядеть BusinessCommand
при принятии этого решения.Базовый класс этих команд может выглядеть примерно так:
public abstract class BusinessCommand
{
public event EventHandler<ValidationErrorEventArgs>
ValidationErrorOccurred;
public void Execute()
{
try
{
this.ExecuteInternal();
}
catch (ValidationException ex)
{
if (this.ValidationErrorOccurred != null)
{
var e = new ValidationErrorEventArgs(ex);
this.ValidationErrorOccurred(this, e);
}
else
{
// Not rethrowing here would be a bad thing!
throw;
}
}
}
protected abstract void ExecuteInternal();
}
ValidationErrorEventArgs
выглядит так:
public class ValidationErrorEventArgs : EventArgs
{
public ValidationErrorEventArgs(ValidationException error)
{
this.Error = error;
}
public ValidationException Error { get; private set; }
}
Я надеюсь, что все это имеет смысл, и извините за мой длинный ответ: -)
Удачи.