11

Could somebody please provide an example of how to combine two models within one view?

Currently I have a page called RecordCard which contains:

@model IEnumerable<WebApplication1.Models.Weight>

This is provided by the following code in the AccountController:

public ActionResult RecordCard()
{
    var UserId = User.Identity.GetUserId();
    var weightModel = from m in db.Weights where m.UserId == UserId select m;
    return View(weightModel);
}

The RecordCard page also contains a form which is bound to the following class:

public class AddWeightModel
{
    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "Stone")]
    public Nullable<short> Stone { get; set; }

    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "Pound")]
    public Nullable<short> Pound { get; set; }
}

However, these are two individual models with different purposes, so how do I combine to a single model that contains an IEnumerable list and set of form elements that will ultimately post to the AccountController correctly to add a record to the database using the following code:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RecordCard(Weight Model)
{
    if (ModelState.IsValid)
    {
        using (WebApplication1Entities db = new WebApplication1Entities())
        {
            Weight weight = new Weight();
            weight.UserId = User.Identity.GetUserId();
            weight.Stone = Model.Stone;
            weight.Pound = Model.Pound;
            weight.Date = System.DateTime.Now;

            db.Weights.Add(Model);
            db.SaveChanges();
        }
    }
    return View(Model);
}

I have included the Weight class below:

public partial class Weight
{
    public int Id { get; set; }
    public string UserId { get; set; }
    public Nullable<short> Stone { get; set; }
    public Nullable<short> Pound { get; set; }
    public Nullable<System.DateTime> Date { get; set; }
}

Also here is the WebApplication1Entities class which declares the Weight table as Weights:

public partial class WebApplication1Entities : DbContext
{
    public WebApplication1Entities()
        : base("name=WebApplication1Entities")
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<Weight> Weights { get; set; }
}

Please explain what needs to be modified and how, no matter what I try to read, follow and implement, I seem to be missing something.

Any help would be much appreciated :-)

iggyweb
  • 2,373
  • 12
  • 47
  • 77

3 Answers3

14

I would say this is good example of using ViewModel here. I would suggest something like -

Create ViewModel with the composition of the two classes

public class AddWeightModel
{
    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "Stone")]
    public Nullable<short> Stone { get; set; }

    [Required]
    [DataType(DataType.Text)]
    [Display(Name = "Pound")]
    public Nullable<short> Pound { get; set; }
}
....
public partial class Weight
{
    public int Id { get; set; }
    public string UserId { get; set; }
    public Nullable<short> Stone { get; set; }
    public Nullable<short> Pound { get; set; }
    public Nullable<System.DateTime> Date { get; set; }
}
.....
public class WeightViewModel
{
    public IList<AddWeightModel> AddWeightModel { get; set; }
    public Weight Weight { get; set; }
}

Then change your view to accept the view models -

@model WeightViewModel

Finally modify your controller to cope with the change -

public ActionResult RecordCard()
    {
        var UserId = User.Identity.GetUserId();
        var weightModel = from m in db.Weights where m.UserId == UserId select m;
        var viewModel = new WeightViewModel
        {
            Weight = weightModel,
            AddWeightModel = new List<AddWeightModel>(){}
        };
        return View(viewModel);
    }

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RecordCard(WeightViewModel viewModel)
{
    Weight Model = viewModel.Weight;
    if (ModelState.IsValid)
    {
        using (WebApplication1Entities db = new WebApplication1Entities())
        {
            Weight weight = new Weight();
            weight.UserId = User.Identity.GetUserId();
            weight.Stone = Model.Stone;
            weight.Pound = Model.Pound;
            weight.Date = System.DateTime.Now;

            db.Weights.Add(Model);
            db.SaveChanges();
        }
    }
    return RedirectToAction("RecordCard");
}
brainless coder
  • 6,310
  • 1
  • 20
  • 36
  • Please see my amended post as I already have a Weight class which is used for the IEnumerable list. – iggyweb May 08 '14 at 08:48
  • It is not a new class, they are all classes you mentioned, so edit them as per your need, I showed you how you can pass multiple models and bind them. So they are just examples. I have edited the answer though. – brainless coder May 08 '14 at 08:51
  • Ok, think the penny is starting to drop, please excuse my stupidity, I'm knew to this. I have red underlines with the line `Weight = weightModel,` and is List to declare form fields and IEnumerable for list of records? – iggyweb May 08 '14 at 09:21
  • Also am I correct in using `@Html.TextBoxFor(AddWeightModel => AddWeightModel.Weight.Pound, new { @placeholder = "Pound *", @type = "text" })` for the form elements, not getting any red undlerlines for that, phew. – iggyweb May 08 '14 at 09:24
  • I figured out why the red line was for the line `Weight = weightModel` its because it was expecting and IEnumerable so I've amended the WeightViewModels as `public IList AddWeightModel { get; set; } public IEnumerable ListWeight { get; set; } public Weight Weight { get; set; }` There are no more red lines in my code and the project has compiled but I am now getting the error `The model item passed into the dictionary is of type 'WebApplication1.Models.Weight', but this dictionary requires a model item of type 'WebApplication1.Models.WeightViewModel`. – iggyweb May 08 '14 at 09:40
  • Because you are return `View(model)` where model is of type `Weight`, Check my code, I am returning `WeightViewModel`, so change your code to return `WeightVeiwModel` – brainless coder May 08 '14 at 10:49
  • Sorry, but my code is verbatim to yours, could you please specify the exact line I need to change, because mine looks the same as yours. – iggyweb May 08 '14 at 10:57
  • Check my function - `public ActionResult RecordCard()` and then check your function `public ActionResult RecordCard()` .. specially the model I am creating and the model you are creating and returning `View(...)`.. my one is of type `WeightViewModel` yours is of type `Weight`. I am assigning the `Weight` object inside my viewModel and then returning the viewmodel - `var viewModel = new WeightViewModel { Weight = weightModel, AddWeightModel = new List(){} }; return View(viewModel);` – brainless coder May 08 '14 at 11:08
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52290/discussion-between-iggyweb-and-mahmud) – iggyweb May 08 '14 at 11:12
9

I've tackled this before, can came to an elegant solution.

First, you'd want to setup your main classes to send, as well as a 'holder' class to store them to eventually send to a view.
As you probably found out, this is because a view can't have multiple models sent to it.

public class WebsiteTheme
{
    public string Color { get;set; }
    public string Title { get;set; }

    public WebsiteTheme() {
         Color = "blue";
         Title = "test website";
    }
}

public class User
{
    public string Name { get;set; }
    public string Gender { get;set; }

    public User() {
         Name = "Anonymous";
         Gender = "Unspecified";
    }
}

public class ToPage
{
    public WebsiteTheme WebsiteTheme{ get; set; }
    public User User { get; set; }

    public ToPage() {
         websiteTheme = new WebsiteTheme();
         user = new User();
    }
}

This will allow you to send any amount of classes to your page.

Then, in your controller, you'd want to populate those classes. Make sure to initialise them all first, then set the populated classes to your holder class.

WebsiteTheme websiteTheme = new WebsiteTheme();
websiteTheme.Color = "orange";

User user = new User();
user.Name = "Darren";

ToPage toPage = new ToPage();
toPage.User = user;
toPage.WebsiteTheme = websiteTheme;

return View(toPage);

In your view, you'd call them in any way you want to. But make sure to use HolderModel.SpecifiedModel in every case.

@model WebApplication1.Models.ToPage

@Html.DisplayFor(model => model.User.Name)
TheGeekZn
  • 3,696
  • 10
  • 55
  • 91
  • 1
    nice cluterless answer explaining it very concisely. The accepted answer was perfect for the question but your answer will ulitmately help more people. – rory Oct 25 '15 at 19:56
1

I did a compound model like this:

public class CompoundModel 
{
    public SearchModel SearchModel { get; set; }
    public QueryResultRow ResultModel { get; set; }
}
public class QueryResultRow
{
    [DisplayName("Id")]
    public long id { get; set; }
    [DisplayName("Importdatum")]
    public System.DateTime importdate { get; set; }
    [DisplayName("Mandant")]
    public int indexBMClient { get; set; }
}

public class SearchModel 
{
    [Required]
    [DataType(DataType.Date)]
    [Display(Name = "Zeitraum von")]
    public DateTime dateFrom { get; set; }
    [Display(Name = "Terminal-ID")]
    public string tid { get; set; }
    [Display(Name = "Belegnummer")]
    public string receiptnumber { get; set; }
}

In the view header:

@model MyProject_aspmvc.Models.CompoundModel

And get data access from the SearchModel, for example:

model => model.SearchModel.tid

and data access from the ResultModel, for example:

model => model.ResultModel.importdate 
Uwe Köhler
  • 123
  • 1
  • 7
  • why would you use ReceiptCompoundModel in your view header and a different name in the Model? – Ken-F Jun 22 '18 at 14:07