Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5 Multiple Models in a Single View

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

like image 822
iggyweb Avatar asked May 08 '14 08:05

iggyweb


3 Answers

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");
}
like image 161
brainless coder Avatar answered Oct 10 '22 16:10

brainless coder


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)
like image 32
TheGeekZn Avatar answered Oct 10 '22 15:10

TheGeekZn


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 
like image 1
Uwe Köhler Avatar answered Oct 10 '22 15:10

Uwe Köhler