ChatGPT解决这个技术问题 Extra ChatGPT

ViewModel Best Practices

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:

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

I wouldn't exactly call NerdDinner a "Best Practices" example. Your intuition serves you well. :)

D
DOK

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.


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: 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.
What about a login [POST] and login [GET] actions? with different viewmodels?
Usually, login [GET] don't call ViewModel because don't need to load any data.
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.
M
Max Toro

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.


Things are gonna get pretty messy pretty quickly if you do this.
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: 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.
@RyanJMcGowan separation of concerns is not separation of classes.
@RyanJMcGowan 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.
M
Mark

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.


J
JMS

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.


z
zihotki

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.


O
Omu

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

B
Beep beep

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


P
Preet Sangha

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.


M
Marc Climent

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.


C
C.T.

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)


Hi, can you share what is the structure of the Entry class?
A
Ajay Kelkar

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