Страница JSF не будет перезагружаться, если я напишу в ByteArrayOutputStream

По сути, у меня есть страница JSF, на которой отображается счетчик и кнопка «скачать». Я хочу, чтобы при нажатии кнопки текстовый файл создавался и загружался, а количество страниц увеличивалось.

Когда я нажимаю CommandButton, вызывается метод bean.download, текст записывается в ByteArrayOutputStream и загружается. Но счетчик не обновляется (страница не перезагружается).

Если я не вызываю метод для записи в ByteArrayOutputStream, страница обновляется и показывает новое значение счетчика.

Страница JSF:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core">
<h:body>

    <h:form id="mainForm">
        <h:outputText id="countLbl" value="Download Count = #{bean.count}" />

        <br />

        <h:commandButton value="Do Download" action="#{bean.download}"/>
    </h:form>


</h:body>
</html>

Сессионный компонент: Bean

import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

@Named
@SessionScoped
public class Bean implements Serializable {

    private int count;

    @PostConstruct
    private void init() {
        count = 0;
    }

    public String download() {
        count++;
        try {
            doDownload();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } catch (ServletException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return "";
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public void doDownload() throws IOException, ServletException {

        final ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            String line = "dummy text";
            baos.write(line.getBytes());
            baos.close();

            sendReport(baos.toByteArray());

        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        } catch (ServletException e) {
            e.printStackTrace();
            throw e;
        }

    }

    public static void sendReport(byte[] bs) throws IOException,
            ServletException {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        HttpServletResponse response = (HttpServletResponse) externalContext
                .getResponse();

        response.reset();
        response.setHeader("Expires", "0");
        response.setHeader("Cache-Control",
                "no-store, no-cache, post-check=0, pre-check=0");
        response.setHeader("Pragma", "no-cache");
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-disposition",
                "attachment; filename=\"down.csv\"");
        response.setContentLength(bs.length);

        ServletOutputStream os = response.getOutputStream();
        os.write(bs);

        os.close();

        facesContext.responseComplete();

    }

}

Спасибо за помощь.


person Novarse    schedule 13.04.2013    source источник


Ответы (1)


Я думаю, проблема в том, что сервер может отправить только один ответ на запрос, попробуйте использовать ajax. Я не очень много использовал JSF, но это может помочь. Когда вы делаете запрос, сервер может либо отправить загрузку обратно, либо обновленную страницу. С помощью ajax вы можете сделать запрос на обновление счетчика отдельно от запроса на загрузку.

Похоже, говоря, что ответ завершен, вы отменяете загрузку страницы, что вы делаете правильно, потому что, если бы вы этого не сделали, пользователь загрузил бы часть страницы со своим файлом.

Если у вас есть функция javascript, которая выполняет вызов ajax для обновления счетчика при нажатии кнопки «Загрузить», это может сработать.

person rubixibuc    schedule 13.04.2013
comment
Спасибо за совет rubixibuc. Для меня имеет смысл то, что вы сказали о том, что сервер может отправить только один ответ. Но я все еще застрял в том, как обойти это. Как я могу заставить функцию javascript выполнять вызов ajax для обновления счетчика, когда я нажимаю «Загрузить»? - person Novarse; 16.04.2013
comment
Вы можете использовать атрибут onclick кнопки загрузки, чтобы вызвать вызов ajax. Поэтому, когда вы нажимаете кнопку, он отправляет запрос ajax на сервер (ajax работает почти так же, как и любой другой запрос, но происходит не синхронно с загрузкой страницы) и загружает содержимое на страницу, обычно внутри <div>. В библиотеке jQuery есть метод, который делает это. api.jquery.com/load Вам нужно будет создать отдельную страницу, которая просто возвращает счетчик. - person rubixibuc; 17.04.2013
comment
Фреймворк JSF предоставляет для этого встроенные инструменты с тегом ajax, но у меня нет большого опыта работы с ними. Любой способ будет работать одинаково, хотя тег JSF будет менее запутанным, если вы знаете, как его использовать. Я рекомендую попробовать это с JSF. - person rubixibuc; 17.04.2013
comment
Спасибо за вашу помощь rubixibuc. Я решил проблему, добавив onclick=setTimeout('history.go(0)', 1000); в commandButton для перезагрузки страницы через одну секунду. Не лучшее решение, но оно работает. Ура Стивен - person Novarse; 18.04.2013