Getting null value for object bind with #ModelAttribute - spring-boot

Using - Spring Boot, Thymeleaf
I have following code under controller class:
public class HotelController {
private HotelService hotelService;
private HotelRepository hotelRepository;
#Autowired
public HotelController(HotelService hotelService,HotelRepository hotelRepository) {
this.hotelService = hotelService;
this.hotelRepository = hotelRepository;
}
#GetMapping("/index")
public String searchHotel(Model model) {
Hotel hotel = new Hotel();
model.addAttribute("searchObject",hotel);
model.addAttribute("cityList", hotelRepository.findListOfCities());
model.addAttribute("hotelList", hotelRepository.findListOfHotels());
return "search_hotel";
}
#PostMapping("/availabilityResult")
public String afterClickingSearch(#ModelAttribute("searchObject") Hotel hotel,Model model) {
if(hotelService.searchHotelResult(hotel)==null)
return "failure_page";
else {
model.addAttribute("hotelRoom_TypePriceGST",hotelService.findHotelPriceandRoomType(hotel.getCity(),hotel.getHotel()).split(","));
model.addAttribute("total", hotelService.findTotal(hotel.getCity(), hotel.getHotel()));
return "booking_confirmation";
}
}
#GetMapping("/navUserRegisterPage")
public String userRegisterPage(Model model){
User user = new User();
model.addAttribute("userObj", user);
return "user_registerForm";
}
#PostMapping("/reserveUser")
public String reserveUserHandler(#ModelAttribute("userObj") User user,#ModelAttribute("searchObject") Hotel hotel,Model model) {
System.out.println(hotel.getHotel());
System.out.println(user.getGuest_name());
model.addAttribute("hotelName", hotel.getHotel());
model.addAttribute("userName", user.getGuest_name());
return "confirmation_page";
}
}
I can find hotel value(usig hotel.getHotel()) under handler method- public String afterClickingSearch()
But when I try to find the hotel value(using hotel.getHotel()) under handler method -reserveUserHandler(); I get null as value.
Please help with how can I retrieve hotel value under reserveUserHandler() method.

It appears as though you have a Hotel backed form on the "search_hotel" page and the action goes to afterClickingSearch(). You can access the Hotel model attribute in afterClickingSearch() because you are submitting the attributes of the Hotel in your form. When you transition to "booking_confirmation" and other pages it's not apparent that you are passing the hotel's id to maintain state.
You have a few options, some more elegant than others. You can pass around the hotel's id via a request parameter. A second option is to include a hidden Hotel id in all forms from which reserveUserHandler() is reachable, making sure each form is initialized with the id, and lookup the hotel by id in reserveUserHandler(). A third option, which I wouldn't recommend, is to make Hotel or Hotel id a session attribute. See this for more information on session attributes. When you use session attributes you need to be wary of multiple browser tabs and how unique state is maintained across each tab.

Related

why Spring mvc redirect not working well?

#PostMapping("/regist")
public String regist(Person person, Model model) {
Person p = new Person("name", "age");
model.addAttribute("person", p); //add person to model
model.addAttribute("hobby", "reading);
return "redirect:/info";
}
#GetMapping("/info")
public String info() {
return "result";
}
Why (person) model.addAttribute("person", p) not appended to url like (hobby) when redirecting?
Model attributes are not used for redirects. Have a look at RedirectAttributes instead.
A specialization of the Model interface that controllers can use to
select attributes for a redirect scenario. Since the intent of adding
redirect attributes is very explicit -- i.e. to be used for a redirect
URL, attribute values may be formatted as Strings and stored that way
to make them eligible to be appended to the query string or expanded
as URI variables in org.springframework.web.servlet.view.RedirectView.
This interface also provides a way to add flash attributes. For a
general overview of flash attributes see FlashMap. You can use
RedirectAttributes to store flash attributes and they will be
automatically propagated to the "output" FlashMap of the current
request.
Example usage in an #Controller:
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(Account account, BindingResult result, RedirectAttributes redirectAttrs) {
if (result.hasErrors()) {
return "accounts/new";
}
// Save account ...
redirectAttrs.addAttribute("id", account.getId()).addFlashAttribute("message", "Account created!");
return "redirect:/accounts/{id}";
}
A RedirectAttributes model is
empty when the method is called and is never used unless the method
returns a redirect view name or a RedirectView.
After the redirect, flash attributes are automatically added to the
model of the controller that serves the target URL.

how to pass object between two controllers in spring mvc?

Actually I've googled to see how we can pass data from one controller to another in Spring MVC and I found that using flash attributes do the job.
It consists on declaring one controller with RequestMapping=GET and an other with RequestMapping=POST. I have tested this code in my controller and it worked.
#RequestMapping(value ="/ajouter", method = RequestMethod.POST)
public String add(#ModelAttribute User user,final RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("user", user);
return "redirect:/account";
}
and the other:
#RequestMapping(value ="/account", method = RequestMethod.GET)
public String addAccount(#ModelAttribute("user") User user,#ModelAttribute Account account) {
System.out.println(user.getId());
return "account";
}
But I have another case: I have two JSPpages:
The first will add a user into the database. I want to recuperate the id of the user just inserted to set it as a foreign key for the account (after submitting the page for adding a user, a second page for adding an account appears).
I've tested this code:
This controller will insert a user into the database and recuperate the user which has been just inserted.
#RequestMapping(value ="/addUser", method = RequestMethod.POST)
public String add(#ModelAttribute User user,final RedirectAttributes redirectAttributes) {
this.userService.insertData(user);
redirectAttributes.addFlashAttribute("user", user);
return "redirect:/account";
}
and this controller will insert an account into the database, but can not recuperate the user id of the user just inserted.
#RequestMapping(value= "/ajouterCompte", method = RequestMethod.POST)
public String addCompte(#ModelAttribute("account") Account compte,#ModelAttribute("user") User user){
System.out.println(user.getId()); //this code shows 0
System.out.println(compte.getPwd());
String hashPassword = passwordEncoder.encode(compte.getPwd());
System.out.println(hashPassword);
compte.setPwd(hashPassword);
compte.setId(user.getId());
this.compteService.insertCompte(compte);
return "redirect:/users";
}
I guess that the problem is that the 2 controllers are declared with a method method = RequestMethod.POST when it should be one GET and an other POST. But in this case how can I recuperate the id of the user so that it could be inserted into the database?
When I declare the second controller with GET, the insertion of the database fails!
Need your help please :(
The object which are set as flash attributes are only available for the first request after those have been set. (As also explained in the reference guide).
Assuming that in your account controller you have a GET based method for /account pre-populate the Account with the information at that point.
public String account(#ModelAttribute("user") User user, Model model) {
Account account = new Account();
account.setId(user.getId());
model.addAttribute("account", account);
return "account";
}
Now you can either annotate your controller with #SessionAttribute("account") to have the Account stored in the session in between requests. If so then you have to modify your POST handling method to include a SessionStatus object and after storing call setComplete() on it.
public String addCompte(#ModelAttribute("account") Account compete, SessionStatus sessionStatus){
...
sessionStatus.setComplete();
return "redirect:/users";
}
Or store the pre-filled information in hidden form fields so that it gets passed along with the following request and the Account can be reconstructed.
<form:hidden path="id" />

Spring MVC and Thymeleaf Prevent Entity Id Leak

I have Demand entity. I can update my entity without any problem but I think my approch have some security problem.
demandController
#RequestMapping(value = "/details/{id}", method = RequestMethod.POST)
public String updateDemand(#PathVariable("id") Long id, #Valid #ModelAttribute Demand demand, BindingResult result) {
if (result.hasErrors()) {
return "demandUpdateForm";
} else {
demand.setDemandId(id);
demandService.updateDemand(demand);
return "redirect:/demands";
}
}
serviceImpl
#Override
public Demand updateDemand(Demand demand) {
return demandRepository.save(demand);
}
form
<form id="vendorForm" th:action="#{/demands/details/__${demand.demandId}__}" th:object="${demand}" method="post" >
As you see I get DemandId from action. For example I want to update 5th id's demand and get the update form. Then I changed demandId via developer tools and click submit. If I modify id for example 2nd and form update my 2nd id demand not original the 5th one. How can I prevent this situation.
I think it would be better if you create unmanaged bean for this operations and will pass it as form backing bean.
public class DemandBean {
private Long id;
private String name;
...
// more fields
}
Controller :
#RequestMapping(value = "/details/update", method = RequestMethod.POST)
public String updateDemand(#Valid #ModelAttribute("demandBean") DemandBean demandBean, BindingResult result) {
if (result.hasErrors()) {
return "demandUpdateForm";
} else {
demandService.updateDemand(demandBean.getId(), demandBean.getName, ...);
return "redirect:/demands";
}
}
Service method :
#Override
public void updateDemand(Long id, String name, //etc) {
Demand d = id == null ? new Demand() : demandRepository.findOne(id);
d.setName(name);
// ...
// set other fields
return demandRepository.save(demand);
}
This approach helps you to avoid security leaks with passing id.

Is my MVC approach correct?

I'm writing an app in MVC 5 right now. I've made MVC app(for iOS) some time ago, but honestly i'm a little bit confused right now. I tried to find some info about this pattern, but it seems that there are many approaches.
My app uses external database operating on JSON format. I have bunch of methods in Api class that return objects filled with data from database. In my opinion this Api class is basically model, but i am not sure.
Model:
//Model
public class UserModel
{
public string Name { get; set; }
public string Lastname { get; set; }
}
Api class(Model?):
public class Api
{
public UserModel GetUserData()
{
UserModel model = new UserModel();
//code connecting to DB and filling UserModel object
return model;
}
}
Controller:
//Controller
public ActionResult Index ()
{
UserModel model = new UserModel();
Api api = new Api();
model = api.GetUserData();
return View(model);
}
View:
#*View*#
#model application.Models.UserModel
#Model.Name
#Model.Lastname
And my last question. Where should i put methods like loginuser, delete user:
public void DeleteUser(UserModel model)
{
//code deleting user
}
Should it go to model or controller? What i think is - if it will be used multiple times in different places i should put it in model, otherwise it should go to controller.
Thanks in advance.
Your Api class is not a model, the model classes must be entities of your domain. Your domain is the core of your business, for example, if your are making a School Application, your models/entities are: Student, Teacher, Discipline ...
So your Api belongs to your infrasctucture layer, and all classes with database access responsability.
Your controller's must only orchestrate the workflow, for example:
private readonly api;
public HomeController()
{
this.api = new Api();
}
public ActionResult Index ()
{
var model = api.GetUserData();
return View(model);
}
Your Api probably will be used on other Actions, so you can make a private field and initialize it from constructor. But the best approach is to use an IoC container.
The LoginUser must be a action on your Controller, but the business rule could be on other layer of your project, so it can be reusable. For example:
public ActionResult Login(string login, string password)
{
var user = api.GetUserByLogin(login);
if(user == null)
{
ViewBag.ErrorMsg = "There is no user with this login";
return View();
}
//userService is a class responsible for business rules for users
var isSuccess = userService.LoginUser(login, password);
if(isSuccess)
return RedirectToAction("Index", "Home");
ViewBag.ErrorMsg = "Password is incorrect";
return View();
}
Be careful because the ASP.NET MVC still have the concept of ViewModels. That are classes where we use to show data in our Views. In our school application, for example, we could have a View which is necessary to show a Discipline with all students enrolled, and what professor. So we need to use information of 3 entities, in that case we make a ViewModel to show all this information together.

Force Initialization of #ModelAttributes in Spring MVC 3.1

I am writing a wizard-like controller that handles the management of a single bean across multiple views. I use #SessionAttributes to store the bean, and SessionStatus.setComplete() to terminate the session in the final call. However, if the user abandons the wizard and goes to another part of the application, I need to force Spring to re-create the #ModelAttribute when they return. For example:
#Controller
#SessionAttributes("commandBean")
#RequestMapping(value = "/order")
public class OrderController
{
#RequestMapping("/*", method=RequestMethod.GET)
public String getCustomerForm(#ModelAttribute("commandBean") Order commandBean)
{
return "customerForm";
}
#RequestMapping("/*", method=RequestMethod.GET)
public String saveCustomer(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the customer data ];
return "redirect:payment";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String getPaymentForm(#ModelAttribute("commandBean") Order commandBean)
{
return "paymentForm";
}
#RequestMapping("/payment", method=RequestMethod.GET)
public String savePayment(#ModelAttribute("commandBean") Order commandBean, BindingResult result)
{
[ Save the payment data ];
return "redirect:confirmation";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String getConfirmationForm(#ModelAttribute("commandBean") Order commandBean)
{
return "confirmationForm";
}
#RequestMapping("/confirmation", method=RequestMethod.GET)
public String saveOrder(#ModelAttribute("commandBean") Order commandBean, BindingResult result, SessionStatus status)
{
[ Save the payment data ];
status.setComplete();
return "redirect:/order";
}
#ModelAttribute("commandBean")
public Order getOrder()
{
return new Order();
}
}
If a user makes a request to the application that would trigger the "getCustomerForm" method (i.e., http://mysite.com/order), and there's already a "commandBean" session attribute, then "getOrder" is not called. I need to make sure that a new Order object is created in this circumstance. Do I just have to repopulate it manually in getCustomerForm?
Thoughts? Please let me know if I'm not making myself clear.
Yes, sounds like you may have to repopulate it manually in getCustomerForm - if an attribute is part of the #SessionAttributes and present in the session, then like you said #ModelAttribute method is not called on it.
An alternative may be to define a new controller with only getCustomerForm method along with the #ModelAttribute method but without the #SessionAttributes on the type so that you can guarantee that #ModelAttribute method is called, and then continue with the existing #RequestMapped methods in the existing controller.

Resources