ViewModel 모범 사례


238

에서 이 질문에 , 그것은 컨트롤러가 만들도록하는 것이 합리적 것 같습니다 뷰 모델 보다 정확하게보기를 표시하려고하고있는 모델을 반영하지만, 나는 (내가 MVC 패턴에 새로운 해요 규칙의 일부에 대해 궁금 해요 아직 명확하지 않은 경우).

기본적으로 다음과 같은 질문이 있습니다.

  1. 나는 보통 하나의 클래스 / 파일을 갖고 싶습니다. 로모그래퍼이 메이크업 감각을합니까 뷰 모델 이 만 볼 수있는 컨트롤러에서 데이터 떨어져 손에 작성되는 경우?
  2. 경우 뷰 모델은 자체 파일에 속하지 않는, 당신은, 일을 분리 유지하기 위해 디렉토리 / 프로젝트 구조를 사용하는 곳 수행 뷰 모델에 속하는 파일을? 에서 컨트롤러 디렉토리?

그것은 기본적으로 지금입니다. 몇 가지 질문이 더 나올 수도 있지만 지난 한 시간 동안 저를 괴롭 히고 다른 곳에서 일관된 지침을 찾을 수 있습니다.

편집 : CodePlex 의 샘플 NerdDinner 앱 을 보면 ViewModels가 Controllers의 일부인 것처럼 보이지만 여전히 자신의 파일에 있지 않은 것이 불편합니다.


66
나는 NerdDinner를 "Best Practices"예제라고 정확히 말하지는 않을 것이다. 당신의 직감이 당신에게 잘 봉사합니다. :)
Ryan Montgomery

답변:


211

각 뷰에 대해 "ViewModel"이라고하는 것을 만듭니다. MVC 웹 프로젝트의 ViewModels 폴더에 넣었습니다. 나는 컨트롤러와 그들이 나타내는 행동 (또는보기)의 이름을 따서 명명합니다. 따라서 멤버쉽 컨트롤러의 SignUp보기에 데이터를 전달해야하는 경우 MembershipSignUpViewModel.cs 클래스를 만들어 ViewModels 폴더에 넣습니다.

그런 다음 컨트롤러에서보기로 데이터를 쉽게 전송할 수 있도록 필요한 속성과 방법을 추가합니다. Automapper를 사용하여 ViewModel에서 도메인 모델로 이동하고 필요한 경우 다시 돌아옵니다.

이것은 다른 ViewModel 유형의 속성을 포함하는 복합 ViewModel에도 효과적입니다. 예를 들어 멤버쉽 컨트롤러의 인덱스 페이지에 5 개의 위젯이 있고 각 부분 뷰에 대해 ViewModel을 만든 경우 인덱스 작업에서 부분으로 데이터를 어떻게 전달합니까? MyPartialViewModel 유형의 MembershipIndexViewModel에 속성을 추가하고 부분을 렌더링 할 때 Model.MyPartialViewModel에 전달합니다.

이렇게하면 인덱스 뷰를 전혀 변경하지 않고도 부분 ViewModel 속성을 조정할 수 있습니다. 여전히 Model.MyPartialViewModel을 전달하기 때문에 부분 ViewModel에 속성을 추가하는 것이 무엇이든 할 때 무언가를 해결하기 위해 전체 부분 체인을 거치지 않아도됩니다.

또한 네임 스페이스 "MyProject.Web.ViewModels"를 web.config에 추가하여 각 뷰에 명시적인 import 문을 추가하지 않고도 모든 뷰에서 참조 할 수 있도록합니다. 좀 더 깨끗하게 만듭니다.


3
부분 뷰에서 POST하고 전체 뷰를 반환하려면 (모델 오류의 경우) 어떻게해야합니까? 부분보기에서는 상위 모델에 액세스 할 수 없습니다.
Cosmo

5
@Cosmo : 그런 다음 모델 오류가 발생한 경우 전체보기를 반환 할 수 있는 작업으로 POST합니다 . 서버 측에서 상위 모델을 다시 작성하기에 충분합니다.
Tomas Aschan

로그인 [POST] 및 로그인 [GET] 작업은 어떻습니까? 다른 뷰 모델로?
Bart Calixto

일반적으로 로그인 [GET]은 데이터를로드 할 필요가 없으므로 ViewModel을 호출하지 않습니다.
Andre Figueiredo

훌륭한 조언. 모델 / VM 속성의 데이터 액세스, 처리 및 설정은 어디로 가야합니까? 필자의 경우 로컬 CMS 데이터베이스에서 일부 데이터를 가져오고 일부는 웹 서비스에서 가져 오므로 모델에서 설정하기 전에 처리 / 조작해야합니다. 컨트롤러에 모든 것을 넣으면 꽤 지저분 해집니다.
xr280xr

124

범주 (컨트롤러, 뷰 모델, 필터 등)별로 클래스를 분리하는 것은 의미가 없습니다.

웹 사이트의 홈 섹션 (/)에 대한 코드를 작성하려면 Home이라는 폴더를 만들고 HomeController, IndexViewModel, AboutViewModel 등 및 홈 작업에 사용되는 모든 관련 클래스를 저장하십시오.

ApplicationController와 같은 클래스를 공유 한 경우 프로젝트의 루트에 놓을 수 있습니다.

왜 관련이있는 것들 (HomeController, IndexViewModel)을 분리하고 전혀 관계가없는 것들 (HomeController, AccountController)을 유지 하는가?


이 주제에 관한 블로그 게시물을 작성했습니다 .


13
이 작업을 수행하면 상황이 꽤 복잡해집니다.
UpTheCreek

14
아니, 지저분한 것은 모든 컨트롤러를 하나의 디렉토리 / 네임 스페이스에 두는 것입니다. 각각 5 개의 뷰 모델을 사용하는 5 개의 컨트롤러가있는 경우 25 개의 뷰 모델이 있습니다. 네임 스페이스는 코드를 구성하는 메커니즘이며 여기서 다르지 않아야합니다.
Max Toro

41
@ 맥스 토로 : 당신이 너무 많이 downvoted있어 놀랐습니다. 약간의 시간이 ASP.Net MVC 작업 후, 나는 느끼고 많은 것으로부터 고통을 모두 한 곳에서 ViewModels를 모든 다른의 컨트롤러를, 그리고 모든 뷰의 또 다른. MVC는 그들이 관련 부분의 트리오 되어 결합 - 서로를지지한다. 주어진 섹션의 컨트롤러, ViewModels 및 뷰가 동일한 디렉토리에 함께 있으면 솔루션을 훨씬 더 체계적으로 구성 할 수 있다고 생각 합니다. MyApp / Accounts / Controller.cs, MyApp / Accounts / Create / ViewModel.cs, MyApp / Accounts / Create / View.cshtml 등
quentin-starin

13
@RyanJMcGowan 우려의 분리는 클래스의 분리가 아닙니다.
Max Toro

12
@RyanJMcGowan 개발 방식에 관계없이 특히 대규모 앱의 경우 문제가 발생합니다. 유지 관리 모드에서는 모든 모델과 모든 컨트롤러에 대해 생각하지 않고 한 번에 하나의 기능을 추가합니다.
Max Toro

21

응용 프로그램 클래스를 "Core"(또는 별도의 클래스 라이브러리)라는 하위 폴더에 보관하고 KIGG 샘플 응용 프로그램 과 동일한 방법을 사용 하지만 약간 더 변경하여 응용 프로그램을 더 건조하게 만듭니다.

일반적인 사이트 전체 속성을 저장하는 / Core / ViewData /에 BaseViewData 클래스를 만듭니다.

이 후에도 동일한 폴더에 모든 View ViewData 클래스를 만든 다음 BaseViewData에서 파생하여 특정 뷰 속성을 갖습니다.

그런 다음 모든 컨트롤러에서 파생되는 ApplicationController를 만듭니다. ApplicationController에는 다음과 같은 일반적인 GetViewData 메소드가 있습니다.

protected T GetViewData<T>() where T : BaseViewData, new()
    {
        var viewData = new T
        {
           Property1 = "value1",
           Property2 = this.Method() // in the ApplicationController
        };
        return viewData;
    }

마지막으로 내 컨트롤러 작업에서 ViewData 모델을 빌드하기 위해 다음을 수행합니다.

public ActionResult Index(int? id)
    {
        var viewData = this.GetViewData<PageViewData>();
        viewData.Page = this.DataContext.getPage(id); // ApplicationController
        ViewData.Model = viewData;
        return View();
    }

나는 이것이 정말로 잘 작동한다고 생각하고 당신의 견해를 깔끔하게 유지하고 컨트롤러는 날씬하게 만듭니다.


13

ViewModel 클래스는 클래스의 인스턴스로 표현되는 여러 데이터를 하나의 관리하기 쉬운 객체로 캡슐화하여 View에 전달할 수 있습니다.

ViewModel 클래스를 자체 파일의 자체 디렉토리에 두는 것이 좋습니다. 내 프로젝트에는 ViewModels라는 Models 폴더의 하위 폴더가 있습니다. 그것이 내 ViewModels (예 :)가 ProductViewModel.cs사는 곳입니다.


13

모델을 보관하기에 적합한 장소는 없습니다. 프로젝트가 크고 ViewModel (데이터 전송 객체)이 많은 경우 별도의 어셈블리로 유지할 수 있습니다. 또한 사이트 프로젝트의 별도 폴더에 보관할 수 있습니다. 예를 들어, Oxite 에서는 다양한 클래스가 포함 된 Oxite 프로젝트에 배치됩니다. Oxite의 컨트롤러는 별도의 프로젝트로 이동하고 뷰도 별도의 프로젝트에 있습니다.
에서 CodeCampServer ViewModels *이 양식 명명 된 그들은 모델 폴더에 UI 프로젝트에 배치됩니다.
에서 MvcPress의 프로젝트 그들은 또한 데이터베이스와 좀 더와 작업에 대한 모든 코드를 포함하는 데이터 프로젝트에 배치됩니다 (하지만이 방법을 권장하지 않았다, 그것은 샘플은 단지)
많은 관점이 있습니다. 일반적으로 사이트 프로젝트에 내 ViewModel (DTO 객체)을 유지합니다. 그러나 10 개가 넘는 모델이있는 경우 별도의 어셈블리로 모델을 옮기는 것을 선호합니다. 일반적 으로이 경우 컨트롤러를 분리하여 어셈블리를 분리합니다.
또 다른 질문은 모델의 모든 데이터를 ViewModel에 쉽게 매핑하는 방법입니다. AutoMapper 라이브러리를 살펴 보는 것이 좋습니다 . 나는 그것을 매우 좋아한다, 그것은 나를 위해 모든 더러운 일을한다.
또한 SharpArchitecture 프로젝트 를 살펴볼 것을 제안합니다 . 프로젝트를위한 매우 훌륭한 아키텍처를 제공하며 멋진 프레임 워크와 지침 및 훌륭한 커뮤니티가 많이 포함되어 있습니다.


8
ViewModels! = DTO
Bart Calixto

6

모범 사례의 코드 스 니펫은 다음과 같습니다.

    public class UserController : Controller
    {
        private readonly IUserService userService;
        private readonly IBuilder<User, UserCreateInput> createBuilder;
        private readonly IBuilder<User, UserEditInput> editBuilder;

        public UserController(IUserService userService, IBuilder<User, UserCreateInput> createBuilder, IBuilder<User, UserEditInput> editBuilder)
        {
            this.userService = userService;
            this.editBuilder = editBuilder;
            this.createBuilder = createBuilder;
        }

        public ActionResult Index(int? page)
        {
            return View(userService.GetPage(page ?? 1, 5));
        }

        public ActionResult Create()
        {
            return View(createBuilder.BuildInput(new User()));
        }

        [HttpPost]
        public ActionResult Create(UserCreateInput input)
        {
            if (input.Roles == null) ModelState.AddModelError("roles", "selectati macar un rol");

            if (!ModelState.IsValid)
                return View(createBuilder.RebuildInput(input));

            userService.Create(createBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }

        public ActionResult Edit(long id)
        {
            return View(editBuilder.BuildInput(userService.GetFull(id)));
        }

        [HttpPost]
        public ActionResult Edit(UserEditInput input)
        {           
            if (!ModelState.IsValid)
                return View(editBuilder.RebuildInput(input));

            userService.Save(editBuilder.BuilEntity(input));
            return RedirectToAction("Index");
        }
}

5

모든 ViewModel을 Models 폴더에 넣습니다 (모든 비즈니스 로직은 별도의 ServiceLayer 프로젝트에 있음)


4

개인적으로 ViewModel이 사소한 것이 아닌 경우 별도의 클래스를 사용하는 것이 좋습니다.

하나 이상의 뷰 모델이있는 경우 적어도 디렉토리에서 파티션을 분할하는 것이 좋습니다. 뷰 모델이 나중에 공유되면 디렉토리에 내재 된 네임 스페이스를 통해 새 어셈블리로 쉽게 이동할 수 있습니다.


2

이 경우 뷰와 별개의 프로젝트에 모델과 컨트롤러가 있습니다.

경험상, 대부분의 ViewData [ "..."] 항목을 ViewModel로 옮기고 피하려고 시도했습니다. 따라서 캐스팅과 마법 문자열을 피하는 것이 좋습니다.

ViewModel은 또한 빵 부스러기와 제목을 그리는 페이지의 목록 정보 또는 페이지 정보에 대한 페이지 매김 정보와 같은 일반적인 속성을 보유합니다. 현재 기본 클래스는 내 의견으로는 너무 많은 정보를 보유하고 있으며 기본 뷰 모델 페이지의 99 %에 대해 가장 기본적이고 필요한 정보 인 3 가지 부분으로 나눈 다음 목록 및 모델의 모델로 나눌 수 있습니다. 해당 시나리오에 대한 특정 데이터를 보유하고 기본 시나리오에서 상속되는 양식의 경우

마지막으로 각 엔터티가 특정 정보를 처리 할 수있는 뷰 모델을 구현합니다.


0

컨트롤러의 코드 :

    [HttpGet]
        public ActionResult EntryEdit(int? entryId)
        {
            ViewData["BodyClass"] = "page-entryEdit";
            EntryEditViewModel viewMode = new EntryEditViewModel(entryId);
            return View(viewMode);
        }

    [HttpPost]
    public ActionResult EntryEdit(Entry entry)
    {
        ViewData["BodyClass"] = "page-entryEdit";            

        #region save

        if (ModelState.IsValid)
        {
            if (EntryManager.Update(entry) == 1)
            {
                return RedirectToAction("EntryEditSuccess", "Dictionary");
            }
            else
            {
                return RedirectToAction("EntryEditFailed", "Dictionary");
            }
        }
        else
        {
            EntryEditViewModel viewModel = new EntryEditViewModel(entry);
            return View(viewModel);
        }

        #endregion
    }

뷰 모델의 코드 :

public class EntryEditViewModel
    {
        #region Private Variables for Properties

        private Entry _entry = new Entry();
        private StatusList _statusList = new StatusList();        

        #endregion

        #region Public Properties

        public Entry Entry
        {
            get { return _entry; }
            set { _entry = value; }
        }

        public StatusList StatusList
        {
            get { return _statusList; }
        }

        #endregion

        #region constructor(s)

        /// <summary>
        /// for Get action
        /// </summary>
        /// <param name="entryId"></param>
        public EntryEditViewModel(int? entryId)
        {
            this.Entry = EntryManager.GetDetail(entryId.Value);                 
        }

        /// <summary>
        /// for Post action
        /// </summary>
        /// <param name="entry"></param>
        public EntryEditViewModel(Entry entry)
        {
            this.Entry = entry;
        }

        #endregion       
    }

프로젝트 :

  • DevJet.Web (ASP.NET MVC 웹 프로젝트)

  • DevJet.Web.App.Dictionary (별도의 클래스 라이브러리 프로젝트)

    이 프로젝트에서 DAL, BLL, BO, VM (뷰 모델 용 폴더)과 같은 폴더를 만들었습니다.


안녕하세요, Entry 클래스의 구조는 무엇입니까?
Dinis Cruz

0

작업 결과 및 상황 별 데이터와 같이 일반적으로 필요한 속성이있는 뷰 모델 기본 클래스를 작성하고 현재 사용자 데이터 및 역할을 넣을 수도 있습니다.

class ViewModelBase 
{
  public bool HasError {get;set;} 
  public string ErrorMessage {get;set;}
  public List<string> UserRoles{get;set;}
}

기본 컨트롤러 클래스에는 PopulateViewModelBase ()와 같은 메소드가 있으며이 메소드는 컨텍스트 데이터 및 사용자 역할을 채 웁니다. HasError 및 ErrorMessage는 service / db에서 데이터를 가져 오는 동안 예외가있는 경우 이러한 특성을 설정하십시오. 이러한 속성을보기에 바인딩하여 오류를 표시하십시오. 사용자 역할을 사용하여 역할을 기반으로보기에 숨기기 섹션을 표시 할 수 있습니다.

다른 get 액션으로 뷰 모델을 채우려면 추상 메소드 FillModel을 사용하여 기본 컨트롤러를 사용하여 뷰 모델을 일관성있게 만들 수 있습니다.

class BaseController :BaseController 
{
   public PopulateViewModelBase(ViewModelBase model) 
{
   //fill up common data. 
}
abstract ViewModelBase FillModel();
}

컨트롤러에서

class MyController :Controller 
{

 public ActionResult Index() 
{
   return View(FillModel()); 
}

ViewModelBase FillModel() 
{ 
    ViewModelBase  model=;
    string currentAction = HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString(); 
 try 
{ 
   switch(currentAction) 
{  
   case "Index": 
   model= GetCustomerData(); 
   break;
   // fill model logic for other actions 
}
}
catch(Exception ex) 
{
   model.HasError=true;
   model.ErrorMessage=ex.Message;
}
//fill common properties 
base.PopulateViewModelBase(model);
return model;
}
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.