Spring+JSP url building best practices - spring

I wonder if there are any good practices for addressing Spring controllers in JSP.
Suppose I have controller:
#Controller
class FooController {
// Don't bother about semantic of this query right now
#RequestMapping("/search/{applicationId}")
public String handleSearch(#PathVariable String applicationId) {
[...]
}
}
Of course in JSP I can write:
<c:url value="/search/${application.id}" />
But it's very hard to change url then. If you familiar with Rails/Grails then you now how this problem resolved:
redirect_to(:controller => 'foo', :action = 'search')
But in Spring there is so much UrlMappers. Each UrlMapper have own semantic and binding scheme. Rails alike scheme simply doesn't work (unless you implement it yourself). And my question is: are there any more convenient ways to address controller from JSP in Spring?

I hope i understood your question. I think your asking about how to maintain urls when url strings are in jsp and controller mappings.
Your Controller should do the logic, your JSP should do the output. Constructing an Url should be the responsability of the controller handling it. So
class SearchController {
#RequestMapping("/search/{applicationId}")
public String handleSearch(#PathVariable String applicationId) {
[...]
}
public String getUrl(Long applicationId) {
return "/search/" + applicationId;
}
}
class StartController {
private SearchController controller;
#ModelAttribute("searchUrl")
public String getSearchUrl() {
return fooController.getUrl(applicationId);
}
}
and in your start.jsp do
<c:url value="${searchUrl}" />

Try using Apache as a front end to remap the URLs:
http://www.simonecarletti.com/blog/2009/01/apache-rewriterule-and-query-string/
This way you can change the applicationId parameter from a query string into a friendly URL.
Additionally, here is the documentation for mod_rewrite:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html

Related

How to reference/build the URL of a (named) Spring Web #RequestMapping on a HTML page using Thymeleaf?

I just started using Spring Web & Thymeleaf for the development of a simple webapplication.
It is going pretty well but so far I am unable to figure out how to prevent duplicate code for the hrefs and various paths/routes.
Example Spring get mapping:
...
#GetMapping(path="/hello")
public String hello(){
return "hello";
}
...
First HTML page (index.html) containing href to '/hello', using Thymeleaf:
...
<a th:href="#{/hello}">Hello</a>
...
Second HTML page (home.html) containing href to '/hello', using Thymeleaf:
...
<a th:href="#{/hello}">Hello</a>
...
How can I arrange my code so that I don't have to manual update the href on each HTML page when I change the path of a #GetMapping?
Basically I would like to assign the path '/hello' to a variable which i can then reference at each href and getmapping
BR, Kazi
I have been googling for a couple of hours but am unable to find any usable information on my problem.
I have noticed the 'name' attribute of the Spring '#GetMapping' annotation but have no idea if this provides a solution or how it should work together with Thymeleaf.
Also i know that the Django framework for Python based webapplications provides the ability to name routes (using urlpatterns) and reference these from within its templating language. I would expect a similar solution is available for the Spring framework.
I manage to find two solutions:
Solution 1
Using the capital letters of the controller name in combination with the name of the requestmethod, as described in section 11.1 'Building URIs to controllers' of the official Thymeleaf document Thymeleaf Tutorial: Thymeleaf + Spring. The capital letters of the controller name and the requestmethod name are separate by a '#'.
Important: The 'name' attribute of the #RequestMapping must not be used else Thymeleaf won't be able to resolve the view!
Note: Possibly this solution is preferred in case path variables or request parameters must be added to the url. (I have not investigated this.)
Example Controller
public class ExampleController {
#RequestMapping("/data")
public String getData(Model model) {
...
return "template"
}
#RequestMapping("/data")
public String getDataParam(#RequestParam String type) {
...
return "template"
}
}
Example links
<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get
Data Param</a>
Solution 2
Using the 'name' attribute of the #RequestMapping. As discribe in the following answer to Stackoverflow question Url building by #RequestMapping name not working as expected with custom names
The first example only uses the 'name' attribute in the #RequestMapping at the method level.
Example Controller 1
#Controller
public class ExampleController {
#RequestMapping(value="/", method=RequestMethod.GET, name="home")
public String index(Model model) {
return "index"
}
}
Example link 1
<a th:href="${(#mvc.url('home')).build()}">Home</a>
The second example uses the 'name' attribute in the #RequestMapping at the method level and type level.
Example Controller 2
#Controller
#RequestMapping(value="/information", name="info")
public class ExampleController {
#RequestMapping(value="/organisation", method=RequestMethod.GET,
name="org")
public String contact(Model model) {
return "organisation"
}
}
Example link 2
<a th:href="${(#mvc.url('info#org')).build()}">Contact</a>

Spring Boot - mapping

In the code below there are two methods annotated with #GetMapping annotation, one expects empty path, another one expects a path variable.
#Controller
#RequestMapping("/")
public class BasicController {
#GetMapping()
public String get(Model model) {
// doing something
}
#GetMapping("/{variable}")
public String getWithPathVar(#PathVariable("variable") String variable, Model model) {
// doing something different
}
}
Problem: When the app is running and I hit "www.myurl.com/" it enters both methods even though there is no path parameter. How can I fix this?
If so it sounds like a bug or some misconfiguration with filters. I can't reproduce this behaviour on the Spring 5.2.7. Here's an article that explains how Spring works under the hood.
If you can't upgrade the Spring version you can use only single endpoint as a workaround.
#GetMapping("/{variable}")
public String getWithPathVar(#PathVariable("variable") String variable, Model model) {
// doing something different
if(variable != null) {
// fulfill the normal workflow
} else {
// call ex get() workflow
}
}

Thymeleaf + Spring MVC + Rest

I don't understand, how to change #Controller to #RestController for RESTFull serivce, if html template linked with attributes that I get in ModelAndView
#Controller
public class MyController{
#GetMapping("/index")
public ModelAndView index(){
return new ModelAndView("/index", name, userService.getUser().getName());
}
}
and in thymeleaf template it's look like
<p th:text="'Hello, ' + ${name} + '!'" />
But I wanna go to index page, and in background get user name
#RestController
public class MyController{
#GetMapping("/api/user")
public String index(){
return userService.getUser().getName();
}
}
I can use ajax for update tag "p", but in this way it's nothing benefit of using thymeleaf, I can use jsp. So what the best way use thymeleaf with rest and is it rational?
I think the purpose of Thymeleafis for server-side rendering.
Thymeleaf is a Java template engine for processing and creating HTML, XML, JavaScript, CSS, and text.
When you are using JSON API and parse the JSON and use angular or any other client-side rendering framework for that. Thymeleaf with REST is not the approach.
But if you want to use both ways like provide data to Thymeleaf and also provide REST services to other application follow below approach.
#RequestMapping('/foobars')
abstract class FoobarBaseController {
#RequestMapping
abstract listAll()
}
#Controller
class FoobarHtmlController extends FoobarBaseController {
#Override ModelAndView listAll() {
new ModelAndView('foobars/foobarThymeleafTemplate', [foobars: foobarsList])
}
}
#RestController
#RequestMapping('/foobars', produces = MediaType.APPLICATION_JSON_VALUE)
class FoobarJsonController extends FoobarBaseController {
#Override Collection<Foobar> listAll() {
foobarsList
}
}
I hope this address your question properly.

Attribute routing and inheritance

I am playing around with the idea of having a base controller that uses a generic repository to provide the basic CRUD methods for my API controllers so that I don't have to duplicate the same basic code in each new controller. But am running into problems with the routing attribute being recognized when it's in the base controller. To show exactly what the problem I'm having I've created a really simple WebAPI controller.
When I have a Get method in the main Controller and it inherits from the ApiController directly I don't have any problems and this works as expected.
[RoutePrefix("admin/test")]
public class TestController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
When I move the Get method into a base controller it is returning the contents of the 404 page.
[RoutePrefix("admin/test")]
public class TestController : TestBaseController
{
}
public class TestBaseController : ApiController
{
[Route("{id:int:min(1)}")]
public string Get(int id)
{
return "Success";
}
}
Some more interesting notes:
I can access the action at GET /Test/1. So it is finding it based on the default route still.
When I try to access POST /admin/test, it returns the following JSON
{
"Message":"No HTTP resource was found that matches the request URI 'http://test.com/admin/test'.",
"MessageDetail":"No type was found that matches the controller named 'admin'."
}
Does anyone know of a way to get the routing to work with attributes from a base controller?
Attribute routes cannot be inherited. This was a deliberate design decision. We didn't feel right and didn't see valid scenarios where it would make sense to inherit them.
Could you give a more realistic scenario as to where you would want to use this?
[Update(3/24/2014)]
In the upcoming 5.2 release of MVC Web API, there is going to be an extensibility point called System.Web.Http.Routing.IDirectRouteProvider through which you can enable the inheritance scenario that you are looking for here. You could try this yourself using the latest night builds(documentation on how to use night builds is here)
[Update(7/31/2014)]
Example of how this can be done in Web API 2.2 release:
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
//---------
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
// inherit route attributes decorated on base class controller's actions
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
Using Web API 2.2, you can:
public class BaseController : ApiController
{
[Route("{id:int}")]
public string Get(int id)
{
return "Success:" + id;
}
}
[RoutePrefix("api/values")]
public class ValuesController : BaseController
{
}
config.MapHttpAttributeRoutes(new CustomDirectRouteProvider());
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
protected override IReadOnlyList<IDirectRouteFactory>
GetActionRouteFactories(HttpActionDescriptor actionDescriptor)
{
return actionDescriptor.GetCustomAttributes<IDirectRouteFactory>
(inherit: true);
}
}
as outlined here: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22
Got it.
[Route("api/baseuploader/{action}")]
public abstract class BaseUploaderController : ApiController
{
[HttpGet]
public string UploadFile()
{
return "UploadFile";
}
}
[Route("api/values/{action}")]
public class ValuesController : BaseUploaderController
{
[HttpGet]
public string Get(int id)
{
return "value";
}
}
One caveat here is that the route action paramter must be the same as the action name. I could not find a way to get around that. (You cannot rename the route with a RouteAttribute)

Mvc3 - Best practice to deal with data which are required for (almost) all requests?

I am creating an application in mvc3 and wondering how to deal with database data which is required for all application requests, some of them depends on a session, some of them depends on url pattern basically all data is in database.
Like to know best practice
What I do in my applications and consider to be the best practice is to load your common data to the ViewBag on the Controller constructor.
For every project, I have a DefaultController abstract class that extends Controller. So, every controller in the project must inherit from DefaultController, instead of Controller. In that class' constructor, I load all data common to the whole project, like so:
// DefaultController.cs
public abstract class DefaultController : Controller
{
protected IRepository Repo { get; private set; }
protected DefaultController(IRepository repo)
{
Repo = repo;
ViewBag.CurrentUser = GetLoggedInUser();
}
protected User GetLoggedInUser()
{
// your logic for retrieving the data here
}
}
// HomeController.cs
public class HomeController : DefaultController
{
public HomeController(IRepository repo) : base(repo)
{
}
// ... your action methods
}
That way you will always have the logged in user available in your views.
I do the same as #rdumont but with one exception: I create a CommonViewModel which I use to define all common properties that I use.
public class CommonViewModel
{
public string UserName {get;set;}
public string Extension {get;set; }
}
Declare a property in the base controller:
public abstract class BaseController : Controller
{
protected CommonViewModel Commons { get; private set; }
protected virtual void OnResultExecuting(ResultExecutingContext filterContext)
{
ViewBag.Commons = Commons;
}
}
By doing so I get everything almost typed. The only cast that I need to do is to cast ViewBag.Commons to the CommonViewModel.
Best is to avoid ViewBag at all.
See this answer, which details how to use Html.RenderAction() for that purpose:
Best way to show account information in layout file in MVC3
I'd suggest using a base ViewModel class.
So a base class with properties/functions which should be available at any point.

Resources