Não estou conseguindo passar os dados da View para o Controller

Olá amigos! Feliz ano novo adiantado!

Estou empacado num problema, vou descrevê-lo e colocar um link para um gist com os arquivos relevantes.

Tenho um serviço que faz o cadastro de usuários. Esse cadastro é feito através de um form, mas o role do usuário não é determinado nesse form. O sistema tem 3 roles disponíveis e eu preciso conseguir, através de uma tela de edição, mudar o role dos usuários conforme minha necessidade. (as

O form está funcionando perfeitamente, a tabela que lista os usuários também e o que eu preciso é passar as informações do usuário para a tela de edição. Porém, quando eu clico no botão para me levar até o form de edição, não consigo acessar as informações pelo método no meu controller. Fiz diversas tentativas diferentes, porém o resultado sempre foi o um sysout com o usuário null.

Espero que a explicação não tenha ficado muito esquisita. Segue abaixo o gist com os arquivos relevantes.
arquivos

vou deixar elencado aqui onde está meu problema
lista.jsp

 <c:forEach items="${usuarios}" var="usuario">
          <tr>
            <td>${usuario.nome}</td>
            <td>${usuario.email}</td>
            <td>${usuario.roles}</td>
            <td>
              <form:form servletRelativeAction="${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build()}"
                method="POST" modelAttribute="email">
                <input type="image" name="usuario" src="${resPath}/imagens/editar.png" alt="Editar" title="Editar" >
              </form:form>
            </td>
          </tr>
        </c:forEach>

UsuarioController.java

@RequestMapping("/roles/{email}")
  public ModelAndView editarForm(@PathVariable("email") String email) {
    ModelAndView mav = new ModelAndView("/usuarios/editar");

    Usuario usuario = usuarioDao.loadUserByUsername(email);
    mav.addObject("usuario", usuario);

    List<Role> listaRoles = roleDao.listar();
    mav.addObject("listaRoles", listaRoles);

    return mav;
  }

ambos os códigos acima já foram bastante alterados e com tentativas diferentes: tentar passar o usuário através do form:form e receber como parâmetro no método, usar o modelAttribute para marcar o usuário e receber isso através da anotação no controller, tentar passar o email e localizar o usuário pelo email (como feito acima pelo método loadUserByUsername()). Nem lembro todas as tentativas. A última, essa que está aí agora, foi uma tentativa baseada em outro canto do projeto que funciona ao carregar detalhe dos produtos. O erro que recebi nessa tentativa (e já ocorreu em outras também) foi:

javax.persistence.NoResultException: No entity found for query

não colei o stacktrace todo, porque só nessa linha já dá pra notar que ele não conseguiu encontrar o usuario pelo email. No caso, esqueci de fazer o sysout nessa vez, já tinha apagado de dentro do método editarForm, mas o resultado seria uma impressão de valor nulo do email que deveria estar sendo recebido.

Galera, me dá uma luz, o que eu estou deixando passar? Realmente não faço ideia =/

Grato,

Vinícius

Você tem que mudar para GET, ou:

Você tem que adicionar o atributo post na anotação.
Tem que adicionar o consumes e talvez o produces também passando o MediaType.

A anotação correta passa a ser @RequestBody seguido de String email

Por ser uma consulta, a semântica é do verbo GET, então o recomendado é trocar de POST para GET, mantendo o método como se encontra, ou seja, sem as alterações propostas.

Essa questão do GET e do POST, eu já tinha pensado. Pra mim, faz total sentido que seja o GET a ser utilizado, pois não estou fazendo uma alteração de dados, apenas uma consulta. Tentei com GET várias vezes e não consegui, aconteceu que quando enviei minha dúvida, estava tentando com o POST para ver se mudava algo. Bom, mudei para o GET de novo (tirei o modelAttribute porque ele não estava fazendo diferença):

              <form:form servletRelativeAction="${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build()}"
                method="GET">
                <input type="image" name="usuario" src="${resPath}/imagens/editar.png" alt="Editar" title="Editar" >
              </form:form>

e depois no controller:

  @RequestMapping(value = "/roles/{email}", method = RequestMethod.GET, consumes = "application/x-www-form-urlencoded")
  public ModelAndView editarForm(@RequestBody String email) {
    ModelAndView mav = new ModelAndView("/usuarios/editar");

    Usuario usuario = usuarioDao.loadUserByUsername(email);
    mav.addObject("usuario", usuario);

    List<Role> listaRoles = roleDao.listar();
    mav.addObject("listaRoles", listaRoles);

e recebi:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'null' not supported

Que é o que eu venho recebendo sempre e por isso o título do meu tópico.

Depois então tentei com o POST novamente, usando as dicas que você me deu.

              <form:form servletRelativeAction="${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build()}"
                method="POST">
                <input type="image" name="usuario" src="${resPath}/imagens/editar.png" alt="Editar" title="Editar" >
              </form:form>
  @RequestMapping(value = "/editar/{email}", method = RequestMethod.POST, consumes = "application/x-www-form-urlencoded")
  public ModelAndView editarForm(@RequestBody String email) {
    ModelAndView mav = new ModelAndView("/usuarios/editar");

    Usuario usuario = usuarioDao.loadUserByUsername(email);
    mav.addObject("usuario", usuario);

    List<Role> listaRoles = roleDao.listar();
    mav.addObject("listaRoles", listaRoles);

    return mav;
  }
java.lang.IllegalStateException: Invalid target for Validator [br.com.casadocodigo.loja.validation.UsuarioValidation@d0a138f]: usuario.x=15&usuario.y=5&_csrf=15f13ef7-23bf-42c9-9863-90e4104400f4
	at org.springframework.validation.DataBinder.assertValidators(DataBinder.java:568)
	at org.springframework.validation.DataBinder.addValidators(DataBinder.java:579)
	at br.com.casadocodigo.loja.controllers.UsuarioController.initBinder(UsuarioController.java:36)

Eu não entendi porque ele teve problemas com o initBinder nesse momento, porque eu não invoco ele durante o uso do método editarForm. Não adicionei o produces como atributo da anotação, porque a intenção é só pegar a lista de roles no rolesDao (o que é feito com sucesso, quando eu tiro o método que tenta buscar o usuário do método, consigo a lista de roles tranquilamente), pegar os roles que estão salvos no usuário e jogar para um form:checkboxes para poder mexer nisso depois. Essa parte de fazer a atualização dos roles para o usuário, ainda nem mexi, porque ainda não estou conseguindo recuperar o usuário através do método. E eu sei que o problema não está no form:checkboxes porque no editar.jsp, ele está comentado:

    <div class="container">
      <h1>Cadastro de Permissões para ${usuario.nome}</h1>
      <p>${usuario.roles}</p>
      <p>${listaRoles}</p>
<%--       <form:checkboxes items="${listaRoles}" path="${roles}"/> --%>
    </div>

obs: Eu coloquei consumes = “application/x-www-form-urlencoded” porque foi o que tinha aparecido no stacktrace quando coloquei consumes = “text/plain”, que tinha sido o que eu imaginei que precisava consumir.

Você quer pesquisar um email(texto plain), mas está passando uma imagem no seu form.

Se você que editar a imagem usa PUT, se quer cadastrar usa POST.

Veja, quando você usa o GET as informações transitam por meio da URL.

Quando você usa POST as informações transitam por meio do Body.

Se você analisar o seu form, quando você usa o GET a url sai assim : urlDestino?usuario = "dataImage"

O seu form deveria conter um input type= "text" name = "email"
Aí a url sai assim:
urlDestino? email = "inputEmail.value"

Na anotacao:

@RequestMapping(value = "/roles/{email}")// o GET é default
  public ModelAndView editarForm(@RequestParam("email") String email) 

Obs.: use o PostMan para fazer estes testes.

Obs.: não tenho como ajudar muito com o Spring, comecei a estudar esta semana.

Eu resumi a burocracia da arquitetura rest no POST recebendo e transmitindo JSON.

Ou seja, CREATE, READ, UPDATE, DELETE estão todos contidos em um JSON no POST.

Por quê?
A linguagem deve servir ao programador, não o contrário.
Assim não sofro com a burocracia do rest.

Teste com o POSTMAN, se der certo, o problema será no form e não no end point.

puts, pior que eu to usando o input pra criar um botão e esse botão precisa ser uma imagem. Aí ele vai me levar para o form de edição. Nesse form de edição, vai ter as checkboxes como eu mencionei e um botão (submit normal) para atualizar - essa é a parte que eu vou mexer depois, porque por enquanto tenho que resolver isso, pra ver se estou recebendo direitinho as informações.

quanto ao spring, eu fiz um curso já, esse é o projeto de finalização. Quando eu precisei usar o rest, foi de boas (uma parte anterior do projeto, onde eu recebo um json com pedidos). O meu problema tá em acertar essa parte do jsp, que enquanto eu tava fazendo, estava justamente achando que seria a parte de boa hahaha, porque na hora de atualizar as informações, eu vou ter que lidar com uma consulta no BD que tem relação ManyToMany.

mas você já me deu uma luz. O problema está em criar esse botão com o input com tipo imagem, sendo que eu quero passar outra coisa. Tenho que dar um jeito de criar esse botão com a imagem, sem passar imagem na requisição.

EDIT:

fiquei confuso agora. Para mim, o type do input só definia como seria aquele campo e não o que eu tô passando. Outra coisa é que em outra parte do projeto, tem um pedaço que faz mais ou menos a mesma coisa (utiliza um botão de formato de imagem para passar dados ao controller e fazer uma ação) que é assim oh:

<form:form action="${s:mvcUrl('CCC#remover').arg(0, item.produto.id).arg(1,item.tipoPreco).build() }" method="POST">
  <input type="image" src="${contextPath }resources/imagens/excluir.png" alt="Excluir" title="Excluir" />
</form:form>
@RequestMapping("/remover")
public ModelAndView remover(Integer produtoId, TipoPreco tipoPreco) {
  carrinho.remover(produtoId, tipoPreco);
  return new ModelAndView("redirect:/carrinho");
}

inclusive, esse trecho de código tinha sido minha base inicialmente, fui tentando outros approachs conforme foi dando errado. No caso desse exemplo que eu dei aí em cima, ele passa o produtoId e o tipoPreco através do arg(0, item.produto.id).arg(1,item.tipoPreco) e é a mesma coisa que eu estou tentando fazer aqui

${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build()}

As minhas ideias realmente acabaram, por isso acabei recorrendo a postagem aqui :pleading_face::pensive:

Aqui você não especificou nenhum valor as variáveis o que como consequência ira ser nulo.

e outra coisa aqui tem dois parâmetros sendo que no RequestMapping você não passa nenhum parâmetro

Olá, Jhonatas

cara, eu acho que você entendeu errado. Essa parte que você citou, funciona. Eu me baseei nela para tentar implementar a parte que não está funcionando, que é a parte relativa a edição de permissões

@RequestMapping(value = "/roles/{email}")// o GET é default
  public ModelAndView editarForm(@RequestParam("email") String email) 

que é o que eu faço nesse método aqui em cima.

Também não entendi isso de não especificar valor para as variáveis. Eles são parâmetros que eu recebo através da view, na hora que eu invoco o

<form:form action="${s:mvcUrl('CCC#remover').arg(0, item.produto.id).arg(1,item.tipoPreco).build() }" method="POST">
  <input type="image" src="${contextPath }resources/imagens/excluir.png" alt="Excluir" title="Excluir" />
</form:form>

pra ficar mais claro o que eu tenho. Quando eu clico no botão de editar, é para me levar à outra tela, passando a lista de roles disponíveis (que eu pego através do dao e ESTÁ funcionando) e ter o checkbox marcado com o que o usuário tem (nesse caso, o checkbox de ROLE_ADMIN apenas). Só que eu não estou conseguindo recuperar os dados do usuário no controller que deveria estar vindo pela view

em vez de retornar isto

porque não Retorna um ResponseEntity<?> e passa os parametros dentro do método,pra ver se o método esta retornando algo e testa isso no Postman

Outra coisa qual tecnologia voce está usando para fazer seu front-end

Então, o método retorna, porque se eu tiro isso

Usuario usuario = usuarioDao.loadUserByUsername(email);

a página que eu estou chamando retorna tranquilamente. Não estou no pc agora, mas assim que eu tiver, eu mando um print da tela com o retorno do ModelAndView pra você ver. Eu sei que o problema não está no retorno do método, mas sim na consulta que eu tento fazer recebendo o email como parâmetro. Já tentei receber o próprio usuário e não foi, eu simplesmente não estou conseguindo recuperar os dados a partir da view (no caso, lista.jsp)

Quanto ao que estou usando para mexer nisso tudo é o SpringMVC. Esse código faz parte de um projeto pra finalizar um curso de Java. Até agora tava indo bem, os poucos problemas que tive consegui googlar e achei coisas aqui, no stackoverflow e no forum do próprio curso. Mas esse tá tenso

lista.jsp

<c:forEach items="${usuarios}" var="usuario">
          <tr>
            <td>${usuario.nome}</td>
            <td>${usuario.email}</td>
            <td>${usuario.roles}</td>
            <td>
              <form:form action="${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build() }" method="GET">
                <input type="image" src="${resPath}/imagens/editar.png" alt="Editar" title="Editar"/>
              </form:form>
            </td>
          </tr>
        </c:forEach>

UsuarioController.java

  @RequestMapping(value = "/editar")
  public ModelAndView editarForm(String email) {
    ModelAndView mav = new ModelAndView("/usuarios/editar");

//    Usuario usuario = usuarioDao.loadUserByUsername(email);
//    mav.addObject("usuario", usuario);

    List<Role> listaRoles = roleDao.listar();
    mav.addObject("listaRoles", listaRoles);

    return mav;
  }

editar.jsp

      <h1>Cadastro de Permissões para ${usuario.nome}</h1>
      <p>${usuario.roles}</p>
      <p>${listaRoles}</p>
<%--       <form:checkboxes items="${listaRoles}" path="${roles}"/> --%>

Resultado (ao clicar no botão de edição):

ou seja, o meu método editarForm está retornando o ModelAndView corretamente. O problema não está no retorno, está no parâmetro que não estou conseguindo receber (tirei o @RequestParam do método porque senão ele dá a exceção abaixo).

org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'email' is not present

O estranho também é o seguinte: no editar.jsp, para fazer um teste, eu chamo três elementos:

${usuario.nome}
${usuario.roles}
${listaRoles}

Onde o usuario.nome e o usuario.roles deveriam vir para a página quando o usuário fosse encontrado pelo método loadUserByUsername e o objeto fosse adicionado à página ao fazer o mav.addObject (que é o que está acontecendo corretamente com o listaRoles). Como eu não recebo o email, não consigo localizar o usuário e a página não deveria estar recebendo e nem mostrando o conteúdo referente ao usuario (usuario.nome e usuario.roles). Mas se eu cadastrar um novo usuário e chamar a edição dele e vai aparecer os mesmos usuario.nome e usuario.roles (ADMIN e ADMIN_ROLE). Como o parâmetro não está chegando ao método, eu entendo que o nome e as roles do outro usuário não aparecem, mas de onde estão saindo o nome e os roles do admin (nesse caso de tentar editar outro usuário)? Loucura.

EDIT:

esqueci de falar porque eu não testo no PostMan igual vocês sugeriram. Nunca mexi com PostMan, sinceramente não sei se aprender agora é o melhor momento.

Galera, tô me sentindo trouxa :man_facepalming:

Eu jurava que já tinha tentado isso, mas consegui resolver toda essa treta de comunicação entre a view e o controller dessa maneira abaixo:

lista.jsp

<form:form action="${s:mvcUrl('UC#editarForm').arg(0, usuario.email).build() }" method="POST">
                <input type="image" src="${resPath}/imagens/editar.png" alt="Editar" title="Editar"/>
              </form:form>

Foi só deixar o method=“POST” e pronto. Sério que eu jurava já ter feito isso. Porém agora meu problema está em outro lugar e como foge da questão inicial do tópico, vou deixar essa postagem marcada como solução e se não conseguir, eu abro outro tópico.

UsuarioController.java

  @RequestMapping(value = "/editar")
  public ModelAndView editarForm(@RequestParam String email) {
    ModelAndView mav = new ModelAndView("/usuarios/editar");

    Usuario usuario = usuarioDao.loadUserByUsername(email);
    mav.addObject("usuario", usuario);
    mav.addObject("roles", usuario.getRoles());

    List<Role> listaRoles = roleDao.listar();
    mav.addObject("listaRoles", listaRoles);

    return mav;
  }

só pra ficar completo, o controller ficou assim, recebendo um parâmetro a partir da requisição (e não do Body).

    mav.addObject("usuario", usuario);
    mav.addObject("roles", usuario.getRoles());

isso aqui ainda está passível de mudança. Tem a ver com o erro que tô recebendo agora.

Para alguém que futuramente consultar isso aqui, o meu atual entendimento do que estava acontecendo de errado e de como eu consertei:

Eu precisava de um dado (usuario.email) sendo passado pela view (lista.jsp). Ao tentar usar o get, o controller (UsuarioController.java) tentava consumir um dado sendo passado pela URL. Como não estava vindo nada na URL, eu recebia null no que eu tentava acessar o email ou tentava carregar as informações do usuário através dele. Quando tentei usar o post, eu usei a anotação ResponseBody junto, o que também me dava null, porque eu estava tentando consumir através do corpo da resposta e não da requisição. Talvez eu tenha usado o post corretamente antes, mas não devo ter notado que o problema mudou na stacktrace, então mea culpa. O problema foi consertado fazendo o post (enviando dados presentes no corpo da view) e recuperando como um parâmetro da requisição no método (anotação RequestParam).

Muito obrigado pelo tempo e atenção de vocês e desculpe qualquer coisa! :smile:

Consegue sim :slightly_smiling_face:.

Como vc está fazendo uma consulta com POST, tecnicamente abandonou a arquitetura REST.
Vc poderia ter usado um input type texto com hide, e name = email e value = usuário.email dentro do form.
Assim, quando usasse o GET o email seria enviado na URL.

No meu caso, prefiro tudo via POST, com o padrão envia e recebe JSON, ou seja, também não uso a arquitetura REST.
Mas em se tratando de uma solução empresarial ao se adotar REST, tem que obedecer às regras do REST.

Como dito, consigo fazer o mesmo em menos tempo usando somente POST, consumindo e produzindo JSON.

Apesar de ter funcionado do meu jeito, preferi alterar e fazer assim como você indicou:

dessa maneira, na minha cabeça, faz mais sentido e me parece mais elegante. Agora tô quebrando a cabeça com esses outros problemas que mencionei, vou tentar mais um pouco e qualquer coisa abro outro tópico. Muito obrigado mesmo, @PedreiroDeSoftware !