Thymeleaf href to controller : Could not parse as expression - spring-mvc

I am using Thymleaf href inside a loop.
<th:block th:each="study : ${studyList}">
<tr>
<td th:text="${study.patient.name}"></td>
<td th:text="${study.status}"></td>
<td th:text="${study.description}"></td>
<td><a th:href="#{/updateStudy/{studyId} (studyId=${study.id})}>"</a>Update</td>
</tr>
</th:block>
And my controller definition is
#RequestMapping(value = "/updateStudy/{studyId}")
public void updateStudy(#RequestParam("studyId") long studyId
) {
....
}
The page is not rendering as I am getting parsing exception:
Could not parse as expression: "#{/updateStudy/{studyId}
(studyId=${study.id})}>"
Also in the html page it is complianing : Undefined attribute name ((studyId).
What I am missing here ? Kindly help.
UPDATE:
Apparently the expression should have been
<td><a th:href="#{/updateStudy/{studyId} (studyId=${study.id})}" >Update</a></td>
Now at least the parsing error is resolved. But I am getting this:
There was an unexpected error (type=Bad Request, status=400).
Required long parameter 'studyId' is not present
Hopefully it is not related to the href expression.
SOLVED
Just to complete the answer, I have changed the controller definition to
#GetMapping("/updateStudy/{studyId}")
public String updateSchedulePage(#PathVariable Long studyId, Model model) {
..}
And now everything works fine.

Try with this:
<td><a th:href="#{/updateStudy/${study.id}}" >Update</a></td>
Here is another problem in controller:
#RequestParam will be `#PathVariable `
Change to it:
#RequestMapping(value = "/updateStudy/{studyId}")
public void updateStudy(#PathVariable ("studyId") long studyId
) {
....
}

Related

Passing an object from view to controller

I want to add new message to database from my .jsp inputs via controller. I tried to just create new object of message in controller and put it in database and it works fine. But when I try to do it using inputs i receive error like this:
SEVERE [http-nio-8080-exec-2]org.springframework.web.servlet.tags.form.InputTag.doStartTag Neither BindingResult nor plain target object for bean name 'message' available as request attribute
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'message' available as request attribute
My code:
#Controller
public class DemoController {
#Autowired
UserService userService;
#Autowired
MessageService messageService;
#PostMapping("/messages/sendNewMessage")
public String sendNewMessage(#ModelAttribute("message") MessagesEntity tempMessage) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
tempMessage.setFromUsername(userService.getUser(auth.getName()));
messageService.sendNewMessage(tempMessage);
return "redirect:/messages";
}
}
JSP here
<form:form action="sendNewMessage" modelAttribute="message" method="POST">
<table>
<tbody>
<tr>
<td><label>Username:</label></td>
<td><form:input path="toUsername" /></td>
</tr>
<tr>
<td><label>Subject:</label></td>
<td><form:input path="subject" /></td>
</tr>
<tr>
<td><label>Content:</label></td>
<td><form:input path="content" /></td>
</tr>
<tr>
<td><label></label></td>
<td><input type="submit" value="Send" class="save" /></td>
</tr>
</tbody>
</table>
</form:form>
you are using action url as sendNewMessage in jsp view but in the controller, it is /messages/sendNewMessage.
You need to put MessagesEntity object in ModelAttribute.
Either define the ModelAttribute while loading the form page like below.
#RequestMapping(value = "/", method = RequestMethod.GET)
public String messageForm(Model model) {
model.addAttribute("message", new MessagesEntity());
return "messageFormPageName";
}
or Put below method in the controller which will be common for complete controller so ModelAttribute will be available always.
#ModelAttribute("message")
public MessagesEntity createModel() {
return new MessagesEntity();
}

Neither BindingResult nor plain target object for bean name xxx available as request attribute

"Neither BindingResult nor plain target object for bean name 'loginCommand' available as request attribute"
I keep getting this Binding result error and nothing I've tried seems to be making it stop. I've seen other posts for this question, but none of them seem to fix whatever issue I'm having.
This is the first controller of my new project and I had some issues getting the xml squared away. I think that's all fixed, but if nothing looks off I supposed the problem could be there. The weird thing is that all this code is almost straight copied from another project I have and it works just fine.
Also I'm running on glassfish if that matters at all. Thanks in advance!
edit: The webpage is /morencore/login.jsp. I tried going to login.html assuming that would bring it up, but it only seems to work when I go to login.jsp. I believe I tried changing my controller to map to the jsp instead, but that did not work.
here is my login.jsp page:
<form:form method="post" modelAttribute="loginCommand">
<form:errors cssClass="error" element="p" />
<table border="0">
<tr>
<td align="right">Username:</td>
<td><form:input path="userName" /> <form:errors path="userName" cssClass="error" /></td>
</tr>
<tr>
<td align="right">Password:</td>
<td><form:password path="password" /> <form:errors path="password" cssClass="error" /></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" id="submit" name="submit" value="Log In" disabled="disabled"></td>
</tr>
</table>
</form:form>
and here is my controller:
#Controller
#ControllerAdvice
#RequestMapping("/login.html")
public class LoginController {
protected final Logger logger = LogManager.getLogger(getClass());
#Autowired
protected LoginValidator loginValidator;
#RequestMapping(method= RequestMethod.GET)
protected String initializeForm(#ModelAttribute("loginCommand")LoginCommand loginCommand,
BindingResult result,
ModelMap model)
{
logger.info("INITIALIZING LOGIN FORM");
model.addAttribute("loginCommand", new LoginCommand());
return "login";
}
#InitBinder("loginCommand")
protected void initBinder(ServletRequestDataBinder binder) throws Exception
{
binder.addValidators(loginValidator);
}
#RequestMapping(method=RequestMethod.POST)
protected String onSubmit(#ModelAttribute("loginCommand")LoginCommand loginCommand,
BindingResult result,
HttpServletRequest request) throws Exception
{
logger.info("validating login input");
loginValidator.validate(loginCommand, result);
if (result.hasErrors())
{
result.reject("login.failure");
return "login";
}
UserDao userDao = new UserDao();
User user = userDao.by_name(loginCommand.getUserName());
if (user == null
|| !user.getName().equals(loginCommand.getUserName())
|| !user.getPassword().equals(loginCommand.getPassword()))
{
result.reject("login.failure");
return "login";
}
return "redirect:main.html";
}
}
Here is my LoginCommand class:
#XmlRootElement
public class LoginCommand
{
private String userName;
private String password;
/** blah blah blah getters and setters*/
}
Here is the full stack trace as requested:
Neither BindingResult nor plain target object for bean name 'loginCommand' available as request attribute
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'loginCommand' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:142)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:168)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:188)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:154)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.autogenerateId(AbstractDataBoundFormElementTag.java:141)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.resolveId(AbstractDataBoundFormElementTag.java:132)
at org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:116)
at org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:422)
at org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:142)
at org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:84)
at org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:80)
at org.apache.jsp.login_jsp._jspx_meth_form_input_0(login_jsp.java:233)
at org.apache.jsp.login_jsp._jspService(login_jsp.java:126)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:111)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:411)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:473)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:377)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1580)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:338)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.glassfish.tyrus.servlet.TyrusServletFilter.doFilter(TyrusServletFilter.java:305)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:250)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:652)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:591)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155)
at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:371)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:238)
at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:463)
at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:168)
at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:206)
at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:180)
at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:242)
at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133)
at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112)
at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:539)
at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112)
at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117)
Among other things, it seems like your mappings need to be modified. Here is what I would try. There are a lot of adjustments so no guarantees on whether it will work completely, but it should get you in the right direction.
#Controller
public class LoginController {
protected final Logger logger = LogManager.getLogger(getClass()); //look at SLF4J instead. Then you're not tied to a specific logger and you use a facade.
#Autowired //may want to use constructor wiring instead on these
private LoginValidator loginValidator;
#Autowired
private UserDao userDao; //this should be wired and not simply instantiated - Spring won't know about it otherwise
#Autowired
private LoginValidator loginValidator;
#GetMapping("/login")
public String initializeForm(Model model) {
logger.info("INITIALIZING LOGIN FORM");
model.addAttribute("loginCommand", new LoginCommand());
return "login";
}
#PostMapping("/loginPost")
public String onSubmit(#ModelAttribute("loginCommand") LoginCommand loginCommand,
BindingResult result) throws Exception {
logger.info("validating login input");
loginValidator.validate(loginCommand, result);
if (result.hasErrors()) {
result.reject("login.failure");
return "login";
}
User user = userDao.by_name(loginCommand.getUserName());
if (user == null
|| !user.getName().equals(loginCommand.getUserName())
|| !user.getPassword().equals(loginCommand.getPassword())) { //you should really refactor this and move it outside of your controller. Just keep routing code in your controller, not logic
result.reject("login.failure");
return "login";
}
return "main"; //you should return just "main" or redirect:/main depending on what you're trying to do - you want the JSP to be processed. Leaving off the extension allows you to change frameworks without changing the server-side code and allows the page to be compiled. You could switch to Thymeleaf, for example, and not touch any of this code.
}
}
Add an action to your form:
<form:errors cssClass="error" element="p" />
<table border="0">
<tr>
<td align="right">Username:</td>
<td><form:input path="userName" /> <form:errors path="userName" cssClass="error" /></td>
</tr>
<tr>
<td align="right">Password:</td>
<td><form:password path="password" /> <form:errors path="password" cssClass="error" /></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" id="submit" name="submit" value="Log In"></td>
</tr>
</table>
For the next developer reading your code, I'd rename LoginCommand to something closer to what it actually is - like UserDetailsAdapter or something along those lines. I am assuming that LoginCommand will implement UserDetails from Spring Security if you're using that.
You may also want to consider updating your UserDao to have the method called findOneByUsername instead of by_name. The naming convention can help you later when you use Spring Repositories.
Lastly, look at Project Lombok for your beans. It'll save you lots of headaches.

ASP.NET Razor: Send variable as parameter

My view contains the following table definition:
<table>
<thead>
<th>Title</th>
<th>Artist</th>
<th>Genre</th>
<th>Price</th>
</thead>
#foreach (var album in Model)
{
<tr>
<td>#album.Title</td>
<td>
#{
foreach (var artist in ViewBag.Artists)
{
if (artist.ArtistID == album.ArtistID)
{ #artist.Name }
}
}
</td>
<td>
#{
foreach (var genre in ViewBag.Genres)
{
if (genre.GenreID == album.GenreID)
{ #genre.Name }
}
}
</td>
<td>#album.Price</td>
<td>
<a asp-action="Details" asp-controller="Albums">Details </a>
<a asp-action="Edit" asp-controller="Albums">Edit </a>
<a asp-action="Delete" asp-controller="Albums">Delete</a>
</td>
</tr>
}
</table>
The Details, Edit, and Delete options in the final column need to be keyed to the AlbumID property of the album variable, in order to ensure that the redirect operates on the proper album. I had thought the parameter for this might be asp-parameter, much like the action is set by asp-action, but this does not appear to exist.
What is the proper method to pass a variable as a URL parameter (for instance, /Albums/Details/var), and what name is used to retrieve this on the other side?
Shyju's answer deals with passing the parameter; my attempts at retrieving the parameter thus far have been as follows:
<%=Url.RequestContext.RouteData.Values["id"]%> - Fails to load, invalid character <.
ViewContext.RouteData.Values["id"] - Fails to load, cannot compare int and object using ==.
RouteData.Values["id"] - Compilation error, name does not exist in current context.
When using link tag helper, You can pass additional information to the action method using the asp-route-[paramName] attribute. For example, if you want to pass the album id to the id parameter of your details action method, you can do this
<a asp-action="Details" asp-controller="Albums" asp-route-id="#album.ID">Details </a>
This will generate the markup like below
<a href='/Albums/Details/101'>Details</a>
Where 101 will be replaced by an actual Album Id value.
Assuming your Details action method has a parameter named id
public ActionResult Details(int id)
{
// to do : Get the album using the id and send something to the view
}

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'loginForm' available as request attribute

Getting java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'loginForm' available as request attribute. My login.jsp is as below.
<form:form method="POST" action="/loginPage" commandName = "loginForm">
<tr>
<td><form:label path="name">Username</form:label></td>
<td><form:input path="name" /></td>
</tr>
<tr>
<td><form:label path="age">Password</form:label></td>
<td><form:input path="age" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Submit"/>
</td>
</tr>
and my controller is
#RequestMapping(value="/loginPage",method = RequestMethod.POST)
public String processForm(#ModelAttribute("loginForm") LoginForm loginForm, BindingResult result,
Map model)
can anyone suggest how can i resolve this exception.
You need to add "loginForm" key to ModelMap before you start using it..
Example:
//This code needs to be invoked first
#RequestMapping(value="/onPageLoadOfLogin",method = RequestMethod.GET)
public String onLoadOfLoginPage(ModelMap m){
m.put("loginForm",new LoginForm());
}
model.put("loginForm",new LoginForm());
This means that, your command name should be exactly the same as the modelAttribute name... so if your commandName in your jsp file is "loginForm" then you model should add the attribute with the name "loginForm".
Please add model map attribute before rendering to login page.
<form:form method="POST" action="/loginPage" modelAttribute = "loginForm">
try adding modelAttribute in
also u need to first bind the form with the controller. To do this you can first perform a GET to perform the binding.
#RequestMapping(value="/loginPage",method = RequestMethod.GET)
public String processForm(#ModelAttribute("loginForm") LoginForm loginForm, BindingResult result,
Map model)
after this do POST. And submit button will do the binding of its own in POST method calling. Your form will be like
<form:form modelAttribute = "loginForm">

Thymeleaf send parameter from html to controller

I'm newbie in Thymeleaf. I'm trying to create simple crud application. I'm trying to delete object of Customer class on delete button. How can I set parameter(for example - id) to the method which called deleteUser using Thymeleaf. Here's my controller.
package controllers;
//imports
#Controller
public class WebController extends WebMvcConfigurerAdapter {
#Autowired
private CustomerDAO customerDAO;
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/results").setViewName("results");
}
//show all users
#RequestMapping(value="/users", method=RequestMethod.GET)
public String contacts(Model model) {
model.addAttribute("users",customerDAO.findAll());
return "list";
}
//show form
#RequestMapping(value="/users/add", method=RequestMethod.GET)
public String showForm(Customer customer) {
return "form";
}
//add user
#RequestMapping(value="/users/doAdd", method=RequestMethod.POST)
public String addUser(#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName,
#RequestParam("lastName") String email) {
customerDAO.save(new Customer(firstName, lastName, email));
return "redirect:/users";
}
//delete user
#RequestMapping(value="users/doDelete/{id}", method = RequestMethod.POST)
public String deleteUser (#PathVariable Long id) {
customerDAO.delete(id);
return "redirect:/users";
}
}
Here's my view.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Getting Started: Serving Web Content</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
List of users
Add new user
<table>
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Action</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}">Id</td>
<td th:text="${user.firstName}">First name</td>
<td th:text="${user.lastName}">Last Name</td>
<td th:text="${user.email}">Email</td>
<td>
<form th:action="#{/users/doDelete/}" th:object="${customer}" method="post">
<button type="submit">Delete</button>
</form>
</td>
</tr>
</table>
</body>
</html>
You do not need form to do this:
<td>
<a th:href="#{'/users/doDelete/' + ${user.id}}">
<span>Delete</span>
</a>
</td>
Blejzer answer is an easy and straight forward solution unless you are also working with spring security (always recommended) in which case you should prefer POST instead of GET for all modification operations such as delete to protect against CSRF attack. This is exactly why spring recommends logout be done like this only. In order for you to adapt to POST, change your controller to read this parameter from request parameters instead of path variable
//delete user
#RequestMapping(value="users/doDelete", method = RequestMethod.POST)
public String deleteUser (#RequestParam Long id) {
customerDAO.delete(id);
return "redirect:/users";
}
Add a hidden field in the form which posts with the id as name and it's value in hidden parameter
<form th:action="#{/users/doDelete}" th:object="${customer}" method="post">
<input type="hidden" th:field="${id}" />
<button type="submit">Delete</button>
</form>
On a side note, even if you are not using spring security, it is always recommended to use post for any entity modification operations (like delete or update). Saves you from lots of trouble on web in long run. Take a look at Quick Checklist for Choosing HTTP GET or POST for detailed information.
path variables can be set as:
<a th:href="#{/users/doDelete/__${user.id}__}"><span>Delete</span></a>

Resources