Should I put the ID of my entity in the URL or into the form as a hidden field?

marc82ch :

I think in terms of REST, the ID should be placed into the URL, something like:

https://example.com/module/[ID]

and then I call GET, PUT, DELETE on that URL. That's kind of clear I think. In Spring MVC controllers, I'd get the ID with @PathVariable. Works.

Now, my practical problem with Spring MVC is, that if I do this, I have to NOT include the ID as part of the form (as well), Spring emits warnings of type

Skipping URI variable 'id' since the request contains a bind value with the same name.

otherwise. And it also makes kind of sense to only send it once, right? What would you do if they don't match??

That would be fine, but I do have a custom validator for my form backing bean, that needs to know the ID! (It needs to check if a certain unique name is already being used for a different entity instance, but cannot without knowing the ID of the submitted form).

I haven't found a good way to tell the validator that ID from @PathVariable, since the validation happens even before code in my controller method is executed.

How would you solve this dilemma?

This is my Controller (modified):

@Controller
@RequestMapping("/channels")
@RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
    protected ChannelService channelService;
    protected ChannelEditFormValidator formValidator;

    @Autowired
    public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
    {
        this.channelService = channelService;
        this.formValidator = formValidator;
    }

    @RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
    public String editChannel(@PathVariable Long channelId, @ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
    {
        if (channelId > 0)
        {
            // Populate from persistent entity
        }
        else
        {
            // Prepare form with default values
        }
        return "channel/admin/channel-edit";
    }

    @RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
    public String saveChannel(@PathVariable Long channelId, @ModelAttribute("channelForm") @Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
    {
        try
        {
            // Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
            Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
            if (nameChannelId != null && !nameChannelId.equals(channelId))
                result.rejectValue("name", "channel:admin.f1.error.name");
        }
        catch (EmptyResultDataAccessException e)
        {
            // That's fine, new valid unique name (not so fine using an exception for this, but you know...)
        }

        if (result.hasErrors())
        {
            return "channel/admin/channel-edit";
        }

        // Copy properties from form to ChannelEditRequest DTO
        // ...

        // Save
        // ...

        redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
        // POST-REDIRECT-GET
        return "redirect:/channels/" + channelId + "/admin";
    }


    @InitBinder("channelForm")
    protected void initBinder(WebDataBinder binder)
    {
        binder.setValidator(formValidator);
    }
}
marc82ch :

I think I finally found the solution.

As it turns out Spring binds path variables to form beans, too! I haven't found this documented anywhere, and wouldn't have expected it, but when trying to rename the path variable, like @DavidW suggested (which I would have expected to only have a local effect in my controller method), I realized that some things got broken, because of the before-mentioned.

So, basically, the solution is to have the ID property on the form-backing object, too, BUT not including a hidden input field in the HTML form. This way Spring will use the path variable and populate it on the form. The local @PathVariable parameter in the controller method can even be skipped.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=35484&siteId=1