Parameter not passed to controller - Spring - spring-mvc

I am at wits end here. This seems a very trivial thing but its not working. I have a jsp page that passes back to the server a Long (tournamentId) and a list of Objects.
When I post the form, the list is passed properly, but the Long member comes back as null, even though I can see it was sent.
The jsp:
<form:form method="post" action="addBets" modelAttribute="gwbCollection">
<c:choose>
<c:when test="${gwbCollection.tournamentState == 'CLOSED_FOR_BETS'}">
<br>
</c:when>
</c:choose>
<input name="tournamentId" value="${gwbCollection.tournamentId}" type="hidden"/>
<table>
<tr>
<td>Side A:</td>
<td>Score A:</td>
<td>Side B:</td>
<td>Score B:</td>
</tr>
<c:forEach var="gwb" items="${gwbCollection.games}" varStatus="status">
<tr>
<td><input name="games[${status.index}].game.gameId" value="${gwb.game.gameId}" type="hidden"/>
<input name="games[${status.index}].userId" value="${gwb.userId}" type="hidden"/>
<input name="games[${status.index}].game.tournamentId" value="${gwb.game.tournamentId}" type="hidden"/>
<input name="games[${status.index}].bet.betId" value="${gwb.bet.betId}" type="hidden"/>
${gwb.game.sideA}</td>
<td><input name="games[${status.index}].bet.scoreA" value="${gwb.bet.scoreA}"/></td>
<td>${gwb.game.sideB}</td>
<td><input name="games[${status.index}].bet.scoreB" value="${gwb.bet.scoreB}"/></td>
</tr>
</c:forEach>
</table>
<c:choose>
<c:when test="${gwbCollection.tournamentState == 'OPEN_FOR_BETS'}">
<input type="submit" />
</c:when>
</c:choose>
</form:form>
The Controller:
#Controller
#SessionAttributes
public class BetController {
...
#RequestMapping(value = "/addBets", method = RequestMethod.POST)
public String addBet(#ModelAttribute("gwbCollection") GamesWithBetsCollection gwbCollection) {
List<Bet> bets = gwbUtil.getBets(gwbCollection);
...
}
And finally, GamesWithBetsCollection:
public class GamesWithBetsCollection {
private TournamentState tournamentState;
private Long tournamentId;
private List<GameWithBet> games;
public GamesWithBetsCollection() {
}
public List<GameWithBet> getGames() {
return games;
}
public void setGames(List<GameWithBet> games) {
this.games = games;
}
public TournamentState getTournamentState() {
return tournamentState;
}
public void setTournamentState(TournamentState tournamentState) {
this.tournamentState = tournamentState;
}
public Long getTournamentId() {
return tournamentId;
}
public void setTournamentId(long tournamentId) {
this.tournamentId = tournamentId;
}
}

Nickdos - WOW! That is the answer! Beautiful catch!
To recap - the field "tournamentId" is defined as Long (object), but the setter has long (primitive) as parameter. Changing it to Long(object) did the trick.
Thanks again Nickdos!

Related

Unable to pass view model list in CSHTML to controller

I have a View Model, View, and Controller that works great displaying the data, but I cannot get the data entered in the form to save to the controller. I've tried using a list, array, and a list for the view model.
Here's my view model:
public class AssignedHostData
{
public int HostID { get; set; }
public string HostName { get; set; }
public bool Assigned { get; set; }
[DisplayName("Additional Details")]
[DataType(DataType.MultilineText)]
public string AddDetails { get; set; }
}
Here's the section of my view that displays the data:
<table class="table">
<tr>
#{
int cnth = 0;
List<Support_Web.Models.ViewModels.AssignedHostData> hosts = ViewBag.Hosts;
foreach (var host in hosts)
{
if (cnth++ % 1 == 0)
{
#:</tr><tr>
}
#:<td>
<input type="checkbox"
name="selectedHosts[#cnth].HostID"
id="selectedHosts_[#cnth]_HostID"
value="#host.HostID"
#(Html.Raw(host.Assigned ? "checked=\"checked\"" : "")) />
#host.HostID #: #host.HostName
#:</td>
#:<td>
<input type="text" name="selectedHosts[#cnth].AddDetails" id="selectedHosts_[#cnth]_AddDetails" value="#host.AddDetails" />
#:</td>
}
#:</tr>
}
</table>
And here're the parameters from my Edit controller. The selectedProducts list returns an empty list every time:
public async Task<IActionResult> Edit(int? id, string[] selectedProducts, List<HostCheckListItem> selectedHosts)
My view needed to be formatted this way:
#{
int cnth = 0;
List<Support_Web.Models.ViewModels.AssignedHostData> hosts = ViewBag.Hosts;
for (int i = 0; i < hosts.Count; i++)
{
<tr>
<td>
<input type="checkbox"
name="selectedHosts[#i].HostID"
value="#hosts[#i].HostID"
#(Html.Raw(hosts[#i].Assigned ? "checked=\"checked\"" : "")) />
#hosts[#i].HostName
</td>
<td>
<input type="text" name="selectedHosts[#i].AddDetails" value="#hosts[#i].AddDetails" />
</td>
</tr>}
}
If you want to transfer the modified model data list in the view to
the controller, you can use "asp-for" to bind in the form.
Please refer to the following for details:
public class ProductController : Controller
{
private readonly MydbContext _context;
public ProductController (MydbContext context)
{
_context = context;
}
public IActionResult Index()
{
ViewBag.Hosts = _context.AssignedHostData.ToList();
return View();
}
public async Task<IActionResult> Edit(List<AssignedHostData> Hosts)
{
List<AssignedHostData> selectedHosts = Hosts.Where(x => x.Assigned == true).ToList();
return View();
}
}
Edit.cshtml:
<form asp-controller="Product" asp-action="Edit" method="post">
<table class="table">
<tr>
#{
int cnth = 0;
List<WebApplication_core.Models.AssignedHostData> Hosts = ViewBag.Hosts;
for (int i = 0; i < Hosts.Count; i++)
{
<tr>
<td>
<input id="Hidden1" type="hidden" asp-for="#Hosts[i].HostID" />
<input type="checkbox" asp-for="#Hosts[i].Assigned" />
#Hosts[i].HostName
<input id="Hidden2" type="hidden" asp-for="#Hosts[i].HostName" />
</td>
<td>
<input type="text" asp-for="#Hosts[i].AddDetails" />
</td>
</tr>
}
}
</table>
<input id="Button1" type="submit" value="Edit" />
</form>
Here is the debug result:

Asp.net core razor pages [BindProperty] doesnt work on collections

Im trying to use [BindProperty] annotation in asp.net core razor pages in order to Bind an Ilist<T> collection of one of my model classes so i can edit some of them at once, but it doesnt work at all, every time in OnPostAsync function the collection is empty, and neither the changes that i made on data nor it default values wont post back to the server, but when its a singel object [BindProperty] works fine and the values post back and can be changed, i also tried wraping a collection (i.e list<T>) in an object but it didnt work either way, so is there any way for doing so or i should lets say send a edit request for every object in that collection and edit them one by one(which cant be done in razor pages easilly and need some ajax calls)??
For binding IList between RazorPage and PageModel, you will need to use Product[i].Name to bind property.
Here are complete steps.
Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
PageModel
public class IndexModel : PageModel
{
private readonly CoreRazor.Data.ApplicationDbContext _context;
public IndexModel(CoreRazor.Data.ApplicationDbContext context)
{
_context = context;
}
[BindProperty]
public IList<Data.Product> Product { get; set; }
public async Task OnGetAsync()
{
Product = await _context.Product.ToListAsync();
}
public async Task OnPostAsync()
{
var product = Product;
}
}
View
<form method="post">
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Product[0].Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Product.Count(); i++)
{
<tr>
<td>
<input hidden asp-for="Product[i].Id" class="form-control"/>
<input asp-for="Product[i].Name" class="form-control" />
</td>
<td>
<a asp-page="./Edit" asp-route-id="#Model.Product[i].Id">Edit</a> |
<a asp-page="./Details" asp-route-id="#Model.Product[i].Id">Details</a> |
<a asp-page="./Delete" asp-route-id="#Model.Product[i].Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>

How to add a value to a class if the class contains LIST<class>

I have a class graf.
public class Graf
{
public List<Point> first { get; set; }
public List<Point> second { get; set; }
}
This class contains List
public class Point
{
public int x { get; set; }
public int y { get; set; }
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
I need to add a Point into class Graf from index.cshtml:
#model WebApplication2.Models.Graf
<table>
<tr>
<td></td>
<td>
<div class="item">
<label>Y</label>
<input name="Y11" value="#Model.first" /> --------??
</div>
</td>
</tr>
</table>
<input type="submit" value="Send" />
But i dont now how i can input into Graf class Point?
How can I do it?
Ok. So let's start from a client-side code.I suppose that you have a next Index.cshtml view.
<!-- You use this code to display data from your model -->
<table>
<tr>
<td></td>
<td>
<div class="item">
<label>Y</label>
<input name="Y11" value="#Model.first" /> --------??
</div>
</td>
</tr>
</table>
Than you need a code that post new Point object from your view to controller.It could be like something like this:
<form asp-controller="Home" asp-action="InsertPoint" method="post">
X value: <input type="text" name="x"><br>
Y value: <input type="text" name="y"><br>
<input type="submit" value="Submit">
</form>
In your controller you should create action with following signature
[HttpPost]
public async Task<IActionResult> InsertPoint(Point point)
{
//Validation and insertion to list
return View();
}
NB
It's not an ideal solution. You could perform this task in many different ways. My aim, is just to show you the basic idea how it could be done. If you need more information you could start from this article
And of course, keep in mind that google is your good friend.

Request method 'POST' not supported - SPRING

login.jsp
<div id="login" class="animate form">
<form action="${loginUrl}" method="POST">
<h1>Log in</h1>
<c:url var="loginUrl" value="/login" />
<c:if test="${param.error != null}">
<input type="text" class="alert-danger" id="danger" name="danger"
placeholder="Invalid username and password." disabled />
<br />
</c:if>
<c:if test="${param.logout != null}">
<input type="text" class="alert-success" id="success"
name="success"
placeholder="You have been logged out successfully." disabled />
<br />
</c:if>
<p>
<label for="username" class="uname" data-icon="u"> Your
email </label> <input id="username" name="login" required="required"
type="text" placeholder="mymail#atos.net" />
</p>
<p>
<label for="password" class="youpasswd" data-icon="p">
Your password </label> <input id="password" name="password"
required="required" type="password" placeholder="eg. X8df!90EO" />
</p>
<p class="keeplogin">
<input type="checkbox" name="remember-me" id="rememberme"
value="rememberme" /> <label for="rememberme">Remember
Me</label>
</p>
<p class="login button">
<input type="submit" value="Login" />
</p>
<p class="change_link"></p>
</form>
</div>
userlist.jsp
<div class="generic-container">
<%#include file="authheader.jsp" %>
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading"><span class="lead">List of Users </span></div>
<table class="table table-hover">
<thead>
<tr>
<th>Prenom</th>
<th>Nom</th>
<th>Matricule</th>
<th>Login</th>
<sec:authorize access="hasRole('ADMIN') or hasRole('READ')">
<th width="100"></th>
</sec:authorize>
<sec:authorize access="hasRole('ADMIN')">
<th width="100"></th>
</sec:authorize>
</tr>
</thead>
<tbody>
<c:forEach items="${users}" var="user">
<tr>
<td>${user.prenom}</td>
<td>${user.nom}</td>
<td>${user.matricule}</td>
<td>${user.login}</td>
<sec:authorize access="hasRole('ADMIN') or hasRole('READ')">
<td>edit</td>
</sec:authorize>
<sec:authorize access="hasRole('ADMIN')">
<td>delete</td>
</sec:authorize>
</tr>
</c:forEach>
</tbody>
</table>
</div>
<sec:authorize access="hasRole('ADMIN')">
<div class="well">
Add New User
</div>
</sec:authorize>
</div>
AppController.java
#Controller
#RequestMapping("/")
#SessionAttributes("roles")
public class AppController {
#Autowired
IService_User<USER> userService;
#Autowired
IService<COMPTE> compteService;
#Autowired
MessageSource messageSource;
#Autowired
PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices;
#Autowired
AuthenticationTrustResolver authenticationTrustResolver;
#RequestMapping(value = { "/", "/list" }, method = { RequestMethod.GET, RequestMethod.POST })
public String listUsers(ModelMap model) {
List<USER> users = userService.findAllOBJECTS();
model.addAttribute("users", users);
model.addAttribute("loggedinuser", getPrincipal());
return "userslist";
}
#RequestMapping(value = {"/login"}, method = { RequestMethod.GET, RequestMethod.POST })
public String loginPage() {
if (isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:/list";
}
}
}
There are 2 pages: login.jsp - start page which includes form to be populated with login and password - userlist.jsp list of results "display all users persisted in DB"..
First the login page is shown, when i click on submit button i got this error:
org.springframework.web.servlet.PageNotFound - Request method 'POST' not supported
In your login.jsp you are using http method POST
<form action="${loginUrl}" method="POST">
and in controller, you are using http method GET
#RequestMapping(value = {"/login"}, method = RequestMethod.GET)
public String loginPage() {
if (isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:/list";
}
}
Problem will be solved after changing method = RequestMethod.POST in your controller like this
#RequestMapping(value = {"/login"}, method = RequestMethod.POST)
public String loginPage() {
if (isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:/list";
}
}
In your login form you are explicitly making an POST request... and in your controller the the url is mapped to GET request.. this is the issue... Please make the controller as POST... like
#RequestMapping(value = {"/login"}, method = RequestMethod.POST)
public String loginPage() {
if (isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:/list";
}
}
Add post method in #RequestMapping annotation, like following;)
#RequestMapping(value = {"/login"}, method = {RequestMethod.GET, RequestMethod.POST})
public String loginPage() {
if (isCurrentAuthenticationAnonymous()) {
return "login";
} else {
return "redirect:/list";
}
}
if you are using spring security 4.x.x. , CSRF is enabled by default. therefore you have to provide the csrf filed in your form.
Adding the csrf token as hidden fields does the trick:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

Spring MVC application: Binding many-to-many list object in form

My Spring MVC web application makes use of two model classes Player and Team. There is ManyToMany relationship between Player and Team. Here are the model classes.
#Entity
#Table(name="TEAMS")
public class Team {
#Id
#Column(name="ID")
private int id;
#Column(name="NAME")
#Size(min=2, max=30)
private String name;
#ManyToMany(mappedBy="teams")
private Set<Player> players = new HashSet<Player>();
//getter and setter methods
}
#Entity
#Table(name="PLAYERS")
public class Player {
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="players_seq")
#SequenceGenerator(name="players_seq", sequenceName="PLAYERS_SEQ")
private int id;
#Column(name="LASTNAME")
#Size(min=2, max=30)
private String lastname;
#Column(name="FIRSTNAME")
#Size(min=2, max=30)
private String firstname;
#Column(name="BIRTHDAY")
#DateTimeFormat(pattern="MM/dd/yyyy")
#NotNull #Past
private Date dob;
#Column(name="PLAYING_ROLE")
#NotNull
private String playingRole;
#ManyToMany(cascade =CascadeType.ALL)
#JoinTable(name="TEAM_PLAYER",
joinColumns={#JoinColumn(name="PLAYER_ID")},
inverseJoinColumns={#JoinColumn(name="TEAM_ID")})
private Set<Team> teams = new HashSet<Team>();
//getter and setters
}
I am trying to assign team to player with the help of Checkboxes and my JSP looks like this
<table>
<form:form modelAttribute="player" method="POST"
action="${editPlayer }">
<tr>
<td>Last name:</td>
<td><form:input path="lastname" /> <form:errors
path="lastname" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>First Name:</td>
<td><form:input path="firstname" /> <form:errors
path="firstname" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Birthday:</td>
<td><form:input path="dob" /> <form:errors path="dob"
cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Playing Style:</td>
<td><form:input path="playingRole" /> <form:errors
path="playingRole" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Teams:</td>
<td><form:checkboxes items="${teams}" path="teams"
itemValue="id" itemLabel="name" /></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Update Player" /></td>
</tr>
</form:form>
</table>
My PlayerController.java looks like this
#Controller
public class PlayerController {
#Autowired
private PlayerService playerService;
#Autowired
private TeamService teamService;
#ModelAttribute("teams")
public List<Team> getTeams(){
return teamService.getTeams();
}
#RequestMapping(value="/player/edit/{id}", method=RequestMethod.GET)
public ModelAndView updatePlayerPage(#PathVariable int id){
Player player = playerService.getPlayer(id);
ModelAndView modelAndView = new ModelAndView("player/edit");
modelAndView.addObject("player", player);
return modelAndView;
}
#RequestMapping(value="/player/edit/{id}", method=RequestMethod.POST)
public ModelAndView updatePlayer(#ModelAttribute #Valid Player player, BindingResult result, #PathVariable int id){
if(result.hasErrors()){
System.out.println("HAS ERRORS!. Number of errors: "+result.getAllErrors().size());
for(Object o : result.getAllErrors()){
System.out.println(o);
}
System.out.println("\n Player details: "+ player );
return new ModelAndView("player/edit");
}
playerService.updatePlayer(player);
ModelAndView modelAndView = new ModelAndView("home");
String message = "Player updated successfully";
modelAndView.addObject("message", message);
return modelAndView;
}
//it has got some other methods also that does some other functionality
}
JSP Page is getting rendered properly but when I try to save I am getting exception
Field error in object 'player' on field 'teams': rejected value [5050]; codes [typeMismatch.player.teams,typeMismatch.teams,typeMismatch.java.util.Set,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [player.teams,teams]; arguments []; default message [teams]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Set' for property 'teams'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.sunil.model.Team] for property 'teams[0]': no matching editors or conversion strategy found]
Reason behind the exception is that I need to assign Team to Player but I am not sure how to do this. Can someone please help me out how to resolve this.
Thanks
You can use #RequestBody to encode form data to json and sent it to your controller method
OK finally I was able to solve it.. I hope it would be useful for others..
I updated JSP page
<form:form modelAttribute="player" method="POST"
action="${editPlayer }">
<tr>
<td>Last name:</td>
<td><form:input path="lastname" /> <form:errors
path="lastname" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>First Name:</td>
<td><form:input path="firstname" /> <form:errors
path="firstname" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Birthday:</td>
<td><form:input path="dob" /> <form:errors path="dob"
cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Playing Style:</td>
<td><form:input path="playingRole" /> <form:errors
path="playingRole" cssStyle="color:red;" /></td>
</tr>
<tr>
<td>Available Teams:</td>
<td><form:select path="teams" multiple="true" items="${teamCache}" itemLabel="name" itemValue="id"/></td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Update Player" /></td>
</tr>
</form:form>
I moved from Checkbox to Select. Reason behind this change is that (I felt) it would helpful for end user to do multiple select from dropdown.
I made couple of updates to my Controller.
#ModelAttribute("teamCache")
public List<Team> getTeams(){
return teamService.getTeams();
}
My Select makes use of teamsCache (items attribute in form:select)
#RequestMapping(value="/player/edit/{id}", method=RequestMethod.GET)
public ModelAndView updatePlayerPage(#PathVariable int id){
Player player = playerService.getPlayer(id);
List<Team> teams = teamService.getTeams();
teamCache = new HashMap<Integer, Team>();
for(Team team : teams){
teamCache.put((Integer)team.getId(), team);
}
ModelAndView modelAndView = new ModelAndView("player/edit");
modelAndView.addObject("player", player);
return modelAndView;
}
#InitBinder
protected void initBinder(WebDataBinder binder) throws Exception{
binder.registerCustomEditor(Set.class,"teams", new CustomCollectionEditor(Set.class){
protected Object convertElement(Object element){
if (element instanceof String) {
Team team = teamCache.get(Integer.parseInt(element.toString()));
return team;
}
return null;
}
});
}
#RequestMapping(value="/player/edit/{id}", method=RequestMethod.POST)
public ModelAndView updatePlayer(#ModelAttribute #Valid Player player, BindingResult result, #PathVariable int id){
if(result.hasErrors()){
return new ModelAndView("player/edit");
}
playerService.updatePlayer(player);
ModelAndView modelAndView = new ModelAndView("home");
String message = "Player updated successfully";
modelAndView.addObject("message", message);
return modelAndView;
}
I hope it would be useful for others. Also please let me know if there are any better solutions.
Thanks

Resources