Handling exceptions in a N Tier, Domain Driven Design, MVC application
I've built an MVC application that looks like this:
MVC Application - Application Layer - Business Layer - Repository - Data
I've read the code of many applications and discovered that no-one seems to pass exceptions in manner similar to how data is passed.
In other words, data is referenced through an interface and reused in different开发者_运维技巧 contexts. If an exception occurs in the Data layer, how would this get sent to the GUI or relevant layer? Should an Interface be used? What is the construct for handling exceptions in this application?
I haven't read the doco u linked but i have a simple case where i raise exceptions in response to failure to satisfy an invariant. Say I'm creating an instance and it's "invalid" for some reason, let's say bad user input. Rather than having my entity be in an invalid state (which is a no-no in DDD) and let the system "validate" it, it throws an exception when it's being created. The relevant "validation messages" (for the user) are extracted from the same Specification instance that was not satisfied and my derived exception contains the values the UI needs.
Example of an InvariantException
:
public class InvariantException : MyAppException
{
public object FailingObject = null;
public ModelStateDictionary ModelState = new ModelStateDictionary();
public InvariantException() { }
public InvariantException(object failingObject, ModelStateDictionary messages)
{
this.FailingObject = failingObject;
this.ModelState = messages;
}
public InvariantException(object failingObject, ModelStateDictionary messages,
Exception innerException)
: base("refer to ModelState", innerException)
{
this.FailingObject = failingObject;
this.ModelState = messages;
}
}
Example of a Specification that returns the relevant "validation messages" for the user/UI:
public class PostFieldLengthSpecification : ISpecification<Post>
{
private const string TITLE_LENGTH_RANGE = "5-100";
private const string BODY_LENGTH_RANGE = "20-10000";
public bool IsSatisfiedBy(Post post)
{
return this.GetErrors(post).IsValid;
}
public ModelStateDictionary GetErrors(Post post)
{
ModelStateDictionary modelState = new ModelStateDictionary();
if (!post.Title.Trim().Length.Within(TITLE_LENGTH_RANGE))
modelState.AddModelError(StongTypeHelpers.GetPropertyName((Post p) => p.Title),
"Please make sure the title is between {0} characters in length".With(TITLE_LENGTH_RANGE));
if (!post.BodyMarkup.Trim().Length.Within(BODY_LENGTH_RANGE))
modelState.AddModelError(StongTypeHelpers.GetPropertyName((Post p) => p.BodyMarkup),
"Please make sure the post is between {0} characters in length".With(BODY_LENGTH_RANGE));
return modelState;
}
}
Example of how the Factory never let's the invalid instance be created, instead it throws an exception and deposits the messages for the UI:
public static Post GetNewPost(string title, string bodyMarkup, DateTime posted)
{
var post = new Post(0, title, bodyMarkup, posted, new List<Comment>());
var fieldLengthSpec = new PostFieldLengthSpecification();
if (fieldLengthSpec.IsSatisfiedBy(post))
return post;
else
throw new InvariantException(post, fieldLengthSpec.GetErrors(post));
}
Finally, an example of a custom model binder that is used to catch said exception and pass the "invalid object" back to the action with the error messages:
public class PostModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(Post))
{
try
{
// Create Post
return base.BindModel(controllerContext, bindingContext);
}
catch (InvariantException ie)
{
// If invalid, add errors from factory to ModelState
bindingContext.ModelState.AddNewErrors(ie.ModelState);
bindingContext.ModelState.AddValuesFor<Post>(bindingContext.ValueProvider);
return ie.FailingObject;
}
}
Hope this helps.
精彩评论