Search code examples
javaspring-bootthymeleaf

How to make variable set into a handler visible into another handler?


I have the following GetMapping handler:

@GetMapping("/auto-upgrade/{id}")
public String followJob(Model model, @PathVariable String id) {
    boolean stopMessageAlreadyThere = model.asMap().containsKey("stopMessage");
    if (!stopMessageAlreadyThere) {
        model.addAttribute("stopMessage", null);
    }
    //do some stuff
    return "dashboard";
}

In the dashboard.html that I return, I have the following form embedded:

<form action="#" th:action="@{/auto-upgrade/stop}" th:object="${stopRequest}" method="post" th:if="${#strings.toString(upgradeState.upgradeStatus) == 'RUNNING'}">
    <input style="visibility: hidden" th:field="*{jobId}" th:value="${upgradeState.id}"/>
    <p>Your mail address: <input type="email" id="stoppingEmail" oninput="saveLastInput()" th:field="*{userEmail}"></p>
    <p><input type="submit" value="Request stop upgrade" />
</form>

... and then right after I have the following element:

<div th:if="${stopMessage != null}">
    <p>Message for stop command: <b th:text="${stopMessage}"></b></p>
</div>

When I render the view, the stopMessage is null because it's me inserting it as such.

If I submit the form, I go through this PostMapping handler:

@PostMapping("/auto-upgrade/stop")
public String stopJob(@ModelAttribute StopRequest stopRequest, Model model) {
    try {
        //something that may fail
        model.addAttribute("stopMessage", "Stop request accepted");
    } catch (UpgradeNotRunningException | NotInGracePeriodException | StopUpgradeException e) {
        model.addAttribute("stopMessage", "FAILED TO STOP: " + e.getMessage());
    }
    return "redirect:/auto-upgrade/" + stopRequest.getJobId();
}

So basically I'm always going to add a `stopMessage, whether successful or failing.

However, when I'm called back to my @GetMapping("/auto-upgrade/{id}") to which I redirect, the Model model is empty meaning that my value previously saved is not added.

I would have expected Model to be shared across handlers, but it doesn't look to be the case.

How can I retrieve the value of a property set into another handler? Is it possible with Thymeleaf ?

Note: for the time being, I have made stopMessage a field of my Controller class and I set it null / not null around the handlers so that I'm always able to see its value. It is a solution, but it doesn't seem to be the right way to do it. So since I'm new to Thymeleaf, I'd be happy to hear the proper way of handling such cases.


Solution

  • You need to use RedirectAttributes:

    @PostMapping("/auto-upgrade/stop")
    public String stopJob(@ModelAttribute StopRequest stopRequest, 
                          Model model,
                          RedirectAttributes redirectAttributes) {
        try {
            //something that may fail
            redirectAttributes.addFlashAttribute("stopMessage", "Stop request accepted");
        } catch (UpgradeNotRunningException | NotInGracePeriodException | StopUpgradeException e) {
            redirectAttributes.addFlashAttribute("stopMessage", "FAILED TO STOP: " + e.getMessage());
        }
        return "redirect:/auto-upgrade/" + stopRequest.getJobId();
    }
    

    That way, stopMessage is available after the redirect. (Note that if the user manually refreshes the page after the redirect, they are lost)

    See https://www.baeldung.com/spring-web-flash-attributes for more info.