2

From my understanding, a view model represents only the data that I would like to display in a particular view and can consist of properties from multiple domain models. I also understand that view models act as a data-binder between my domain models and views.

With that said, I have the following model:

public class Image
{
    public int ImageID { get; set; }
    public string ImageCaption { get; set; }
    public int NumOfLikes { get; set; }
    public string ImageFilePath { get; set; }
}

And a ProfileViewModels class with the the following view models:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace Splend.Models
{
    public class ImageUploadViewModel
    {
    }

    public class EditUserProfileViewModel
    {
    }

    public class ViewUserProfileViewModel
    {
    }

    public class ViewImagesViewModel
    {
    }
}

Firstly, I assumed that this is the correct way of creating view models based on AccountViewModels generated by ASP.NET Identity. Secondly, I have not scaffolded any items from these two models. So I have no views or controllers yet.


Questions

  1. How would my view models bind data between my domain model and views?
  2. How would I map my view models to my domain models?
  3. How does Entity Framework know that my view model properties are referenced from my domain model and that I am not creating new model properties, i.e. is there a particular view model and property naming convention that I have to use to tell Entity Framework that ImageUploadViewModel is a view model?
Bonga
  • 97
  • 11
  • 1
    Tools such as [automapper](https://github.com/AutoMapper/AutoMapper) make it relatively easy to map data models to view models and vice versa. –  Oct 07 '15 at 11:25
  • @StephenMuecke I will have a look at it. However, is the way in which I created my view models correct? Is there any convention that I have to use or are view models just models that need to be mapped to other models? – Bonga Oct 07 '15 at 11:41
  • 2
    They are just dumb models (generally only properties with display and validation attributes for use in the view). And put them in a separate folder (say) ViewModels to keep them separate from your data models. They should not be in your `Splend.Models` namespace –  Oct 07 '15 at 11:44
  • I've tried to answer your questions to the best of my ability below. It's not perfect and I haven't filled in ALL of the blanks or written ALL of the view models, but I hope it helps. – Luke Oct 07 '15 at 13:09
  • @StephenMuecke If I were to add `Mapper.CreateMap();` in `Application_Start()` in my `Global.asax` file, would I need to do the same for `ApplicationUser` since I would also have`Username` as a property (assuming I'm using Automapper)? @Coulton Thanks, I'll try it out and see how it works. – Bonga Oct 07 '15 at 15:53

1 Answers1

1

I wouldn't recommend a one-size-fits-all approach to creating and managing view models cause it gets complicated quickly, especially if you come to assign validation to them.

It seems like you're trying to anticipate the view models that you need before you create your views?

Generally I would recommend having a 1:1 relationship between your view models and your views and ensure that your view models contain the least amount of fields to hold only the information that is required to display said view.

I have assumed that you have a users that have multiple Images and the following view models:

public class ProfileViewModel
{
    public string UserName { get; set; }
    public string Password { get; set; }
    public string FullName { get; set; }
    public List<ImageViewModel> UserImages { get; set; }
}

// View model to represent user images
public class ImageViewModel
{
    public int ImageID { get; set; }
    public string ImageCaption { get; set; }
    public int NumOfLikes { get; set; }
    public string ImageFilePath { get; set; }
}
  1. Here's how to map to your view model. Simply create a new instance of your view model class(es) , assign the values to them and pass them to the view:

    [HttpGet]
    public ActionResult EditUser()
    {
        //
        // LOAD INFO ABOUT THE USER (Using a service layer, but you're probably just using Entity Framework)
        //
    
        // Get the user ID?
        var userId = this.User.Identity.GetUserId();
    
        // Get the user domain models from the database?
        var userDomainModel = this.UserService.GetUserInfo(userId);
    
        // Get the user's image domain models from the database?
        var userImagesDomainModelList = this.UserImageService.GetImagesForUser(userId);
    
        //
        // THE VIEW MODEL MAPPING STARTS HERE...
        //
    
        // Create an instance of your view model and assign basic info
        var responseModel = ProfileViewModel()
        {
            public string UserName = userDomainModel.UserName;
            public string Password = userDomainModel.Password;
            public string FullName = userDomainModel.FullName;
        }
    
        // Initialise list for images
        responseModel.UserImages = new List<ImageViewModel>();
    
        // Loop though each image domain model and assign them to the main view model
        foreach (var imageDomainModel in userImagesDomainModelList)
        {
             // Initialise image view model
             var imageViewModel = new ImageViewModel()
             {
                 ImageID = imageDomainModel.ImageID,
                 ImageCaption = imageDomainModel.ImageCaption,
                 NumOfLikes = imageDomainModel.NumOfLikes,
                 ImageFilePath = imageDomainModel.ImageFilePath
             };
    
             // Add the image view model to the main view model
             responseModel.UserImages.Add(imageViewModel);
        }
    
        // Pass the view model back to the view
        return View(responseModel);
    }
    
  2. Just map the incoming values that have been posted to your domain models in the same way:

    [HttpPost]
    public ActionResult UpdateImage(UpdateImageViewModel requestResponseModel)
    {   
        // Get the user ID?
        var userId = this.User.Identity.GetUserId();
    
        // Map incoming view model values to domain model
        var imageDomainModel = new Image()
        {
            ImageID = requestResponseModel.ImageId,
            ImageCaption = requestResponseModel.ImageCaption,
            NumOfLikes = requestResponseModel.NumOfLikes,
            ImageFilePath = requestResponseModel.ImageFilePath,
        }
    
        try
        {
            // Send the domain model up to your service layer ready to be saved
            this.UserImageService.UpdateImage(userId, imageDomainModel);
    
            ViewBag.SuccessMessage = "Your image was updated";
        }
        catch (Exception)
        {
            // Critical error saving user image
        }
    
        return View(requestResponseModel);
    }
    
  3. At no point will you try to attach your View Models to your Entity Framework context. If you did, then it would reject it. It knows which models it can accept by the IDbSet<object> properties that are defined within the definition of your DbContext:

    public class YourDbContext : DbContext
    {
        public YourDbContext() : ("YourDbConnectionStringName")
        {
        }
    
        // Definitions of valid Entity Framework objects
        public virtual IDbSet<User> Users { get; set; }
        public virtual IDbSet<Image> Images { get; set; }
    }
    
    var dbContext = new YourDbContext();
    
    var imageViewModel = new ImageViewModel();
    var imageDomainModel = new Image();
    
    // Adding object defined within your db context is fine...
    dbContext.Set<Image>().Add(imageDomainModel); // OK
    dbContext.Set<Image>().Add(imageViewModel); // Not OK, will result in exception
    

I personally don't sent Domain Models up to my service layer, I use DTOs (Data Transfer Objects). But that's a whole other story...

Please see this post from me that explains how I lay out my MVC applications: https://stackoverflow.com/a/32890875/894792

Community
  • 1
  • 1
Luke
  • 22,826
  • 31
  • 110
  • 193
  • Correct, I'm under the assumption that I can scaffold my view models as I would my domain models. I'm creating a simple application that will allow users to create a profile and upload multiple images. The user is `ApplicationUser` which inherits `IdentityUser`. Thanks, I'll have a look at it. – Bonga Oct 07 '15 at 16:15
  • Yes you can structure them however you like really. Depending on the situation, I flatten them down so that I don't have nested view models and it makes a little easier on the eye. The important thing is that you map from one type of model to the other so that you're not using your domain models as your view model. – Luke Oct 08 '15 at 08:52
  • Is there anything else I can help you with with this? – Luke Oct 08 '15 at 12:32
  • Yes there is, I have defined a one-to-many relationship by adding `public virtual ICollection Image { get; set; }` and `public virtual ApplicationUser ApplicationUser { get; set; }` in the `ApplicationUser` class and `Image` class respectively. Entity Framework has created an `ApplicationUser_id` property in the `Images` database table. However, I would like to return to a view, each images `ApplicationUser`'s name and surname instead of its id. Would I have to add a name and surname property to `Image` or is there a way to derive this from `ApplicationUser_id` within my VM? – Bonga Oct 13 '15 at 07:44
  • I think this might be one for another question. If you post another question then I'll definitely take a look. It will be more clear with code. – Luke Oct 13 '15 at 07:59
  • I have posted the question. [link] (http://stackoverflow.com/questions/33103210/in-a-one-to-many-relationship-between-domain-model-a-and-b-how-would-i-return-a) – Bonga Oct 13 '15 at 12:54