Spring: redirect from POST method doesn't work - spring

I'm trying to get my Spring controller recieve POST request. I want to get the body of post request and show it on web page. I send requests via Postman. My controller receives it, but somehow, when I try to redirect to another page as usual, Spring renders the html template and sends it back to Postman.
My code:
#GetMapping("/webhooks")
public String webhooks(Model model) {
model.addAttribute("response", "webhooks");
return "connected";
#PostMapping("/webhooks")
public String webhooks(String payload, Model model) {
model.addAttribute("response", payload);
return "connected";
}
My connected.html template:
<body>
<a href="/" >Home</a>
<h3>Connected!</h3>
<div>
<button onclick="refreshToken()">Refresh Token</button>
<br /><br />
<button onclick="newCustomer()">Create new customer</button>
<button onclick="invoice()">Create new invoice</button>
<br />
<div><code id="result" th:text="${response}"></code></div>
</div>
</body>
This is what I see in Postman window:
But I expect to see the same form (as on the picture) in my browser and not in Postman. I want to redirect to this page and not to send this page back to Postman. I have a few similar methods in other controllers which are completely identical, but they work correctly.
What am I doing wrong? Thank you in advance.

I have the same issue as you have, I tried different ways to fix this problem but none of them worked for me. But you could give it a try:
#GetMapping("/webhooks")
public RedirectView webhooks(Model model) {
//Do what you want
return new RedirectView("/connected");
#PostMapping("/webhooks")
public RedirectView webhooks(String payload, Model model) {
//Do what you want
return new RedirectView("/connected");
}
This didn't work out for me but it might help you out. You use the RedirectView class from Spring and enter the URL to the endpoint.
Please let me know if it worked for you!

Related

How to add an element id to the url of the Thymeleaf template in the controller in Spring

I have a Spring Application and Server Side Rendering with Thymeleaf as Templating language.
A button sends a get or post request to the controller in Spring, which puts some message to the view, which is rendered into the HTML file and send back to the client. The message should be optional. Thats why the template must also be able to be called without the message.
Next i want the client browser to scroll down to the part of the page where this message is rendered into, which is normally very easy. You would just have to append the id of the element to the url like following example.
https://stackoverflow.com/#footer
In this example the browser scrolls down to the footer of the page.
Below is what i tried. Unfortunately it doesnt't work like that. Spring/Thymeleaf tries to find a index#messagebox template which can't be found. Hence a Whitelabel Error Page error is thrown/shown.
Page.html
<section>
<h2>Form to send request</h2>
<form action="showmessage" method="get">
<input type="submit" value="Click for message">
</form>
</section>
Controller.java
#GetMapping("showmessage")
public ModelAndView showMessage(){
return new ModelAndView("index#messagebox",Map.of("optionalmessage","Some message that is optioal"));
}
src/main/resources/templates/index.html
<body>
<h1>Index Page</h1>
<div id="messagebox" th:fragment="message" th:with="optionalmessage=${optionalmessage}">
<p th:if="${optionalmessage!=null}">[[${optionalmessage}]]</p>
</div>
</body>
The problem can be solved with Flashmessages and Redirects. The html basically keeps the same. If the message attribute is set, you render it.
src/main/resources/templates/index.html
<div th:if="${msg}">
<div class="message" >
<p>[[${msg}]]</p>
</div>
</div>
The most important changes had to be made in the controller. First a parameter of type RedirectAttributes is added to the Controller that handles the request.
If wanted the message is added with the RedirectAttributes.addFlashAttribute function as shown below. Finally a redirect is returned, which contains the needed tag. A second controller is also needed that handles the Get Request of the Redirect with a Model as input parameter and returns the needed Template. The #tag is simply passed throuhg to the client browser.
Controller.java
#GetMapping("showMessage")
public String postNotification(RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("optinalMessage", "Hello I am an optional message");
return "redirect:/index#footer";
}
#GetMapping("index")
public String getIndex(Model model) {
return "index";
}
You add id in URL by using ?id=value.
And in controller #RequestMapping("/path/{id}")
to access that variable #PathVariable

Spring mvc Ajax search Form with Thymeleaf

I'm trying to add a search form in the navbar on every page in my spring mvc web app, just like the one here on stackoverflow, and I'm having issues. Right now I have a working search functions on a couple of my pages, using the typical mvc forms. I take the inputted string and store a variable called "searchString" in an object I created called "searchForm.java". Then I try to query that inputted string in the database using spring data's findbycontaing method, and then put that result on the model, and then represent that on the view, using thymeleaf. However I think that the navbar should be done using ajax, since it's on every page and pages with other forms.
So I think I'm sending the string that was submitted to the search form in the navbar to the controller where I queried it in the repository to bring back search results, then I tried to put the search results on the model, but I get nothing, all it does is redirect me to the search page. I may not be making very much sense, but I'll show my code, and if anyone could let me know if I'm going about my problem in the right way or not, and if you guys see any errors in my code. Thanks in advance.
So here's my ajax and jquery to submit the form.
<script th:inline="javascript">
/*<![CDATA[*/
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#searchButton").on("click", function(ev) {
$.ajax({
url : "navSearch",
type : "post",
data : {
"newSearch" : $("#newSearch").val()
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
});
});
/*]]>*/
</script>
There may be an issue here, because in the console in the chrome developer tools, before it redirects, a message pops up very quickly that says uncaught TypeError: Cannot read property 'toLowerCase' of undefined, and it's coming from jquery.min.js:5 so that could be my issue, but I have no idea how to go about fixing this, and I've searched for answers so far with no luck.
Here's my html form, I think this shouldn't be a problem, but who knows, so I'll put it up anyways. And I'm using thymeleaf for this view.
<form action = "setSearch" class="navbar-form navbar-right">
<div class="form-group">
<input type="text" class="form-control" placeholder="Search" id="newSearch"></input>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</div>
<button type="submit" class="btn btn-default" id="searchButton">Search</button>
</form>
Here's my searchForm.java class, where I temporarily store the string to be queried in the database.
public class SearchForm {
private String searchString;
public String getSearchString()
{
return searchString;
}
public void setSearchString(String searchString)
{
this.searchString = searchString;
}
}
And Here's my controller, where I'm trying to handle the ajax submission and return it as search results on the setSearch.html page. What I'm thinking here is that the string "newSearch" from the form could be matched using the Spring Data query methods, and then be able to return it and add it to the model, but it's not working, it's just redirecting me to the /searchSet page with no data, because that's where the form action goes and that's what I tell it to return. So honestly I'm no sure if any data is even getting to this point.
#RequestMapping(value="setSearch/navSearch", method=RequestMethod.POST)
public #ResponseBody String navSearch (#RequestParam String newSearch, ModelMap model)
{
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(newSearch);
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
And here's an example of a working search method that I have in my controller that I use on a regular form, with no ajax, on the /searchSet page.
#RequestMapping(value="/setSearch", method=RequestMethod.GET)
public String searchGet(ModelMap model) {
SearchForm searchForm = new SearchForm();
model.put("searchForm", searchForm);
return "setSearch";
}
#RequestMapping(value="/setSearch", method=RequestMethod.POST)
public String searchPost(#ModelAttribute SearchForm searchForm, ModelMap model) {
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(searchForm.getSearchString());
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
UPDATE
I've changed my code in the form from <button type="submit" class="btn btn-default" id="searchButton">Search</button> to <button type="button" class="btn btn-default" id="searchButton">Search</button> and now I get the Uncaught TypeError: Cannot read property 'toLowerCase' of undefined from earlier and nothing happens with the page.
UPDATE
I can now submit the ajax form without a problem, I was missing meta tags in the header, so the csrf wasn't submitting correctly, so now I get this error in the chrome developer tools console XHR Loaded (navSearch - 405 Method Not Allowed - 7.265999971423298ms - 634B)
UPDATE
Now everything works on the Ajax side, I needed to adjust my url to match the url I had in the request mapping on the controller and it runs through all the code fine. However the overall search function still doesn't work, here's my updated controller.
I know my issue here is that I'm returning a string and not an object, but I'm not sure how to return the object and then redirect the url to the /setSearch page. It's running through the code and returning a string "setSearch" in the console, because I told it to at the end of the controller.
#RequestMapping(value="/setSearch/search", method=RequestMethod.POST)
public #ResponseBody String search (#RequestParam String newSearch, ModelMap model)
{
List<QuestionAnswerSet> questionAnswerSetByQuestion = (List<QuestionAnswerSet>) questionAnswerSetRepo.findByQuestionContaining(newSearch);
model.put("searchResult", questionAnswerSetByQuestion);
return "setSearch";
}
Here's my working ajax
<script th:inline="javascript">
/*<![CDATA[*/
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
$(document).ready(function(){
$("#searchButton").on("click", function(ev) {
$.ajax({
url : "/setSearch/search",
type : "post",
data : {
"newSearch" : $("#newSearch").val()
},
success : function(data) {
console.log(data);
},
error : function() {
console.log("There was an error");
}
});
});
});
/*]]>*/
</script>
but it's not working, it's just redirecting me to the /searchSet page
with no data, because that's where the form action goes and that's
what I tell it to return
You are right, it is because you are submitting the form and in the action you specify it to submit to setSearch, that is why the page is getting redirected to the same page. Just replace button type="submit" with button type="button" so that the form will not be submitted when searchButton is clicked.

Spring MVC : How to pass Model object from one method in controller to another method in same controller?

I have integrated Spring Security in my application , and would like to display an error message to the user in case of Bad credentials.
jsp:
<c:out value='${secerror}' /> //prints nothing on screen
<c:if test="${empty secerror}">
error is empty or null. //always prints this line on screen
</c:if>
<c:if test="${not empty secerror}">
<div class="errorblock">
Your login attempt was not successful, try again.<br /> Caused :
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
</div>
</c:if>
<c:set var = "url" value = "/j_spring_security_check" />
<form:form method="post" commandName="portalLogin" action="${pageContext.servletContext.contextPath}${url}" name="f">
[Update]: Sorry all, i realized that my Model object was getting overriden after i redirect to portalLogin.html as i had created a new model object created there previously.
But i tried few easy options by which i can pass Model object from one controller method to another method in the same controller. But nothing worked.
I tried using forward: prefix instead of redirect prefix. For this, i didn't get error message at all.
I tried below code in loginerror method.
return new ModelAndView("portalLogin","secerror","true");
I was getting following error for the above code:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'portalLogin' available as request attribute
I did come across this link, but i found it to be a very lengthy solution and wasn't sure if iv'e to write that much code.
I wasn't sure if i can use Model object for #ModelAttribute annotations#ModelAttribute.
Please provide me with code snippets / examples which i can try out.
My controller method is like this:
#RequestMapping("/portalLogin")
public ModelAndView goToLogin(Model model) {
try {
System.out.println("Enter goToLogin************************");
} catch (Exception exception) {
}
return new ModelAndView("portalLogin", "portalLogin", new LoginModel());
//return new ModelAndView("portalLogin", model);
}
#RequestMapping(value="/loginfailed", method = RequestMethod.GET)
public ModelAndView loginerror(ModelMap model) {
//model.addAttribute("secerror", "true");
//return "redirect:portalLogin.html";
return new ModelAndView("portalLogin","secerror","true");
}
[Update]: As a work around i added goToLogin method logic inside loginerror method as my only intention is to get portalLogin page. Error was thrown as expected.
#RequestMapping(value="/loginfailed", method = RequestMethod.GET)
public ModelAndView loginerror(Model model) {
model.addAttribute("secerror", "true");
return new ModelAndView("portalLogin", "portalLogin", new LoginModel());
}
But still i would like to know if i can pass Model object from one controller method to another method in the same controller through some way.
You can also try something like this
<c:if test="${not empty secerror == 'true'}">
<tr>
<td colspan="2" align="center"><font style="color: red">Your login attempt was not successful, try again</font>
</td></tr></c:if>
Let`s make things easy, if you want to show a Bad credentials message, you can simply do something like this:
In your spring-security.xml:
<sec:form-login login-page="/login.jsp"
default-target-url="/default-url-when-login-correct"
authentication-failure-url="/login.jsp?error=true"
login-processing-url="/login" always-use-default-target="true" />
In your login.jsp:
<c:if test="${not empty param.error}">
<span>${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}</span>
</c:if>

MVC form post not reaching controller

I have a form that on submit doesn't reach the controller method (I put a breakpoint there)
[HttpPost]
public ActionResult SaveBilling( FormCollection fc )
{
...
}
the frontend code is something like this
<form action="/PersonSettings/SaveBilling" method="post" >
...
<input type='submit' value="save" />
</form>
any ideas ?
not sure if it is a route handler problem because it does reach the GET version if i go to /PersonSettings/SaveBilling in browser but the post method just yields a blank page and doesnt go into the code
Rewrite your view as:
#using (Html.BeginForm("SaveBilling", "PersonSettings", FormMethod.Post))
{
....
<input type='submit' value="save" />
}
Is this controller in any area or not?
I realize this has been solved, but I thought I'd tack on another possible cause: Having the [ValidateAntiForgeryToken] attribute on your method without an #Html.AntiForgeryToken() in your code will create the same problem.
Turns out it was a routing related issue after all , I have some complex routes and the default mvc route was catching things that should have gone through the default mvc route with areas.
Was able to recreate the issue by having explicit parameters for my function instead of a formcollection, so that might have been the issue ohwell.
use form and define action path.
<form id="subscriptionForm" action="/Category/Create" method="post">
--your div
</form >
Now use script to serialize the form
<script type="text/javascript">
$('#Save').click(function () {
var form = $("#subscriptionForm");
var url = form.attr("action");
var formData = form.serialize();
$.post(url, formData, function (data) {
$("#msg").html(data);
});
})
</script>
Now you can reach your controller action.
Sometimes using a network sniffing tool like Fiddler is helpful to see what's wrong when the request is sent and it seems not reaching the server.

Google Chrome issue - Unable to retrive button(HTML tag) value while submitting form

I have a scenario in which a button value is not being posted while submitting form in ASP.NET MVC controller.
This is happening only for Google Chrome browser. For all other browsers Firefox, IE, I am getting Submit_0 value in Controller.
//Server side
public ActionResult Answers(string id, SurveyViewModel model, string Submit, string button)
{
string[] buttonParts = button.Split(new char[]{'_'});
..
...
}
//Client Side
#using (Html.BeginForm("Answers", "Survey"))
{
<button value="Submit_0" name="button" onclick="document.forms[0].submit();"><span><span>Submit</span></span></button>
}
Kindly suggest.
I wonder if it is because the way you wired the button. Why don't you use something like this instead?
<button type="submit" name="button" value="submit_0">Submit</button>
Notice that the button has a type of submit (which eliminate the need to call document.forms[0].submit() manually)

Resources