I am creating a small validation application using Spring MVC. I am very new to Spring MVC and would like to ensure that what I want is possible.
I have simplified my problem.
I have setup a controller that will be called when a URL is executed. localhost/validate/{SOME TEXT}
The {SOME TEXT} value with be sent to all my validation classes I created.
I currently have 4 classes which does the validation and returns another Object data about what happened during the validation
The 4 validation classes are:
CreditCardValidator
AddressValidator
ZipcodeValidator
AccountNumberValidator
I have a main controller bean that when called I want the string to be passed to each class and the object returned from each to be stored and then finally all results are sent back in a response.
Normally, I would do this without Spring by creating an interface that each validation class implements. Then iteration through the list of classes and execute a method.
The problem doing it that way is that whenever I need to add a new validation class I'll need to register it so the request can use it. This involved modifying existing classes.
Since I am using Spring quick heavily in this application I am wondering if this is possible to do via Spring and annotated classes.
I was thinking of creating a custom annotation that each validation class has and then using spring component-scan to get the classes. This would allow me to create new validations without modifying existing code.
Below is the what I am trying to do.
#Controller
public class StringValidationController {
#RequestMapping(value = "/validate/{text:.+}", method = RequestMethod.GET)
public ModelAndView index(#PathVariable("text") String text) {
ModelAndView model = new ModelAndView();
model.setViewName("index");
model.addObject("result", getListOfValidatedData());
return model;
}
public List getListOfValidatedData(){
//Scan for IValidator annotation
//call each concrete class and pass in text
// get object with has validation information in it
}
}
I have a spring controller which uses bean validation to validate input values, for example to save a topic:
public String save(#ModelAttribute("topic") #Valid Topic topic
This works nicely.
In my data layer i also use bean validation and when something invalid is send to the data layer a ConstraintViolationException is thrown. I catch this one in my controller with a exceptionhandler:
#ExceptionHandler({ ConstraintViolationException.class })
public ModelAndView handleValidationException(HttpServletRequest req,ConstraintViolationException e) {
This also works nicely.
The problem i'm facing is i can't access the model in my exceptionhandler, the docs clearly states this:
Important Note: the Model may not be a parameter of any #ExceptionHandler method.
Access to the model is needed cause i want to show the user the view with the form he just filled in. But because i can't access the model i can't get the filled out form data.
One solution i found is catching the ConstraintViolationException in my save() method, cause here i do have access to the model.
But i would prefer a generic solution and not have messy try/catch/finally when i need to save something to the database.
Does anybody know a good solution?
[edit]
Here's my current exception handler:
#ExceptionHandler({ ConstraintViolationException.class })
public ModelAndView handleValidationException(HttpServletRequest req,ConstraintViolationException e) {
log.debug("handleValidationException");
//no access to model, so i create new one, but this new one doesn't have the values the user entered in the html form.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.setViewName("add_topc");
return mav;
}
After some thoughts on this subject i came to the conclusion that a single error page displaying a error is enough. Mainly because the errors thrown by the data layer are fatal errors that can't be corrected by the user, for example by changing field values. It of course would have been nice to display the error page in the context of where things did go wrong and save the users input. But that doesn't seem to be possible with spring.
A bit old but I found myself in a similar situation.
One possible solution would be to wrap the model in the contraintViolationException such that the exception handler could pull the model from the exception.
I haven't found any good explanation of why the model is cleared/not available in the exception handler - except someone who hypothesized that it was by design as the model could be in an invalid state.
SNIPET from Controller
...
throw new ConstraintViolationException(model);
SNIPET from ModelAwareException
public void ConstraintViolationException(Model saveModel) {
this.model = saveModel;
}
SNIPET from ExceptionHandler
#ExceptionHandler({ ConstraintViolationException.class })
public ModelAndView handleValidationException(HttpServletRequest req,ConstraintViolationException e, Model unpopulatedSpringModel) {
unpopulatedSpringModel = e.getModel();
return new ModelAndView("yourpage");
}
I am intending to pass a Hotel model to my Controller Action - Do some checks/processing on it, then return a potentially different Hotel model rendered in a Partial View.
The problem I'm getting is that if I pass the oHotelParameter Model to the Action then the PartialView uses the model passed to the Action instead of the one passed to the PartialView method.
If I remove the oHotelParameter Parameter from the Action then the View is Rendered as expected using oHotel.
public ActionResult _SaveMasterDetails(Hotel oHotelParameter)
{
//Do some processing on oHotelParameter
//........
Hotel oHotel = new Hotel();
oHotel.GetHotelInfoById(14); //This gets a different Hotel object just for a test
//For some reason oHotel is ignored and oHotelParameter is used instead unless I remove oHotelParameter
return PartialView("_MasterDetails", oHotel);
}
When I debug the View I see that the Model is set to the value I pass to PartialView (oHotel), yet the result I see coming back from the Action contains data from the oHotelParameter object.
In case it makes a difference, I am calling the Action from jQuery ajax.
Can anyone explain why this should happen?
when mvc handles a form post, it fills the ModelState object with the details of the model.
This is when used when the view is rendered again from the post action, this is incase you have thrown the view back out because it has failed validation.
If you want to pass out a new model and not use the view state, then you can call ModelState.Clear() before returning the view and that should let you rebind the view to the new model.
I think that it would help if you had a better understanding of how model binding works when you post back to an action method. In most cases, it is unecessary and inefficient to pass a view model as a parameter to a POST action method. What you are doing is loading the view model into memory twice when you pass your view model as a parameter (assuming a strongly typed view). When you do a post back the model becomes part of the form collection (through model binding) in the request object in the BaseController class that every controller inherits from. All that you need to do is to extract the model from the Form collection in the Request object in the BaseController. It just so happens that there is a handy method, TryUpdateModel to help you do this. Here is how you do it
[POST]
public ActionResult Save()
{
var saveVm = new SaveViewModel();
// TryUpdateModel automatically populates your ViewModel!
// TryUpdateModel also calls ModelState.IsValid and returns
// true if your model is valid (validation attributes etc.)
if (TryUpdateModel(saveVm)
{
// do some work
int id = 1;
var anotherSaveVm = GetSaveVmBySomeId(id);
// do more work with saveVm and anotherSaveVm
// clear the existing model
ModelState.Clear();
return View(anotherSaveVm);
}
// return origonal view model so that the user can correct their errors
return View(saveVm);
}
I think that the data in the form collection contained in the request object is being returned with the view. When you pass the model back to the post action method as a parameter, I believe it is passed in the query string (see Request.QueryString). Most of the time, it is best to only pass one or two primitive type parameters or primitive reverence types such as int? to an action method. There is no need to pass the entire model as it is already contained in the Form collection of the Request object. If you wish to examine the QueryString, seee Request.QueryString.
My model returns 11 rows. I am using ADO.NET entity datamodel i.e .edmx file.
I have stored procedures and function imports in model browsers.
The designer code has GetMonthlyAwardsToEvaluate_Result
In my view I just call the model and scafold template used I Edit.
I am getting this error...
Exception Details:
System.InvalidOperationException: The model item passed into the dictionary is of type
'System.Collections.Generic.List`
1[TestingPMO_RR.Models.GetMonthlyAwardsToEvaluate_Result]',
but this dictionary requires a model item of type
'TestingPMO_RR.Models.Evaluations.EvaluationModel'.
Model.cs:
public List<GetMonthlyAwardsToEvaluate_Result> GetMonthlyEvaluation(int intAwardId,
string strAssociateId, string strStatus)
{
return ctx.GetMonthlyAwardsToEvaluate(intAwardId, strAssociateId, strStatus).ToList();
}
Controller.cs:
public ActionResult ApproveNomination(int ? intAwardId, EvaluationModel eval)
{
intAwardId = 4;
eval.AwardId = Convert.ToInt16(intAwardId);
eval.Status = "PENDING";
var model = eval.GetMonthlyEvaluation(eval.AwardId, eval.strPendingWith, eval.Status);
return View(model);
}
View has
#model TestingPMO_RR.Models.Evaluations.EvaluationModel
Please help.................
The Model returns 11 rows from stored procedure................
You are passing a List<GetMonthlyAwardsToEvaluate_Result> to the view because that's what the GetMonthlyEvaluation method returns. So your view should be strongly typed to this same model:
#model List<TestingPMO_RR.Models.GetMonthlyAwardsToEvaluate_Result>
And by the way that's exactly what the error message you got is telling you:
The model item passed into the dictionary is of type
'System.Collections.Generic.List`1[TestingPMO_RR.Models.GetMonthlyAwardsToEvaluate_Result]',
but this dictionary requires a model item of type
'TestingPMO_RR.Models.Evaluations.EvaluationModel'
I'm trying to use MVC for a new project after having been around the block with all the samples and tutorials and such. However, I'm having a hard time figuring out where certain things should take place.
As an example, I have an entity called Profile. This entity contains the normal profile type stuff along with a DateOfBirth property that is of type DateTime. On the HTML form, the date of birth field is split into 3 fields. Now, I know I can use a custom model binder to handle this, but what if the date entered is not a valid date? Should I be checking for that in the model binder? Should all my validation go in the model binder? Is it ok to have only a few things validated in the model binder and validate the rest in the controller or the model itself?
Here's the code I have now, but it just doesn't look right to me. Seems dirty or smelly.
namespace WebSite.Models
{
public class ProfileModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
DateTime birthDate;
var form = controllerContext.HttpContext.Request.Form;
var state = controllerContext.Controller.ViewData.ModelState;
var profile = new Profile();
profile.FirstName = form["FirstName"];
profile.LastName = form["LastName"];
profile.Address = form["Address"];
profile.Address2 = form["Address2"];
profile.City = form["City"];
profile.State = form["State"];
profile.Zip = form["Zip"];
profile.Phone = form["Phone"];
profile.Email = form["Email"];
profile.Created = DateTime.UtcNow;
profile.IpAddress = controllerContext.HttpContext.Request.UserHostAddress;
var dateTemp = string.Format("{0}/{1}/{2}",
form["BirthMonth"], form["BirthDay"], form["BirthYear"]);
if (string.IsNullOrEmpty(dateTemp))
state.AddModelError("BirthDate", "Required");
else if (!DateTime.TryParse(dateTemp, out birthDate))
state.AddModelError("BirthDate", "Invalid");
else
profile.BirthDate = birthDate;
return profile;
}
}
}
Building on the sample code above, how would you do the validation message for a 3 part field? In the case above, I'm using a completely separate key that doesn't actually correspond to a field in the form, because I don't want an error message to appear beside all 3 fields. I only want it to appear to the right of the Year field.
I think it is reasonable to do validation in the model binder. As Craig points out, the validation is mostly the property of your business domain, however:
Sometimes your model is just a dumb presentation model, not a business object
There are various mechanisms you can use to surface the validation knowledge into the model binder.
Thomas gives you an exmaple of #1.
An example of #2 is when you declaratively desribe validation knowlege using attributes (like the DataAnnotation attribute [Required]), or inject some business layer validation service into a custom model binder. In these situations the model binder is an ideal place to take care of validation.
That being said, model binding (finding, converting, and shuffling data into an object) and validation (data meets our specifications) are two seperate concerns. You could argue that they should be seperate phases/components/extensibility points, but we have what we have, although the DefaultModelBinder makes some distinction between these two responsibilities. If all you want to do is provide some validation for a specific type of object you can derive from the DefaultModelBinder and override the OnPropertyValidating method for property level validations or OnModelUpdated if you need the holistic view.
Here's the code I have now, but it
just doesn't look right to me. Seems
dirty or smelly.
For your specific code I would try to write a model binder for DateTime only. The default model binder can take care of binding firstname, lastname, etc., and delegate to your custom model binder when it reaches a DateTime property on the Profile. In addition, try using the valueProvider in the bindingContext instead of going directly to the form. These things can give you more flexibility.
More thoughts here: 6 Tips for ASP.NET MVC Model Binding.
Sometimes the model is a view-model, not a domain model. In this case you could benefit from separating those two and design the view model to match your view.
Now you can let the view model validate the input and parse the three fields into a DateTime. Then it can update the domain model:
public ActionResult SomeAction(ViewModel vm)
{
if (vm.IsValid)
{
var dm = repositoryOrSomething.GetDomainModel();
vm.Update(dm);
}
// more code...
}
I had the same exact situation the other day...below is my model binding code. Basically it binds all the DateTime? fields of a model to month/day/year fields from a form (if possible) So, yes, I do add in the validation here, since it does seem appropriate to do so.
public class DateModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
string DateMonth = _GetDateValue(bindingContext, propertyDescriptor.Name + "Month");
string DateDay = _GetDateValue(bindingContext, propertyDescriptor.Name + "Day");
string DateYear = _GetDateValue(bindingContext, propertyDescriptor.Name + "Year");
// Try to parse the date if we have at least a month, day or year
if (!String.IsNullOrEmpty(DateMonth) || !String.IsNullOrEmpty(DateDay) || !String.IsNullOrEmpty(DateYear))
{
DateTime fullDate;
CultureInfo enUS = new CultureInfo("en-US");
// If we can parse it, set the model property
if (DateTime.TryParse(DateMonth + "/" + DateDay + "/" + DateYear,
enUS,
DateTimeStyles.None, out fullDate))
{
SetProperty(controllerContext, bindingContext, propertyDescriptor, (DateTime?)fullDate);
}
// The date is invalid, so we need to add a model error
else
{
string ModelPropertyName = bindingContext.ModelName;
if(ModelPropertyName != "")
{
ModelPropertyName += ".";
}
ModelPropertyName += propertyDescriptor.Name;
bindingContext.ModelState.AddModelError(ModelPropertyName, "Invalid date supplied for " + propertyDescriptor.Name);
}
}
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
// Get a property from binding context
private string _GetDateValue(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
//Didn't work? Try without the prefix if needed...
// && bindingContext.FallbackToEmptyPrefix == true
if (valueResult == null)
{
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
}
if (valueResult == null)
{
return null;
}
return (string)valueResult.ConvertTo(typeof(string));
}
}
Note: I had some problems with bindingContext.FallbackToEmptyPrefix always being false...can't find any useful info on that, but you get the idea.
Validation should be done in multiple places, according to the functionality of each place. For example, if your model binder cannot find the submitted values into a proper DateTime value, then the binder can add a model state error. If, on the other hand, your business logic requires the date to be within a certain range, this would not be appropriate to do and the model binder; it should be in the business logic layer. Controllers can potentially add validation errors as well if, for example, the edit model cannot be transformed into an entity model.
A validation framework such as xVal makes this much simpler.
The Contact Manager sample application on the http://www.asp.net/mvc site has an excellent description of separating out your validation logic into a service layer from your controller and model.
It's well work a read
I tired of creating little small-purpose ViewModels that only touched parts of my mile-wide domain model.
Thus, I cooked up my own method for addressing this. My ViewModel is a typeOf DomainModel, and I use a custom model binder to ensure its identity properties load first - once identity is set - it triggers a DomainModel.Load, and the remainder of the binding activity essentially performs a 'merge'.
Again, when my ViewModel is bound (e.g. on a form POST), after essential fields comprising the ID are set - it immediately loads up the domain model from the database. I just had to come up with a replacment for the DefaultModelBinder. My custom model binder posted here on StackOverflow allows you to control the binding order of the properties.
Once I can guarantee that identity properties are bound, (the internals of my viewmodel listen for the completion of identity setters) I trigger a load of my domain model, as the rest of the properties are bound, they are overwriting, i.e. 'merging' into the loaded domain model.
Basically, I can have all my various razor views, whether they expose 5 form fields or 50 fields of the model.. all submit to a controller action that looks like this (granted, I still make separate actions where needed to do appropriate custom business stuff.. but the point is, my controller actions are focused and succinct)
<HttpPost()>
<Authorize(Roles:="MYCOMPANY\activeDirRoleForEditing")>
Function Edit(<Http.FromBody()> ByVal mergedModel As OrderModel) As ActionResult
'notice: NO loading logic here - it already happened during model binding
'just do the right thing based upon resulting model state
If Me.ModelState.IsValid Then
mergedModel.SaveAndReload("MyServiceWebConfigKey")
ViewBag.SuccessMessage = String.Format("You have successfully edited the order {0}", mergedModel.Id)
Return View("Edit", mergedModel)
Else
ViewBag.ErrorText = String.Format("Order {0} not saved. Check for errors and correct.", mergedModel.Id)
Return View("Edit", mergedModel)
End If
End Function