views:

4264

answers:

10

From this question, it looks like it makes sense to have a controller create a ViewModel that more accurately reflects the model that the view is trying to display, but I'm curious about some of the conventions (I'm new to the MVC pattern, if it wasn't already obvious).

Basically, I had the following questions:

  1. I normally like to have one class/file. Does this make sense with a ViewModel if it is only being created to hand off data from a controller to a view?
  2. If a ViewModel does belong in its own file, and you're using a directory/project structure to keep things separate, where does the ViewModel file belong? In the Controllers directory?

That's basically it for now. I might have a few more questions coming up, but this has been bothering me for the last hour or so, and I can seem to find consistent guidance elsewhere.

EDIT: Looking at the sample NerdDinner app on CodePlex, it looks like the ViewModels are part of the Controllers, but it still makes me uncomfortable that they aren't in their own files.

+2  A: 

Personally I'd suggest if the ViewModel is anything but trivial then use a separate class.

If you have more than one view model then I suggest it make sense to partition it in at least a directory. if the view model is later shared then the name space implied in the directory makes it easier to move to a new assembly.

Preet Sangha
+5  A: 

A ViewModel class is there to encapsulate multiple pieces of data represented by instances of classes into one easy to manage object that you can pass to your View.

It would make sense to have your ViewModel classes in their own files, in the own directory. In my projects I have a sub-folder of the Models folder called ViewModels. That's where my ViewModels (e.g. ProductViewModel.cs) live.

JMs
+5  A: 

There are no good place to keep your models in. You can keep them in separate assembly if the project is big and there are a lot of ViewModels (Data Transfer Objects). Also you can keep them in separate folder of the site project. For example, in Oxite they are placed in Oxite project which contains a lot of various classes too. Controllers in Oxite are moved to separate project and views are in separate project too.
In CodeCampServer ViewModels are named *Form and they are placed in UI project in Models folder.
In MvcPress project they are placed in Data project, which also contains all code to work with database and a bit more (but I didn't recommend this approach, it's just for a sample)
So you can see there are many point of view. I usually keep my ViewModels (DTO objects) in the site project. But when I have more than 10 models I prefer to move them to separate assembly. Usually in this case I'm moving controllers to separate assembly too.
Another question is how to easily map all data from model to your ViewModel. I suggest to have a look at AutoMapper library. I like it very much, it does all dirty work for me.
And I also I suggest to look at SharpArchitecture project. It provides very good architecture for projects and it contains a lot of cool frameworks and guidances and great community.

zihotki
+6  A: 

I keep my application classes in a sub folder called "Core" (or a seperate class library) and use the same methods as the KIGG sample application but with some slight changes to make my applications more DRY.

I create a BaseViewData class in /Core/ViewData/ where I store common site wide properties.

After this I also create all of my view ViewData classes in the same folder which then derive from BaseViewData and have view specific properties.

Then I create an ApplicationController that all of my controllers derive from. The ApplicationController has a generic GetViewData Method as follows:

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

Finally, in my Controller action i do the following to build my ViewData Model

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

I think this works really well and it keeps your views tidy and your controllers skinny.

Mark
+17  A: 

I create what I call a "ViewModel" for each view. I put them in a folder called ViewModels in my MVC Web project. I name them after the controller and action (or view) they represent. So if I need to pass data to the SignUp view on the Membership controller I create a MembershipSignUpViewModel.cs class and put it in the VieWModels folder.

Then I add the necessary properties and and methods to facilitate the transfer of data from the controller to the view. I use the Automapper to get from my ViewModel to the Domain Model and back again if necessary.

This also works well for composite ViewModels that contain properties that are of the type of other ViewModels. For instance if you have 5 widgets on the index page in the membership controller, and you created a ViewModel for each partial view - how do you pass the data from the Index action to the partials? You add a property to the MembershipIndexViewModel of type MyPartialViewModel and when rendering the partial you would pass in Model.MyPartialViewModel.

Doing it this way allows you to adjust the partial ViewModel properties without having to change the Index view at all. It still just passes in Model.MyPartialViewModel so there is less of a chance that you will have to go through the whole chain of partials to fix something when all you're doing is adding a property to the partial ViewModel.

I will also add the namespace "MyProject.Web.ViewModels" to the web.config so as to allow me to reference them in any view without ever adding an explicit import statement on each view. Just makes in a little cleaner.

Ryan Montgomery
What if you want to POST from a partial view and return the whole view (in case of model error)? Within the partial view you don't have access to the parent model.
Cosmo
A: 

In our case we have the Models along with the Controllers in a project separate from the Views.

As a rule of thumb, we've tried to move and avoid most of the ViewData["..."] stuff to the ViewModel thus we avoid castings and magic strings, which is a good thing.

The ViewModel as well holds some common properties like pagination information for lists or header information of the page to draw breadcrumbs and titles. At this moment the base class holds too much information in my opinion and we may divide it in three pieces, the most basic and necessary information for 99% of the pages on a base view model, and then a model for the lists and a model for the forms that hold specific data for that scenarios and inherit from the base one.

Finally, we implement a view model for each entity to deal with the specific information.

Marc Climent
A: 

We throw all of our ViewModels in the Models folder (all of our business logic is in a separate ServiceLayer project)

Jess
A: 

Separating classes by category (Controllers, ViewModels, Filters etc.) is nonsense.

If you want to write code for the Home section of your website (/) then create a folder named Home, and put there the HomeController, IndexViewModel, AboutViewModel, etc. and all related classes used by Home actions.

If you have shared classes, like an ApplicationController, you can put it at the root of your project.

Why separate things that are related (HomeController, IndexViewModel) and keep things together that have no relation at all (HomeController, AccountController) ?

Max Toro
I'm fairly certain that ASP.NET MVC, and, for that matter, Ruby on Rails enforce this separation (convention over configuration).
jerhinesmith
ASP.NET MVC does NOT enforce this separatation. They are just classes on an assembly, they can be on any directory/namespace.
Max Toro
Things are gonna get pretty messy pretty quickly if you do this.
UpTheCreek
Nope, messy is to put all controllers in one dir/namespace. If you have 5 controllers, each using 5 viewmodels, then you've got 25 viewmodels. Namespaces is the mechanism for organizing code, and shouldn't be any different here.
Max Toro
ASP.NET MVC defaults to looking under the /View folder for views. Putting views elsewhere is gonna cause problems.
jameszhao00
@jameszhao00 who said anything about Views?
Max Toro
@max what about Controllers then? They're also searched via a pre-defined path.
jameszhao00
@jameszhao00 You are wrong, controllers are searched by class name. In fact, on the ControllerBuilder there's a DefaultNamespaces collection, that can be used to disambiguate when 2 controllers have the same class name but different namespace.
Max Toro
A: 

code in the controller:

    [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
    }

code in view model:

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       
    }

projects:

  • DevJet.Web ( the ASP.NET MVC web project)

  • DevJet.Web.App.Dictionary ( a seperate Class Library project)

    in this project, i made some folders like: DAL, BLL, BO, VM (folder for view models)

C.T.
A: 

here's a code snippet from my best practices:

    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");
        }
}
Omu