잊지 않겠습니다.

'View'에 해당되는 글 4건

  1. 2013.09.13 25. View 데이터의 Controller 전달 방법
  2. 2013.09.12 22. Spring View 처리 구조
  3. 2010.01.11 ASP .NET MVC DataBinding
  4. 2010.01.08 MVC Framework - View
지금까지 우리는 View를 살펴봤습니다. 지금까지 본 View들은 File upload만을 제외하고, 사용자의 입력이 Controller단으로 전달되는 것이 전혀 없는 View입니다. 
기본적으로 Web은 URL을 통해서, 그리고 POST의 경우에는 Body에 실린 parameter를 통해서 값의 전달이 이루어지게 됩니다. 그리고, 이 값은 Controller를 통해서 전달됩니다. 
이제 Controller의 다른 기능인 사용자 Action을 받는 기능에 대해서 알아보도록 하겠습니다.  

다시 한번 기억하는 것으로 Controller의 기능은 두가지입니다. 

1. Client에게 View를 전달하는 주체
2. Client에서 데이터를 전달받는 주체

먼저, 아주 기본적인 HTML입니다. (Tiles를 사용해서 head 부분은 제거 되었습니다.)

<form method="post" class="example-form">
    <fieldset>Book</fieldset>
    <label>책 제목</label>        
    <input type="text" name="title" placeholder = "책 제목을 입력해주세요."/>
    <label>작가 이름</label>
    <input type="text" name="author" placeholder = "작가 이름을 넣어주세요."/>
    <label>추가 설명</label>
    <input type="text" name="comment" placeholder = "추가 설명을 넣어주세요."/>
    <br/>
    <button type="submit" class="btn">추가</button>
</form>


그리고, 이 form의 데이터를 받는 Controller의 action 코드는 다음과 같이 작성될 수 있습니다. 

    @RequestMapping(value = "/book/add01", method = RequestMethod.POST)
    public String add(String title, String author, String comment) {
        Book book = new Book();
        book.setTitle(title);
        book.setComment(comment);
        book.setAuthor(author);
        bookService.add(book);

        return "redirect:add01";
    }

값의 전달방법은 html의 element의 name이 각각의 parameter의 input으로 들어가게 됩니다. 이 부분의 코드를 보다 더 명확히 해준다면 다음과 같이 작성될 수 있습니다.

    @RequestMapping(value = "/book/add01", method = RequestMethod.POST)
    public String add(@RequestParam(value = "title") String title,
            @RequestParam(value = "author") String author,
            @RequestParam(value = "comment") String comment) {
        Book book = new Book();
        book.setTitle(title);
        book.setComment(comment);
        book.setAuthor(author);
        bookService.add(book);

        return "redirect:add01";
    }

입력값이 되는 title, author, comment를 보다더 명확하게 전달하게 되는 것을 볼 수 있습니다. 그런데, 이와 같은 개발 방법은 많은 코딩양을 가지고 오게 됩니다. 그리고, OOP 개발자들은 이렇게 생각할 수 있습니다.

"객체를 전달할 방법은 없을까?" 

다음과 같은 코드로서 말이지요. 

    @RequestMapping(value = "/book/add02", method = RequestMethod.POST)
    public String add02(Book book) {
        bookService.add(book);
        return "redirect:add01";
    }

Controller code만을 고치고, 테스트 코드를 이용해서 한번 알아보도록 하겠습니다.  값의 return 정상적으로 Book 객체가 생성이 되었는지를 알아보기 위해서 Controller 코드와 Test code를 잠시 수정했습니다. redirect시에는 model을 전달할 수 없기 때문에 redirect 시에 임시로 값을 저장하는 RedirectAttribute를 이용, FlashMap에 값을 저장하는 것으로 Controller code를 변경하였습니다. 변경된 결과와 테스트 코드는 다음과 같습니다. 

//BookController
    @RequestMapping(value = "/book/add02", method = RequestMethod.POST)
    public String add02(Book book, final RedirectAttributes redirectAttribute) {
        bookService.add(book);
        redirectAttribute.addFlashAttribute("book", book);
        return "redirect:add01";
    }
//BookControllerTest
    @Test
    public void add02() throws Exception {
        String bookTitle = "Book Title";
        String bookAuthor = "Book Author";
        String bookComment = "Book Comment";

        MvcResult mvcResult = mvc.perform(post("/book/add02")
                .param("title", bookTitle)
                .param("author", bookAuthor)
                .param("comment", bookComment))
                .andExpect(status().isFound())
                .andExpect(flash().attributeExists("book"))
                .andDo(print())
                .andReturn();
        
        Book book = (Book) mvcResult.getFlashMap().get("book");
        assertThat(book.getTitle(), is(bookTitle));
        assertThat(book.getComment(), is(bookComment));
        assertThat(book.getAuthor(), is(bookAuthor));
        
        System.out.println(mvcResult);
    }

예상대로 객체를 그대로 넘겨서 받을 수 있는 것을 알 수 있습니다. form을 통해서 데이터가 전달되게 되면 다음과 같은 형식으로 데이터가 전달되게 됩니다. 

title=BookTitle&author=BookAuthor&comment=BookComment

만약에 Controller에서 객체를 받게 된다면, property의 이름을 이용해서 값을 자동으로 연결(Bind)하게 됩니다. 이를 annotation을 통해서 보다 더 명확하게 적어주면 다음과 같은 코드로 표현될 수 있습니다. 

    @RequestMapping(value = "/book/add02", method = RequestMethod.GET)
    public String add02(Model model) {
        Book book = new Book();
        model.addAttribute("book", book);
        return "book/add02";
    }

    @RequestMapping(value = "/book/add02", method = RequestMethod.POST)
    public String add02(@ModelAttribute Book book, final RedirectAttributes redirectAttribute) {
        bookService.add(book);
        redirectAttribute.addFlashAttribute("book", book);
        return "redirect:add01";
    }


이 부분은 View에서 Controller에서 값이 전달되는 방법입니다. Form을 통해서 뿐 아니라 javascript를 이용한 ajax call 역시 같은 방법으로 데이터가 전달이 되게 됩니다. 이제 jquery를 통해서 값의 전달을 해보도록 하겠습니다. 

먼저 간단히 ajax에 대해서 알아보도록 하겠습니다. 

Ajax는 Synchronous JavaScript And XML(비동기 자바 스크립트와 XML)의 약자로 서버와의 비동기 통신을 이용해 마치 데스크탑 애플리케이션을 사용하는 것과 같은 사용자와 애플리케이션간의 인터랙티브한 사용자 경험을 가능하게 하는 스크립트 언어입니다.

이전의 동기 통신에서는 웹 애플리케이션이 서버와의 인터랙션을 필요로 할 때에 매번 브라우저가 사용자와의 인터랙션을 멈추고, 서버로부터의 응답이 올때까지 기다려야 했습니다. 서버로부터의 응답이 오기 전까지 사용자는 아무것도 할 수 없었죠.
하지만 비동기 통신에서는 서버로부터의 응답을 기다릴 필요 없이 사용자는 계속해서 애플리케이션에서 원하는 작업을 할 수 있습니다.

Ajax를 이용하는 예로는 구글맵, 검색사이트 검색창에서의 검색어 제시, 네이버 실시간 검색 순위 등이 있습니다.


Ajax의 장점

Ajax의 주요 장점은 아래와 같습니다.

1) 페이지 이동없이 고속으로 화면 전환
: Ajax는 페이지의 이전 없이 필요한 부분의 데이터 송수신만을 자유롭게 행할 수 있으므로, 효율적이고 빠르게 페이지를 전환할 수 있습니다.

2) 서버의 처리를 기다리지 않고 비동기 요청이 가능
: 서버와의 통신시 사용자는 서버로부터의 응답을 기다리지 않고 계속해서 다음 작업을 이어갈 수 있습니다.

3) 서버에서 처리하는 부분을 클라이언트에서 분담 가능
: Ajax로는 최소의 데이터만을 브라우저에 전달하기 위해 서버에서 하는 작업 중 JavaScript에서 수행 가능한 일을
클라이언트에서 분담하는 것이 가능합니다.

4) 수신하는 데이터의 양을 줄일 수 있음
: 기존의 브라우저가 수신하는 데이터는 HTML이나 XHTML과 같은 마크업 언어로 받는 것이 일반적인데 반해, Ajax로는 수신하는 데이터가 HTML이나 XML에 한정되지 않고 최소한의 텍스트 데이터로도 수신이 가능하기 때문에 수신 데이터이 양을 줄일 수 있습니다.

5) 실시간 인터렉티브 성능이 증가
: (1) ~ (4) 까지의 장점을 이용해 Ajax에서는 데스크탑 애플리케이션과 유사한 실시간 인터랙티브 성능을 보여줄 수 있습니다.

Ajax의 단점

Ajax의 단점은 아래와 같습니다.

1) 크로스 브라우저화의 노하우가 필요
: Ajax는 JavaScript 이므로 브라우저에 따른 크로스 브라우저 처리가 필요합니다.

2) Ajax를 지원하지 않는 브라우저에 대한 대책 필요
: Ajax를 지원하지 않는 브라우저에서는 사용이 불가능하므로 이에 대한 대책이 필요합니다. (하지만 현재 Ajax를 지원하지 않는 브라우저는 거의 없다고 볼 수 있습니다.)

3) 보안에 대한 주의가 불가피
: 페이지 이동 없이 서버와 통신하기 때문에 전보다 더욱 신중한 보안상의 주의가 요구됩니다.

4) 현재의 처리 상황에 대한 정보가 필요
: 페이지 전환 없이 처리가 진행되므로 사용자가 처리가 완료되었는데도 이를 모를 수 있습니다. 따라서 처리 중을 나타내는 프로그레시브 바등을 사용하는 것이 요구됩니다.

5) 요청을 남발하면 역으로 서버 부하가 늘 수 있음
: Ajax의 장점은 서버 부하의 감소에 있지만 그 의도와 반대로 요청이 너무 빈번하게 일어나 서버의 부하를 늘려버릴 수 있습니다. 따라서 데이터 전송량, 요청 회수, 서버 부하 등에 대한 종합적인 판단과 튜닝이 필요합니다.

6) Socket open / close의 부하 시간 증가
: 브라우저에서 가장 큰 부하를 주는 http call을 open을 자주 시키게 됩니다. URL이동으로 인하여 한번에 모든 데이터를 들고오는 방식을 ajax로 여러번을 나눠서 받는 경우, socket의 open/close 시간이 http call 횟수만큼 증가하게 됩니다. 적정한 ajax call이 필요합니다.

7) 개발의 어려움
: 1)항목과 연관이 있는 이야기입니다. javascript는 브라우져에 따라 동작이 다르게 움직여집니다. javascript를 테스트하기 위해서는 지금 상태로는 browser를 반드시 실행시켜서 그 결과를 확인해야지 됩니다. 상당한 수작업이 들어가기 때문에 ajax를 통해서 일을 진행을 해야지 된다면, 그에 대한 pattern등을 명확히 지정해야지 됩니다. 필요없이 모든 것을 다 ajax로 하게 되면 개발 시간뿐 아니라 부하역시 무시할 수 없기 때문입니다.


ajax의 장/단점을 한번 알아봤습니다. 아무리 단점이 있다고 해도, 기술적으로 가장 큰 장점인 비동기적 요청과 서버의 부하분산을 위해서 반드시 ajax는 사용해야지 됩니다. Book을 추가하는 code를 한번 ajax로 구현해보도록 하겠습니다. (기본적으로 jquery를 이용하도록 하겠습니다.)

다음은 html과 javascript입니다. 기존과 다른 점은 값의 전달을 form의 submit이 아닌 button에 event를 bind시켜서 사용하고 있다는 점입니다. 

<script>
$(function() {
  $('#add-button').on('click', function() {
      var title = $('form input[name=title]').val();
      var author = $('form input[name=author]').val();
      var comment = $('form input[name=comment]').val();
      addBook(title, author, comment);
  });
});

function addBook(title, comment, author) {
    $.post("add03", 
            {
                "title" : title,
                "author" : author,
                "comment" : comment
            }, 
            function(jsonResult){
                alert(jsonResult);
            }, 'json')
            .done(function(jsonResult) {
                console.log(jsonResult);
            })
            .fail(function(jsonResult) {
                console.log(jsonResult);
            });
            
}
</script>

<form method="post" class="example-form">
    <fieldset>Book</fieldset>
    <label>책 제목</label>        
    <input type="text" name="title" placeholder = "책 제목을 입력해주세요."/>
    <label>작가 이름</label>
    <input type="text" name="author" placeholder = "작가 이름을 넣어주세요."/>
    <label>추가 설명</label>
    <input type="text" name="comment" placeholder = "추가 설명을 넣어주세요."/>
    <br/>
    <button id="add-button" type="button" class="btn">추가</button>
</form>

다음은 Controller 코드입니다.

    @RequestMapping(value = "/book/add03", method = RequestMethod.GET)
    public String add03(Model model) {
        Book book = new Book();
        model.addAttribute("book", book);
        return "book/add03";
    }

    @RequestMapping(value = "/book/add03", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, String> add03(@ModelAttribute Book book) {
        bookService.add(book);
        Map<String, String> bookDto = new HashMap<>();
        bookDto.put("title", book.getTitle());
        bookDto.put("author", book.getAuthor());
        bookDto.put("comment", book.getComment());

        return bookDto;
    }

기본적으로 json을 이용해서 book을 하나 insert 하게 되는 것을 볼 수 있습니다. 
지금까지 book을 추가하는 코드에 대해서 form을 이용하는 방법과 ajax를 이용하는 방법, 두가지 방법을 봤습니다. 하나 더 다른 내용을 보여드리겠습니다. 방금의 예시는 json을 이용해서 book에 대한 값을 다시 받아서 그 값을 이용해서 무언가 작업을 하는 것입니다. 만약에 paging을 해줘야지 되는 일이 발생한다면 이 것은 어떻게 처리를 해줘야지 될까요? 

2가지 방법이 있습니다. 

1. json을 만들어서 html 형식으로 문자열을 만든 다음에 특정 <div> section에 넣어주는 방법
2. Controller에서 html형식의 View를 return 시키고, 그 View를 특정 <div> section에 넣어주는 방법이 있습니다. 

이 두가지 방법에 대해서 알아보도록 하겠습니다. 

Ajax + JSON을 이용한 Paging

JSON을 이용해서 HTML을 만드는것은 HTML code 자체를 만드는 방법입니다. 먼저 Controller code를 살펴보도록 하겠습니다. 

    @RequestMapping(value = "book/list/index")
    public String getBookListIndex() {
        return "book/list/index";
    }

    @RequestMapping(value = "book/list/jsonpage", method = RequestMethod.POST)
    @ResponseBody
    public List<Book> getBookJsonList(@RequestParam(value = "pageIndex") int pageIndex,
            @RequestParam(value = "pageSize") int pageSize) {
        List<Book> books = bookService.listup(pageIndex, pageSize);
        return books;
    }

Controller는 2개의 method를 가지고 있습니다. 1개는 book list의 기본을 표시할 page이고, 2 번째 method는 book에 대한 json 값을 return 하는 method입니다. 이에 대한 HTML code를 살펴보도록 하겠습니다. 

<script type ="text/javascript">
$(function() {
    $('#btn-getbookList').on( 'click', function () {
        var pageIndex = parseInt($('input[name=pageIndex]' ).val());
        var pageSize = parseInt($('input[name=pageSize]' ).val());
        getBookList(pageIndex, pageSize);
    });
});

function getBookList(pageIndex, pageSize) {
    $.post("jsonpage", {
        pageIndex : pageIndex,
        pageSize : pageSize
    }, function(jsonResult) {
        var html = '<table class="table table-striped table-bordered table-hover">';
        for(var i = 0 ; i < jsonResult.length ; i++) {
            html += '<tr>';
            html += '<td>' + jsonResult[i].title + '</td>' ;
            html += '<td>' + jsonResult[i].author + '</td>' ;
            html += '<td>' + jsonResult[i].comment + '</td>' ;
            html += '</tr>';
        }
        html += '</table>';
        $( '#bookPage').html(html);       
    }, 'json');
}

</script>

<label>Page Index</label>
<input name ="pageIndex" type="number" />
<br/>
<label>Page Size</label>
<input name ="pageSize" type="number" value="3"/>
<br/>
<input type ="button" id="btn-getbookList" value="GetBookList"/>

<fieldset>
    <legend>Book List page </legend>
    <div id="bookPage" ></div>
</fieldset>

javascript를 이용해서 button에 click event를 bind시키고 있습니다. 그리고 getBookList function은 json을 통해서 얻어온 BookList의 숫자대로 loop를 돌아서 HTML code를 생성하는 것을 볼 수 있습니다. 최종적으로는 <table> 코드가 만들어져서 bookPagae라는 div tag에 추가 되는 형식으로 동작하게 됩니다. 


Ajax + HTML을 이용한 Paging

이 방법은 Controller에서 HTML View를 이용해서 보여지는 방법입니다. 서버단에서 View를 만들어주고, 그 View를 보여주는 방식으로 동작하게 됩니다. 이 View는 위 JSON으로 만들어지는 HTML과 완전히 동일한 HTML을 보내게 됩니다. 먼저 Controller 코드부터 살펴보도록 하겠습니다. 

    @RequestMapping( value = "book/list/index2" )
    public String getBookListIndex2() {
        return "book/list/index2" ;
    }

    @RequestMapping( value = "book/list/html" , method = RequestMethod.POST )
    public String getBookHtmlList( @RequestParam(value = "pageIndex") int pageIndex,
            @RequestParam(value = "pageSize") int pageSize ,
            Model model ) {
        List<Book > books = bookService. listup(pageIndex , pageSize);
        model.addAttribute ("books", books);
        return "book/list/html" ;
    }

Controller code는 매우 단순합니다. JSON으로 보내는것과 매우 유사합니다. 다만,  getBookHtmlList method에서 View name을 return 하는것만 다릅니다. book/list/html의 view code는 다음과 같이 구성됩니다.  view code는 바뀔 부분에 대한 HTML만을 보내게 됩니다. 

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ page language= "java" contentType ="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<table class ="table table-striped table-bordered table-hover">
<c:forEach var="book" items="${books } ">
    <tr>
        <td>${book.title }</td>
        <td>${book.author }</td>
        <td>${book.comment }</td>
    </tr>
</c:forEach>
</table>

위 javascript로 구성되는 html과 완전히 동일한 html을 만들것입니다. 이제 book/list/index2 view code를 살펴보도록 하겠습니다.

<script type="text/javascript">
$(function() {
    $('#btn-getbookList').on( 'click', function () {
        var pageIndex = parseInt($('input[name=pageIndex]' ).val());
        var pageSize = parseInt($('input[name=pageSize]' ).val());
        getBookList(pageIndex, pageSize);
    });
});

function getBookList(pageIndex, pageSize) {
    $.post("html", {
        pageIndex : pageIndex,
        pageSize : pageSize
    }, function(htmlResult) {
        $( '#bookPage').html(htmlResult);       
    });
}

</script >

<label>Page Index</label>
<input name ="pageIndex" type="number" />
<br/>
<label>Page Size</label>
<input name ="pageSize" type="number" value="3"/>
<br/>
<input type ="button" id="btn-getbookList" value="GetBookList"/>

<fieldset>
    <legend>Book List page </legend>
    <div id="bookPage" ></div>
</fieldset>

javascript에서 보내진 데이터를 그대로 div element에 집어넣은것 이외에는 차이가 없습니다. 


두 방법의 장,단점은 다음과 같습니다. 

방법장점단점
Ajax + JSON1. 데이터 전송량이 작다
2. client에서 html을 처리하기 때문에 server의 부하가 작다
1. javascript로 html을 만들어줘야지 되기 때문에 코드 작성에 어려움이 있다.
Ajax + HTML1. HTML + Server 코드이기 때문에 코드 작성이 간편하다

1. 데이터 전송량이 많다.
2. 서버에서 html rendering이 되기 때문에 server의 부하가 발생한다.


이 두 방법이 각각 장,단점을 가지고 있지만 어떤 방법이 더 우월하다고는 보기 힘듭니다. Ajax + HTML의 서버부하는 HTML cache를 통해서 충분히 해결이 될 수 있는 문제이고, javascript의 경우에도 구조화와 객체화를 통해서 충분히 쉬운 코드를 작성할 수 있습니다. 이것은 Style의 문제입니다. 그리고 개발에서 한가지 방법만을 정해두고 사용하기 보다는 주와 부를 나눠서 주는 Ajax + JSON으로 하고, javascript가 너무 복잡해지는 코드의 경우에는 Ajax + HTML로 구성하는것도 충분히 좋은 방법이라고 할 수 있습니다. 


Posted by Y2K
,

22. Spring View 처리 구조

Java 2013. 9. 12. 13:41

* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.


View는 사용자에게 최종적으로 보이는 영역입니다. 이 영역은 우리가 일반적으로 보는 Html이 될 수도 있고, Mobile App에게는 JSON format의 API 결과가 될 수도 있습니다. 
기본적으로 Spring Servlet/MVC를 기반으로 동작하게 됩니다. View 계층 또는 Presentation 계층이라고 합니다. 

Controller가 return한 결과에 대한 표현을 담당하는 영역으로 Controller가 넘겨주는 ModelAndView가 View에서 핵심 객체라고 할 수 있습니다. 

기본적인 동작은 DispatcherServlet이 ModelAndView에서 넘어온 값 ViewResolver를 통해서 해석한 결과를 사용자에게 보내줍니다. ViewResolver는 기본적으로 jsp를 기반으로 하고 있고, jsp 이외에 모든 web context가 될 수 있습니다. 

View의 기본 구조

기본적으로 Spring의 org.springframework.web.servlet.View interface를 구현한 객체를 View로 표현이 가능합니다. 일반적으로 View는 Spring 내부의 View를 확장하거나, pdf, rss, excel과 같은 다른 형태의 View를 표현하기 위해서 class를 확장해서 사용합니다. 또는 외부 View engine을 이용한 View의 표현 역시 가능합니다. 여기서 외부 View engine을 이용하는 것은 velocity, freemarker, tiles와 같은 외부의 View engine과의 연결 interface 및 설정을 구현함으로서 가능합니다. 

View의 기본 동작

Controller가 작업을 마친 후, View정보를 ModelAndView 객체에 담아서 보내주는 DispatcherServlet에 보내주는 것이 기본 동작입니다. 이에 대한 방법은 Spring @MVC는 두가지를 제공하고 있습니다. 첫번째는 View interface를 구현한 객체를 보내주는 방법이고, 다른 하나는 View의 이름만을 보내는 방법입니다. 첫번째 방법의 경우에는 View interface의 구현 방법에 따라 다양한 View를 표시하게 됩니다. DispatcherServlet과 Controller간에는 어떠한 동작도 존재하지 않습니다. 그렇지만 View의 이름만 보내주는 방식은 좀 다르게 동작하게 됩니다. 

View의 이름으로 표현되는 논리적인 View를 실질적인 View interface를 구현한 객체로 변경하는 작업이 필요하게되는데요. 이를 담당하는 객체를 ViewResolver라고 합니다. 
Spring에서 제공하는 주요 ViewResolver는 다음과 같습니다. 

ViewResolver 구현 classDescription
InternalResourceViewResolverView Name에서 jsp나 tiles 연동을 위한 View 객체를 반환
BeanNameViewResolverBean name을 기준으로 View 객체를 반환
ResourceBundlleViewResolverView 이름과 View 객체간의 mapping 정보를 저장하기 위해서 Resource 파일을 이용
XmlViewResolverView 이름과 View 객체간의 mapping 정보를 저장하기 위해서 xml 파일을 이용


기본적으로 만들어지는 ViewResolver의 interface는 다음과 같습니다. 
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

다국어 지원을 위한 locale 정보와 View 이름을 이용한 View 객체를 얻어내는 간단한 interface만을 가지고 있습니다. 이를 이용해서 새로운 ViewResolver를 만드는 것 역시 가능합니다. 

구성되는 View는 다음과 같은 interface를 가지고 있습니다. 

public interface View {
    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
    String getContentType();
    void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

들어오게되는 model을 어떻게 render하는지에 대한 interface 선언을 볼 수 있습니다. 

자, 전에 봤던 Spring MVC에서 Request가 처리되는 과정을 다시 한번 봐보도록 하겠습니다. 

지금까지는 Step2까지 알아본 상황입니다. 이제 Step 2에서  Step 3 사이의 ModelAndView가 Return이 되고 ViewResolver가 ViewName을 통해서 실질적인 View를 Return 시켜주는 영역을 알아봐야지 됩니다. 그리고, 그 설정은 Spring에서 제공하는 ViewResolver interface에서 담당하고 있습니다.  위 그림에서 Step 3이 생략되는 경우 역시 존재할 수 있습니다. 앞에서 이야기한것 처럼, 직접 View가 return 될 때에는 Step 3의 ViewResolver가 동작을 하지 않고, 바로 Step 4로 이동하게 됩니다. 



Summary

이번장에서는 Controller가 return 시켜주는 Model을 render 시켜주는 View interface에 대해서 알아봤습니다.  Spring MVC에서 Request가 처리되는 과정을 좀 더 살펴보시길 바랍니다. 


Posted by Y2K
,
Data Binding : 규약을 이용하여 데이터 Entiry를 다루는 MVC Framework의 기능.

Model Binding
: HTML form의 submit시에 응용프로그램은 key/value pair로 폼의 데이터를 담고 있는 HTTP response를 받게 된다.
이때에, Http 요청 데이터를 Action method의 매개변수 및 사용자 정의 .NET 객체와 직접적으로 mapping하기 위한 알고리즘을 제공한다.

Model Binding 시의 데이터 적용 순서
1) Form(POST 매개변수 : FormCollection)
2) RouteData
3) QueryString

사용자 정의 형식에 대한 Model Binding
: 기본적으로 DefaultModelBinder는 {parameterName.PropertyName}으로 검색을 한다.
(* 대소문자는 가리지 않는다.)

다음과 같은 View가 존재한다고 할때에, 대응되는 ActionMethod는 다음과 같다.

New Product

<%=Html.ValidationSummary() %> <%using(Html.BeginForm()) { %> Name : <%=Html.TextBox("productValue.name") %>
Price : <%=Html.TextBox("productValue.price")%>
Description : <%=Html.TextBox("productValue.description")%>
<%} %>

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(Product productValue)
{
    try
    {
        productValue.CheckValidation();
    }
    catch(ProductException ex)
    {
        foreach(string key in ex.Keys)
        {
            ModelState.AddModelError(key, ex[key]);
        }                
        return View();
    }

    return RedirectToAction("NewResult",
        new { product = productValue });
}

BindAttribute의 이용
Bind Attribute는 Action Method의 매개변수 이름이 아닌 다른 이름으로 Binding을 원하거나, 특정 속성들이 모델 바인딩의 대상이 되어야하는지를 엄밀하게 제어할 필요가 있을 때 사용된다.

/// 
/// 다음 ActionMethod는 productA.*, productB.* 로 prefix된 view의 id를 이용해서 
/// 값을 Binding한다.
/// 
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New([Bind(Prefix = "productA")] Product product1, 
    [Bind(Prefix = "productB")]Product product2)
{

}
/// 
/// 다음 ActionMethod는 Name과 Price를 Binding에 포함한다.
/// 
/// 
/// 
public ActionResult RegisterMember([Bind(Include = "Name, Price")] Product product)
{

}
/// 
/// 다음 ActionMethod는 DateOfBirthDay를 Binding하지 않는다.
/// 
/// 
/// 
public ActionResult DeregisterMember([Bind(Exclude = "DateOfBirthDay")] Product product)
{

}

[Bind(Include="Name")]
public class Product
{        
    public string Name { get; set; }
    public string Price { get; set; }
    public string Description { get; set; }
}

Array, Collection, Dictionary에 대한 Model Binding
: 동일한 이름을 갖는 여러개의 Textbox를 Render하는 View의 Controller는 다음과 같이 사용될 수 있다.

<%=Html.TextBox("movies") %>
<%=Html.TextBox("movies") %>
<%=Html.TextBox("movies") %>
public ActionResult DoSomething(IList movies) { throw new NotImplementedException(); }


사용자 정의 Entry의 Group에 대해서는 다른 방법이 필요하게 되는데, DefaultModelBinder는 ActionMethod의 사용에 C#의 문법과 동일한 명명 규약을 따르기를 요구한다. 다음과 같은 ActionMethod가 존재할 때에, 사용되는 View는 다음과 같다.

public ActionResult DoSomething(IList products)
{
    foreach(Product product in products)
    {
        System.Diagnostics.Debug.WriteLine(product.Name);
    }
    throw new NotImplementedException();
}


<%using(Html.BeginForm("DoSomething", "Products")) { %>
    <%for(int i = 0 ; i < 10 ; i++) { %>
        

<%=string.Format("Product : {0}", i) %>

<%--C#에서 사용되는 배열 문법과 동일하다.(products[0].Name, products[0].Price)--%> Name : <%=Html.TextBox("products["+i.ToString()+"].Name") %>
Price : <%=Html.TextBox("products["+i.ToString()+"].Price") %>
Description : <%=Html.TextBox("products["+i.ToString()+"].Description") %>
<%} %> <%} %>

사용자 지정 Model Binder의 지정
: 특정한 데이터에 대하여 사용자 지정 Model Binder를 사용하고 싶은 경우에는 IModelBinder interface를 상속한 사용자 지정 ModelBinder를 만들어주면 된다.



Model Binder가 사용되도록 구성하기 위해서 3가지 방법중 하나를 사용할 수 있다.
1) Model에 ModelBinderAttribute의 적용
[ModelBinder(typeof(XDocumentBinder))]
public class XDocument
{
    //...
}
2) ModelBinders.Binders.Add를 이용, 전역 ModelBinder에 등록하기
ModelBinders.Binders.Add(typeof(XDocument), new XDocumentBinder());
3) 매개변수를 Binding할때, Parameter attribute를 넣어주기
public ActionResult DoSomething([ModelBinder(typeof(XDocumentBinder))] XDocument xml)
{
    //...
}

.NET MVC Framework에서 ModelBinder Select 순위
1) Binding시에 명시적으로 지정된 Binder
2) 대상 형식을 위해 ModelBinders.Binders에 등록된 Binder
3) 대상 형식에 ModelBinderAttribute를 사용하여 할당된 Binder
4) DefaultModelBinder


Posted by Y2K
,
View는 WebApplication의 Request가 Response로 바뀌는 BlockBox가 된다.

MVC에서 View는 완전한 출력을 담당하는 책임을 가지고 있다. 단지 Controller의 출력만을 담당하며, 간결한 표현 로직을 사용하여 그 출력을 최종 HTML로 Render하는 임무를 가진다. 그렇지만, 표현로직과 업무로직사이의 경계는 매우 모호하다. 업무로직과 표현 로직에 대한 것은 조금은 주관적이라고 생각이 되기 때문에.. 최대한 Controller에서 동작을 하고 표현 부분에 대해서는 로직의 최대한도로 줄이는 것을 목표로 하는 것이 가장 좋다.
또한, View의 문제는 Test에 있다. Html을 테스트하는 것은 GUI 프로그램에서 사용자 액션을 테스트하는 것과 동일하다. 테스트 할 방법도 마땅하지 않으며, 표시된 HTML을 TEXT단위로 테스트 하는 것은 공백 문제부터 시작해서 많은 어려움이 있기 때문에, 많은 책들의 저자와 비슷하게 VIEW는 봐서 잘 되는 것이 최고다. 라는 생각이 든다. 딱히 테스트 할 방법도 안보이는 것이 사실이다.;

WebForm View Engine
: MVC Framework는 WebFormViewEngine이라는 class로 구성된 View Engine을 사용하고 있다. 이 ViewEngine은 기존의 ASP .NET WebForm Engine을 기반으로 구축되어있다. 그러나 WebFormViewEngine에서 View는 단순한 HTML Template이다. 이는 Controller에서 받은 특정 데이터인 ViewData와 강력히 형식화된 Model 객체를 기반으로 만들어진 HTML 문자를 표현하는 기능만을 가지게 된다. 또한 내부적으로 사용되는 기반 기술은 ASP .NET WebForm 서버 페이지 기술과 동일하다.만약에 자체적인 View Engine을 만들고 싶다면, IViewEngine과 IView interface를 상속받아 구현하면 된다.

View의 데이터 전송
: ViewData라는 Dictionary<string, object> 형식의 데이터 또는 강력한 형식의 Model을 이용하는 방법이 있다. 두 방법의 차이는 Controller에서는 전혀 알 필요가 없다. 단지 Controller에서 데이터 형식의 Model을 전송해서 View로 전달 될때, 강력한 Model 형식의 View의 경우에는 형변환이 가능한지만을 알아보게 된다. 따라서 이 방법은 매우 유용한 것이 된다.

ViewData.Eval()의 이용
: ViewData.Eval()은 대소문자를 가리지 않고, 값이 없는 경우에 String.Empty를 리턴하는 사용하기 편한 함수이다. 또한 Eval함수는 .으로 구분되는 토큰 명명법을 사용하고 있기 때문에 Model의 속성또한 표현할 수 있다. ViewData에 key를 Name으로 넣었는지, name으로 넣었는지. 아니면 값이 없는 경우에 NULL처리를 해주는 것을 빼먹지 않았는지 신경쓰지 않고 사용하기에 매우 좋은 함수이다. 또한 Html Helper method에서 Html.TextBox("comment")를 사용하는 경우, 이 코드는 다음과 같다. <input name="comment" id="comment" type="text" value="<%=Html.Encode(ViewData.Eval("comment"))%>"/>. 편하게 작성도 가능하고, 값이 없는 경우에 나오는 모든 에러처리를 해주기 때문에 간결한 코드 작성이 가능하다.
Posted by Y2K
,