Spring MVC, Binding, multi-select options following validation - spring-mvc

Thanks in advance for any help.
I have a form that is being validated with JSR 303. After validation fails, the controller returns the form, shows validation errors, and renders the form with the original values. This works fine with all types of form elements except the mutli-select element.
The command object:
public class TaskOrder implements Serializable {
private static final long serialVersionUID = 1L;
...
#XmlTransient
#ManyToMany
#<OtherJPAAnnotations...>
private List<Contractor> subcontractors;
...
}
Contractor class:
public class Contractor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#<OtherJPAAnnotations...>
private Integer id;
#<OtherJPAAnnotations...>
private String name;
}
Controller:
#RequestMapping(value="processingPath", method=RequestMethod.POST)
public String createNewTaskOrder(#Valid #ModelAttribute TaskOrder taskOrder,
BindingResult result,
Model model) {
...
if (!result.hasErrors()) {
//No binding errors, lots of processing...
else {
model.addAllAttributes(result.getModel());
model.addAttribute(taskOrder);
model.addAttribute("subs", myDAOInstance.getSubs());
return this.setupNewTaskOrder(model);
}
}
#RequestMapping("getFormPath")
public String setupNewTaskOrder(Model model) {
if (!model.containsAttribute("taskOrder")) {
TaskOrder taskOrder = new TaskOrder();
taskOrder.setId(0);
model.addAttribute(taskOrder);
}
return "_n/admin/taskOrder/new";
}
The form:
<form:form commandName="taskOrder" action="processPath">
...
<form:select path="subcontractors">
<form:options items="${subs}" itemValue="id" itemLabel="name"/>
</form:select>
...
</form>
When I open an existing "TaskOrder" with the same form, the values are selected in the "subcontractors" multi-select.
However, trying to add a new "TaskOrder", when it returns from validation the values aren't selected. I know that the selected values are being attached to the model and returned, but just not being selected in the select element.
Thanks for any help!

Thanks for the help. Biju...you were correct!
For those who may stumble across this issue, I added the "EqualsUtil" class described here to my project:
http://www.javapractices.com/topic/TopicAction.do?Id=17
Then added the following method to my Contractor class:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Contractor)) return false;
Contractor c = (Contractor) o;
//Here I ignore the other properties since "id" and "name" are what
//I'm primarily concerned with...
return EqualsUtil.areEqual(this.name, c.name) &&
EqualsUtil.areEqual(this.id, c.id);
}

Related

dropdown list in JSP from database

I'm new to Spring, I fear this question may be duplicate, as there are numerous ways to do get resource from database to drop-down which I don't understand, so I'm asking if anyone can help me by fixing my code or with their own code which matches with mine as it'll be easy for me to learn.
My POJO
#Entity
#Table(name = "emp69")
public class Emp {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String designation;
....Constructor with name & designation field...
......getters method and setters method.....
My Repository
public interface EmpRepository extends JpaRepository<Employee, Integer> {
#Modifying
#Query(
value = "select designation from employee",
nativeQuery = true
)
List<String> designation();
}
My Data Access Object
#Service
public class EmployeeDao {
#Autowired
private EmpRepository repo;
#Transactional
public List<String> desig() {
return repo.designation();
}
}
My Controller
#Controller
public class EmployeeController {
#Autowired
EmployeeDao dao;
#RequestMapping("/empform")
public String showform(Model m) {
List<String> designation= dao.designation();
m.addAttribute("designation", designation);
return "empform";
}
}
My JSP page
<select>
<c:forEach var="dd" items="${designation}">
<option><c:set var="dd.designation">${dd.designation }</c:set></option>
</c:forEach>
</select>
seems everything fine to me but when I execute, it gives
Property [designation] not found on type [java.lang.String]
error.
And on STS console
javax.el.PropertyNotFoundException: Property [designation] not found on type [java.lang.String]
Your Controller, Service & Repository part are correct but it seems like you haven't studied JSP well. I'll suggest you to take a step back from a big framework and individually learn each of its aspects first like JSP, JSTL, etc. Following this path will lead you to understanding of big frameworks and everything will be more clear.
Anyhow here's your answer..
<select>
<c:forEach var="dd" items="${designation}">
<option><c:set var="d" value="${dd}" /><c:out value="${d }"/></option>
</c:forEach>
</select>
just replace this part in your code..
Hope it helps

Validating a form if we have two model object data

Usually i have seen a server side validation in spring mvc like this for example there is customer page which contains customer related information like customerName ,customerAddress,customerPhoneNumber for this we going to have
customer model object then in the spring controller we are going to call like this
Spring Controller
#RequestMapping(value = "/customerRegistrationScreen")
public String customerRegistrationScreen(Model model) {
Customer customer= new Customer();
model.addAttribute("customer", customer);
return "customerRegistrationScreen";
}
#RequestMapping(value = "/doCustomerRegistration", method = RequestMethod.POST)
public ModelAndView registerCustomer(#ModelAttribute("customer") #Validated Customer customer, BindingResult result,Model model) {
if (result.hasErrors()) {
} else {
}
}
CustomerValidator class
#Component
public class CustomerValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Customer.class.equals(clazz);
}
#Override
public void validate(Object obj, Errors err) {
ValidationUtils.rejectIfEmpty(err, "name", "customer.name.empty");
ValidationUtils.rejectIfEmpty(err, "email", "customer.email.empty");
ValidationUtils.rejectIfEmpty(err, "gender", "customer.gender.empty");
ValidationUtils.rejectIfEmpty(err, "languages", "customer.languages.empty");
User user = (User) obj;
Pattern pattern = Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
if (!(pattern.matcher(customer.getEmail()).matches())) {
err.rejectValue("email", "user.email.invalid");
}
}
}
customerRegistration.jsp
<form:form method="post" modelAttribute="customer" action="doCustomerRegistration">
</form:form>
what if the jsp have two model object information like Customer and product information like customerName,customerAddress,customerPhoneNumber,productID,productName,productPrice here i like to have two model object like customer
& Product if i have two model object how can i map model attribute from jsp and Spring contoller and how can i do server side validation for both the validation
Although it is a good idea to keep models separate in each form, but for this particular use case it can be achieved by following below steps.
The best way to achieve this is to wrap both the Model attributes in one wrapper class and use it in validation.
Lets say Product class looks like this.
public class Product{
String productName;
// other fields and their getter setters
}
Create a wrapper class which wraps both models Customer and Product
public class CustomerProductWrapper {
private Customer customer;
private Product product;
//getter setter
}
In your validator class , change the implementation of supports() method as below
#Override
public boolean supports(Class clazz) {
return CustomerProductWrapper .class.equals(clazz);
}
2.1 Change the implementation of Validation Method as below
#Override
public void validate(Object obj, Errors err) {
//The object that you get now is CustomerProductWrapper object
// refer the fields using this object
ValidationUtils.rejectIfEmpty(err, "customer.name", "customer.name.empty");
ValidationUtils.rejectIfEmpty(err, "customer.email", "customer.email.empty");
ValidationUtils.rejectIfEmpty(err, "customer.gender", "customer.gender.empty");
ValidationUtils.rejectIfEmpty(err, "customer.languages", "customer.languages.empty");
CustomerProductWrapper cpw= (CustomerProductWrapper ) obj;
Pattern pattern = Pattern.compile("^[A-Z0-9._%+-]+#[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE);
if (!(pattern.matcher(cpw.getCustomer().getEmail()).matches())) {
err.rejectValue("customer.email", "user.email.invalid");
}
//validate a Product field
ValidationUtils.rejectIfEmpty(err, "product.productName", "product.name.empty");
}
In your controller mapping ,
public String customerRegistrationScreen(Model model) {
CustomerProductWrapper cpw= new CustomerProductWrapper ();
model.addAttribute("cpw", cpw);
return "customerRegistrationScreen";
}
And
#RequestMapping(value = "/doCustomerRegistration", method = RequestMethod.POST)
public ModelAndView registerCustomer(#ModelAttribute("cpw") #Validated CustomerProductWrapper cpw, BindingResult result,Model model) {
if (result.hasErrors()) {
} else {
}
}
And finally in your view page
<form:form method="post" modelAttribute="cpw" action="doCustomerRegistration">
</form:form>
Also refer fields using cpw's attributes that is
<form:input path="name" />
<form:errors path="name" cssClass="error" />
will change to
<form:input path="customer.name" />
<form:errors path="customer.name" cssClass="error" />
Similarly for product validation you can use
<form:input path="product.productName" />
<form:errors path="product.productName" cssClass="error" />
That's all.

When do we need data classes?

Im using asp.net core. Here is the basic way to use model with controller.
public class BookController : Controller
{
private readonly ApplicationDbContext _context { get; set; }
public BookController(ApplicationDbContext context)
{
_context = context;
}
public IActionResult Create(Book model)
{
// adding new model
}
public IActionResult Edit(Book model)
{
// modifying the model
}
public IActionResult Delete(Book model)
{
// removing the model
}
}
My question: when shall/should I implement the code inside the controller? When shall/should I implement it in another class?
Something like this:
public interface IBook
{
int Add(Book book);
int Update(Book book);
int Remove(Book book);
}
public class BookData : IBook
{
private readonly ApplicationDbContext _context { get; set; }
BookData(ApplicationDbContext context)
{
_context = context
}
public int Add(Book model)
{
// ...
return _context.SaveChanges();
}
// other implements...
}
Then, calling it inside controller:
public IActionResult Create(Book model)
{
var bookData = new BookData(_context);
int result = bookData.Add(model);
// ...
}
For the interface, I think it may be useful for the case: I have many controllers that require same action/method names.
Example: MessageController requires 3 actions/methods at least (Create/Add, Edit/Update, Delete/Remove). It's same to NotificationController class, CommentController class...
So, the interface can be improved to:
public interface IMyService<T> where T : class
{
int Add(T model);
int Update(T model);
int Remove(T model);
}
public class MyService<T> : IMyService<T> where T : class
{
private readonly ApplicationDbContext _context { get; set; }
public MyService(ApplicationDbContext context)
{
_context = context;
}
public int Add(T model)
{
Type type = typeof(model);
if (type == typeof(Book))
{
// adding new book model
}
else if (type == typeof(Comment))
{
// adding new comment model
}
// ...
return -1;
}
// other implements...
}
Do I misunderstand something?
If I read it correctly with data classes you actually means repository (which is an abstraction over the persistence layer). You should always encapsulate persistence logic behind a class (be it via repository pattern, command/query pattern or request handler) and use it instead of directly using the context in your service classes.
That being said, you can directly inject your BookData to your controller instead of the ApplicationDbContext. One thing you should consider you lose in your current implementation is the Unit of Work pattern. Right now, every add will instantly persist the data.
This may not be what you want, so you should move the _context.SaveChanges(); outside of the Add/Remove/Update methods and call it explicitly. This allows you to insert i.e. 10 records and if one of them fails, nothing will be persisted to the database.
But if you call _context.SaveChanges(); after each insert and you get an error in the 8th (of 10) records, then 7 get persisted and 3 will be missing and you get inconsistent data.
Controller shouldn't contain any logic at all, only do short validation of the input model (ModelState.IsValid check) and if its okay, call the services which do all the logic and report the result back to the user. Only in very simple tutorials and guides logic is put into the controller action for reasons of simplicity. In real world applications you should never do that. Controllers are much harder to unit test than service classes.

asp.net mvc custom attributes

I am trying to create a custom attribute in mvc to use it's parameters in a view as breadCrumb.
well, this is the code of the attribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class BreadCrumbAttribute : Attribute {
public BreadCrumbAttribute(string title, string parent, string url) {
this._title = title;
this._parent = parent;
this._url = url;
}
#region named parameters properties
private string _title;
public string Title {
get { return _title; }
}
private string _url;
public string Url {
get { return _url; }
}
private string _parent;
public string Parent {
get { return _parent; }
}
#endregion
#region positional parameters properties
public string Comments { get; set; }
#endregion
}
this is the call of the attribute
[BreadCrumbAttribute("tile", "parent name", "url")]
public ActionResult Index() {
//code goes here
}
this is a way of how I'd like to get the values. (this is a partial view)
System.Reflection.MemberInfo inf = typeof(ProductsController);
object[] attributes;
attributes = inf.GetCustomAttributes(typeof(BreadCrumbAttribute), false);
foreach (Object attribute in attributes) {
var bca = (BreadCrumbAttribute)attribute;
Response.Write(string.Format("{0}><a href={1}>{2}</a>", bca.Parent, bca.Url, bca.Title));
}
Unfortunately, the attribute didn't get call with the way I implement it. Although, If I add the attribute in Class instead of an Action method it worked.
How could I make it work?
Thanks
The problem is that you are using reflection to get the attributes for the class, so naturally it does not include attributes defined on the action method.
To get those, you should define an ActionFilterAttribute, and in the OnActionExecuting or OnActionExecuted method, you can use filterContext.ActionDescriptor.GetCustomAttributes() method (MSDN description here).
Note that with this solution, you will likely have two different types of attributes: The first one is the one you wrote, to define the breadcrumbs. The second is the one that looks at the attributes on the executing action and builds up the breadcrumb (and presumably adds it to the ViewModel or sticks it in HttpContext.Items or something).

spring mvc bind into 2 objects that have identical fields

I submit a form, lets say this form contains
<input name="address" ..>
and
<input name="billingAddress" ..>
i have 2 objects to which i need to bind to:
class Address {
String address;
..
}
class BillingAddress {
String address;
..
}
obviously billingAddress wont bind to address in BillingAddress without some magic.
lets say i have multiple identical fields in both Address and BillingAddress but on the form i prefix the billing inputs with billing, ie billingFirstName, billingLastName etc.
is there some elegant way i can bind to BillingAddress that i can reuse for similar problems?
(or is there a better way to solve this then what i have come up with?)
If you wand to use more than one ModelAttribute, you have to create a wrapper object, which holds an instance of each ModelAttribute. In your case I would create a wrapper object called "FormModel" which holds an instance of Address and an instance of a BillingAddress.
class FormModel {
private Address address;
private BillingAddress billingAddress;
// Getters and Setters
}
Now use FormModel as your ModelAttribute.
In your Form you can define your input-elements like:
<input name="address.address" ..>
<input name="billingAddress.address" ..>
Controller:
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(Model model, #ModelAttribute() FormModel formModel) {
// process formModel.getAddress()
// process formModel.getBillingAddress()
return "redirect:home";
}
If you use custom validators for Address and BillingAddress, you also have to create a FormModelValidator that calls the AddressValidator and BillingAddressValidator:
public class FormModelValidator implements Validator {
private final AddressValidator addressValidator;
private final BillingAddressValidator billingAddressValidator;
public FormModelValidator(AddressValidator addressValidator,
BillingAddressValidator billingAddressValidator) {
this.addressValidator = addressValidator;
this.billingAddressValidator = billingAddressValidator;
}
public boolean supports(Class<?> clazz) {
return FormModel.class.equals(clazz);
}
public void validate(Object target, Errors errors) {
FormModel formModel = (FormModel) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator,
formModel.getAddress(), errors);
} finally {
errors.popNestedPath();
}
try {
errors.pushNestedPath("billingAddress");
ValidationUtils.invokeValidator(this.billingAddressValidator,
formModel.getBillingAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}

Resources