31

Is it possible to render my view into html in my controller mapping method, so that i can return the rendered html as a part of my json object ?

Example of my usual controller method :

@RequestMapping(value={"/accounts/{accountId}"}, method=RequestMethod.GET)
public String viewAcc(final HttpServletRequest req, 
        final HttpServletResponse resp, final Model model,
        @PathVariable("accountId") final String docId) {

    // do usual processing ...

    // return only a STRING value, 
    //   which will be used by spring MVC to resolve into myview.jsp or myview.ftl
    //   and populate the model to the template to result in html
    return "myview";
}

What i expect :

@RequestMapping(value={"/accounts/{accountId}"}, method=RequestMethod.GET)
public String viewAcc(final HttpServletRequest req, 
        final HttpServletResponse resp, final Model model,
        @PathVariable("accountId") final String docId) {

    // do usual processing ...

    // manually create the view
    ModelAndView view = ... ? (how)

    // translate the view to the html
    //   and get the rendered html from the view
    String renderedHtml = view.render .. ? (how)

    // create a json containing the html
    String jsonString = "{ 'html' : " + escapeForJson(renderedHtml) + "}"

    try {
        out = response.getWriter();
        out.write(jsonString);
    } catch (IOException e) {
        // handle the exception somehow
    }

    return null;
}

I wonder what is the right way to create the view and render the view into html manually within the controller method.

Update

Here's the working example from the accepted answer's guidance :

View resolvedView = thiz.viewResolver.resolveViewName("myViewName", Locale.US);
MockHttpServletResponse mockResp = new MockHttpServletResponse();
resolvedView.render(model.asMap(), req, mockResp);
System.out.println("rendered html : " + mockResp.getContentAsString());
Onema
  • 7,331
  • 12
  • 66
  • 102
Bertie
  • 17,277
  • 45
  • 129
  • 182
  • 2
    Because i want to return as a json, which contains not only the rendered partial-html, but also some other variables that will be used by the client. – Bertie Mar 15 '14 at 10:25

3 Answers3

26

Try autowiring the ViewResolver then calling resolveViewName("myview", Locale.US) to get the View.

Then call render() on the view, passing it a "mock" HTTP response that has a ByteArrayOutputStream for its output, and get the HTML from the ByteArrayOutputStream.

Update

Here's the working example, copied from the question. (so the code is actually with the answer)

View resolvedView = thiz.viewResolver.resolveViewName("myViewName", Locale.US);
MockHttpServletResponse mockResp = new MockHttpServletResponse();
resolvedView.render(model.asMap(), req, mockResp);
System.out.println("rendered html : " + mockResp.getContentAsString());
Matthias Wiehl
  • 1,799
  • 16
  • 22
Ted Bigham
  • 4,237
  • 1
  • 26
  • 31
1

If you want to render the view under the same locale as the DispatcherServlet would render it, try coping it's render-method:

/** LocaleResolver used by this servlet */
private LocaleResolver localeResolver;

/** List of ViewResolvers used by this servlet */
private List<ViewResolver> viewResolvers;

/**
 * Render the given ModelAndView.
 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
 * @param mv the ModelAndView to render
 * @param request current HTTP servlet request
 * @param response current HTTP servlet response
 * @throws ServletException if view is missing or cannot be resolved
 * @throws Exception if there's a problem rendering the view
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = this.localeResolver.resolveLocale(request);
    response.setLocale(locale);

    View view;
    if (mv.isReference()) {
        // We need to resolve the view name.
        view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                    "' in servlet with name '" + getServletName() + "'");
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                    "View object in servlet with name '" + getServletName() + "'");
        }
    }

    // Delegate to the View object for rendering.
    if (logger.isDebugEnabled()) {
        logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
    }
    try {
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
                    getServletName() + "'", ex);
        }
        throw ex;
    }
}

/**
 * Resolve the given view name into a View object (to be rendered).
 * <p>The default implementations asks all ViewResolvers of this dispatcher.
 * Can be overridden for custom resolution strategies, potentially based on
 * specific model attributes or request parameters.
 * @param viewName the name of the view to resolve
 * @param model the model to be passed to the view
 * @param locale the current locale
 * @param request current HTTP servlet request
 * @return the View object, or {@code null} if none found
 * @throws Exception if the view cannot be resolved
 * (typically in case of problems creating an actual View object)
 * @see ViewResolver#resolveViewName
 */
protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
        HttpServletRequest request) throws Exception {

    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

It should usually be sufficient to add @Autowired to the fields on top, but the DispatcherServlet also employs a fallback when autowiring would fail.

TheConstructor
  • 4,285
  • 1
  • 31
  • 52
-2

you can use templating library for creating html, Velocity for example. Then, you need to define return type as

public @ResponseBody SomeObject viewAcc(...) {...}

and the object itself can obtain the html, as well as some other variables

Miloš Lukačka
  • 842
  • 1
  • 11
  • 25
  • Assuming that i'm using velocity (while i'm actually using freemarker), how do i use Spring MVC's mechanism to render the html based on the velocity's view name only ? like springHtmlRenderer.render("myVelocityViewName"); ? – Bertie Mar 15 '14 at 10:28
  • you need to create a velocity template (.vm file), then, in controller method, load this template, populate variables in it and generate html to string variable from it. is that not, what you seek? – Miloš Lukačka Mar 15 '14 at 10:35
  • How to load the template the spring way ? Because my assumption is that, like freemarker, the template has been configured in the spring configuration, and could be having special variables from spring, etc that wouldnt be available if loaded by the non-spring-way. – Bertie Mar 15 '14 at 10:40
  • hmm, no idea how to load it "spring way". I always did it "velocity way" and populated it with variables in the controller method – Miloš Lukačka Mar 15 '14 at 10:43
  • I understand that velocity or freemarker will work outside the spring box. But in my case, there are already dependencies between freemaker and spring mvc. One example is that there's the spring macros like `` for the i18n stuffs in my template. This wouldnt work out of the box if i just load my freemarker template and populate with my model values. Thanks a lot for your help ! – Bertie Mar 15 '14 at 11:05
  • 1
    as for the i18n, you can load stuff from MessageSource also in the controller method, not only in JSPs. Sorry that I didn't help – Miloš Lukačka Mar 15 '14 at 15:05
  • Yes, we can do the i18n also. But i'm sorry i wasnt clear about my situation where i already have working all these templates and pages, and the related spring configurations like autoimports on some templates. So in my case, it's just cheaper to have spring render the view than having to do all the changes so i can render it without spring. I thank you for your time. – Bertie Mar 15 '14 at 15:21