ViewModel Best Practices
Asked Answered
B

11

240

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.

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.

Bilingual answered 19/3, 2009 at 21:48 Comment(1)
I wouldn't exactly call NerdDinner a "Best Practices" example. Your intuition serves you well. :)Mineralogist
M
214

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 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 it a little cleaner.

Mineralogist answered 29/3, 2009 at 5:55 Comment(9)
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.Hedge
@Cosmo: Then POST to an action that can return the entire view in case of a model error. On the server side, you have enough to recreate the parent model.Watercool
What about a login [POST] and login [GET] actions? with different viewmodels?Mauldin
Usually, login [GET] don't call ViewModel because don't need to load any data.Amorphism
Great advice. Where should data access, processing and setting of model/VM properties go? In my case we will have some data coming from a local CMS database and some coming from web services, which will need to be processed/manipulated before being set on a model. Putting all that in the controller gets pretty messy.Catholic
What view model will be sent to Action from a composite view, which uses a composite ViewModel.Jacobjacoba
We have a service layer in our project. In the case of a view model that might update multiple domain models, is it a good idea to create a middle man "input model" to handle the packaging up of data that you would like to send to the service layer to conduct a data transaction or just pass all the extra data that might be in the view model and ignore it in the service layer?Adele
"how do you pass the data from the Index action to the partials?" - Use ChildActions, for example.Triserial
I have an overarching view that contains several partial views. These partial views are loaded separately from the main view (via ajax). Is it common to practice to also have viewmodels for every partial view that I have as well?Oney
S
128

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) ?


I wrote a blog post about this topic.

Sussna answered 6/10, 2009 at 23:7 Comment(31)
I'm fairly certain that ASP.NET MVC, and, for that matter, Ruby on Rails enforce this separation (convention over configuration).Bilingual
ASP.NET MVC does NOT enforce this separatation. They are just classes on an assembly, they can be on any directory/namespace.Sussna
Things are gonna get pretty messy pretty quickly if you do this.Wrist
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.Sussna
ASP.NET MVC defaults to looking under the /View folder for views. Putting views elsewhere is gonna cause problems.Somewise
@Somewise who said anything about Views?Sussna
@max what about Controllers then? They're also searched via a pre-defined path.Somewise
@Somewise 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.Sussna
@Max Toro: surprised you got downvoted so much. After some time working on ASP.Net MVC, I am feeling a lot of pain from having all the ViewModels in one place, all the controllers in another, and all the Views in yet another. MVC is a trio of related pieces, they are coupled - they support each other. I feel like a solution can me much more organized if the Controller, ViewModels, and Views for a given section live together in the same directory. MyApp/Accounts/Controller.cs, MyApp/Accounts/Create/ViewModel.cs, MyApp/Accounts/Create/View.cshtml, etc.Banzai
@Max Toro: suprised of you downvoted too. There is php symfony framework, in 1.x version it is very similar to RoR, but in symfony 2 version developers have decided to separate all things into bundles like Max Toro suggested plus javascripts, stylesheets and images. All things are being contained in separate bundle and you can very easily copy-paste this bundle between projectsUnfinished
I can see the the benefit to wanting to combine a model, view, viewmodel and controller into one directory for each category when you work on everything in a category at once. However, the entire approach to MVC is separation of concerns. It's division of labor straight from Adam Smith. Work on one thing at a time. Set up your models and business logic. Then your controllers. Finally, present that logic with views.Gantrisin
@Gantrisin separation of concerns is not separation of classes.Sussna
I understand that. I'm just differentiating two methods in approaching an MVC development timeline. If you develop a project in order of all Models, then all Controllers, then all Views, the MVC folder structure makes sense as well. If you approach the project in terms of Model, View, and Controller at the same time, and go from category to category, then it makes more sense that your folder structure reflect that. Personally, I find it more efficient to do one type of thing at a time, like a factory worker.Gantrisin
@Gantrisin no matter how you approach development the problem is what you end up with, specially for large apps. Once in maintenance mode you don't think about all models then all controllers, you add one function at a time.Sussna
@Gantrisin I would add to Max's comment and state that even during initial development we want to work feature by feature. Feature by feature is the approach taken by e.g. SCRUM development process, where each story adds business value. After spending 2 months developing view models only, the effectively added business value is still zero, because nothing is actually usable.Ratib
Guess I'm not surprised everyone thinks you're wrong because the project templates don't ship that way... I'm going to try this method in my next MVC project. Upvote! And yeah, it's definitely a carry-over from Rails.Hogg
I feel this is a good approach for larger more complex systems where separation of sections of the system could aid maintainability. But for simple applications, how MVC structures it is more than adequate.Khano
@Khano Even for the simpler applications you can end up with a mess because you are encouraged to write a lot of classes to take advantage of model validation and editor templates. The linked blog post has a case study.Sussna
I believe that Areas added in MVC2 was an attempt to provide a solution to this problem. You create an Area folder with its own subfolders for Controllers, Views, Models, Content and Scripts. There's a bit more to do to wire it up, including routing. But it would be a helpful way to separate concerns semantically. For example, all of the website admin functions (create user, set user role) could go in an Area with special permissions. Or, a mobile website could be nested in an Area (an approach used in MVC3 before MVC4 came up with a better approach).Goodnatured
@Goodnatured You would have the same problem inside the Area. The problem is having Controllers and ViewModels separate.Sussna
I know this has been dormant for a while, but I'll second what @Goodnatured said. I've had a lot of success with Areas in managing large applications. I've used functionality segments to name my areas for managing everything.Unexpressive
Just to piggyback, this follows (precedes?) Google's own best practices for Angular (MVVM) app structure. This is the style I wanted to follow, I was just trying to find the logic that is used to relate HomeController.cs to Index.cshtml. docs.google.com/document/d/…Lakia
That's a great idea. I don't know why people think this is wrong, it's just different. Oh, because it doesn't follow the "standard template" that comes with visual studio, so it must be wrong.Feel
Just thinking about this myself a bit now and I'm realizing that the "crossover" point is when you get to your Model classes which directly represent the tables in your database. This set of Models I would want to instinctively put into a separate folder (DBModels?) and then use as bases for any ViewModels which would all be in separate folders as suggested here. But I have a question. If I just used ViewModels only (I may be being stupid here)but as a bunch of partial classes, could you form a full class and have it work with EntityFramework in this way? You always need the base class right?Zondra
We used to put models-view-controllers to different projects. Within the project you may separate them into folders by "Moduls" or any topic.Trine
According to your logic, how would you separate the solution into separate projects? i.e. if you want to have presentation layer in one project, data layer in other etc..Snowball
While I understand where you're coming from, typically the point is to make a code structure out of layers, not concerns. Your method works well for single-project solutions, but imagine a complex application with multiple projects in it. Do you create one project for a "Products" model which includes the entire code stack from View to Model to Database? You'd never be able to build a lean Class Library, because of the dependencies on UI components. I'm not saying either way is "right". Pros and cons to both.Daphne
@AndrewS this topic is about presentation layer only.Sussna
@Snowball you can have different layers like presentation and data in separate projects, and use namespaces to bring everything together.Sussna
I've been working on large ASP.Net MVC projects for several years now. I can agree wholeheartedly with @Max on this. Use Areas, and then keep your controllers, views, viewmodels, and viewmodel creators in folders within the area folder. Having one folder for all controllers and one folder for all viewmodels is a recipe for madness on a large project.Brothers
@Max I definitely agree with you. Grouping by "features" is a great way to go, if you notice is the way Angular works and there is a big tendency to move presentation to Angular, but Angular calls them "Components" instead of "Features" and inside a component folder you have typescript (logic), html (the view) and css (styles). If you need to delete one component (for example) you just remove the whole folder and you won't spend much time looking for files or code ev'rywhere. To me is a good way of organizing your presentation layer. Loved your article so thanks for sharing.Clinic
S
21

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.

Stoke answered 25/3, 2009 at 21:53 Comment(0)
Y
13

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.

Yila answered 19/3, 2009 at 22:34 Comment(0)
K
13

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.

Keverne answered 25/3, 2009 at 21:47 Comment(0)
C
6

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");
        }
}
Cerotype answered 8/7, 2010 at 6:31 Comment(0)
R
5

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

Rockery answered 23/9, 2009 at 4:7 Comment(0)
A
4

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.

Antifouling answered 19/3, 2009 at 22:21 Comment(0)
S
2

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.

Subauricular answered 21/5, 2009 at 15:49 Comment(0)
I
0

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)

Isothere answered 18/1, 2010 at 4:10 Comment(1)
Hi, can you share what is the structure of the Entry class?Pustulate
G
0

Create a view model base class which has commonly required properties like result of the operation and contextual data ,you can also put current user data and roles

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

In base controller class have a method like PopulateViewModelBase() this method will fill up the contextual data and user roles. The HasError and ErrorMessage , set these properties if there is exception while pulling data from service/db. Bind these properties on view to show error. User roles can be used to show hide section on view based on roles.

To populate view models in different get actions , it can be made consistent by having base controller with abstract method FillModel

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

In controllers

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;
}
}
Gunsmith answered 6/7, 2014 at 6:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.