可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to adhere to best multi-layer design practices, and don't want my MVC controller to interact with my DAL (or any IRepository for that matter). It must go through my business service layer to enforce proper business rules and validation. Validation - I don't want to perform validation in the controller using the various validation attributes ( such as [Required]) on my domain model entities because this sheds light on my front end. Not to mention this service can also be implemented through a WPF front end.
Since my validation is being done in my service layer, what are best practices for returning values back to the UI? I don't want a 'void addWhatever(int somethingsID)', because I need to know if it failed. Should it be a boolean? Should it be a Enum? Should I take advantage of exception handling? Or should I return some IValidationDictionary object similar to that used by MVC when adorning validation attributes to Model objects? (which I could use an adapter pattern in the UI later if needs be)
I would like to pass my entity from the controller to the service layer, and understand whether or not validation/data-persistence failed. I also don't want to lose sight on the fact that I need to return a view indicating the proper error messages for each field that may have failed validation (I'd like to keep this as painless as possible).
I have had several ideas, all of which don't feel right. I feel the answer includes View-specific-model entities, but this leads to a whole mapping issue that must be dealt with, not to mention this violates the DRY (Don't repeat yourself) principle. What is best practice?
回答1:
I know, it seems like doing MVC validation violates DRY, but in reality.. it doesn't.. at least not for most (non-trivial) applications.
Why? Because your view's validation requirements are quite often different from your business objects validation requirements. Your views validation concerns itself with validating that a specific view is valid, not that your business model is valid.
Sometimes those two are the same, but if you build your app so that the view requires the business model to be valid, then you are locking yourself into this scenario. What happens if you need to split object creation into two pages? What happens if you decide to use the service layer for a web service? By locking your UI into a business layer validation scenario you severly cripple the kinds of solutions you can provide.
The view is validation of the input, not validation of the model.
回答2:
This is how I have done it.
Have your service layer throw exceptions on business rule/validation rule failure. Create your own validation Exception for this, and include some properties to hold details of the validation error - (e.g. which property has the validation error, and what the message is)
Then create an extension method on Exception which will copy the details of the error to the ModelState (I got this idea from Steve Sandersons rather excellent 'Pro Asp.Net MVC 2 Framework' book) - if you do this right, MVC will highlight invalid fields, show errors in the UI etc.
Then your controller will contain something like this
try
{
Service.DoSomeThing();
}
catch (Exception err)
{
err.CopyTo(ModelState);
}
This means that your business rules and validation are now in your service layer, and this could be re-used.
Consider also passing DTOs / View Models to your Views, and mapping your domain objects to DTO's and (vice-versa), rather than passing your domain objects to your views.
Then the DTOs / View Models can reside in the MVC layer, and you can decorate these with the Validation attributes, and have the controller pass these to the views - thus using the built in MVC validation.
You will find that for any complex project, the validation you require at the UI end may be slightly different from what you require at the business rules end, so this helps seperate that.
There is a good library called AutoMapper which makes it easy to map from your domain objects to your DTOs (and vice versa) without a lot of boilerplate code.
回答3:
I recommend you take advantage of the built in MVC validation by decorating your Model classes with data annotations. This is only for basic input validation which is different from processing business rules and validation. Data Annotations are great because they are useful for any consumer that is aware but don't adversely impact consumers that don't understand how to use them.
I think you are right to use a service layer to abstract business rules and data access. You may want to do a couple of things to enhance the interaction between controller and service:
Return XXXResult objects instead of void or primatives. If your service method is AddProduct then return AddProductResult or more broadly ProductServiceOperationResult. This result contains success/fail indicator as well as additional information.
If you are using WCF then use Fault Contracts and exceptions.
A typical MVC Application solution of mine looks like this:
- MVC Website Project
- xxx.Model (project, referenced by most layers)
- xxx.Services (project)
- xxx.DataAccess (project, sometimes merge with services)
- others as needed
Good luck!
回答4:
The issue here is validation in the service layer but how to get that information 'back up' to the web app.
Something similar we discussed a bit back since the idea of dependency injection clearly comes into play here if a service is validating, you can't sinply call the service in the Model (for instance if IValidateableObject is implemented there you dont want to call the service directly)
The approach taken was:
Option 3: I didn't know about this earlier, but what appears to be a
very powerful way to write validators is to use the ModelValidator
class and a corresponding ModelValidatorProvider.
ASP.NET MVC 3: Validating model when information external to the model is required
So basically you are injecting a validator (which would then be in your service layer) to be resolved by mvc without a need to an explicit service locator call.
回答5:
Stephen advice is perfect in this scenario. Currently, I am working on a very large MVC 3.0 application with SOA and other things are involved. So in a response, you would like to fill all the necessary information and showing them to your views (of-course controller will dictate). Hope this helps.
回答6:
It's actually not a bad thing to repeatedly run validations in a few layers (client side, server side in the controller or equivalent, and again in the business layer). It makes your code somewhat uncoupled. Ideally you'd only have to describe them in one place, but sometimes this is not possible. By failing to use data annotations, are you making it really hard on yourself if you want to do client-side validations? It seems so.
Anyway, what I've done in the past in non-mvc applications is have most action methods return a Response object that includes a status (success, errors, warnings) and a list of validation errors, as well as any other properties required.
You might be able to leverage the IValidateableObject interface, but this again ties you somewhat to something ASP.net-specific. Perhaps a compromise would be to consume your response object and convert to DataAnnotation-specific errors.