MVC Multiple Models in one view using a parent model - c#

I'm trying to add a second model to my index page(contains 2 partial views as a two column page) following the "Passing Multiple Models using ViewModel" section of this page: http://www.codeproject.com/Articles/687061/Using-Multiple-Models-in-a-View-in-ASP-NET-MVC-M
Ive created a model to hold the other 2 called MasterModel which is where i wish to store my other models
public class MasterModel
{
public UserInfo UserInfo { get; set; }
public LogDataServerDBEntities LogDataServerDBEntities { get; set; }
}
In my Index, _NewRequest _ExistingRequest pages i changed the model from
#model IEnumerable<TMTMonitorandCompare.Models.UserInfo>
to
#model IEnumerable<TMTMonitorandCompare.Models.MasterModel>
and changed my data display to be "Model.UserInfo"
#if (Model.UserInfo != null)
{
foreach (var item in Model.UserInfo)
{
<tr>
<td>
<input type="checkbox" class="checks">
</td>
<td class="modal2row" data-toggle="modal" data-id="1" data-target="#basicModal3">
#Html.DisplayFor(modelItem => item.CreationDateTime)
</td>
<td class="modal2row" data-toggle="modal" data-id="1" data-target="#basicModal3">
#Html.DisplayFor(modelItem => item.AppModeId)
</td>
</tr>
}
}
ControllerMethod:
[HttpGet]
public ActionResult Index(string filtername)
{
var filterresults = from m in db.UserInfoes
select m;
filterresults = filterresults.Where(x => x.UserCode.ToString().Contains(filtername)).OrderBy(x => x.UserCode);
CheckDownloaded();
PopulateViewbag();
return View(filterresults);
}
Only now i get the error :
Error 1 'System.Collections.Generic.IEnumerable' does not contain a definition for 'UserInfo' and no extension method 'UserInfo' accepting a first argument of type 'System.Collections.Generic.IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Can anyone Explain to me where /with what I am going wrong ??

UserInfo is an object in MasterModel (not a collection). I suspect what you want is
public class MasterModel
{
public List<UserInfo> UserInfo { get; set; }
....
}
and in the main view
#model TMTMonitorandCompare.Models.MasterModel
then you can use
foreach (var item in Model.UserInfo)
{
....
Edit
Based on additional information from OP, the action method needs to be changed to match the model
[HttpGet]
public ActionResult Index(string filtername)
{
var filterresults = from m in db.UserInfoes select m;
filterresults = filterresults.Where(x => x.UserCode.ToString().Contains(filtername)).OrderBy(x => x.UserCode);
....
MasterModel model = new MasterModel();
model.UserInfo = filterresults;
// set other properties of model as required
return View(model);
}

Related

HttpPost returns null Model in ASP.NET MVC

Advance warning, I am extremely new to ASP.NET.
I'm working on a project which will display rows of data from a db table. When a user clicks the "Ignore" button next to a row, it should update the corresponding "Ignore" column on that row with true in the database.
The view itself works fine, it displays all the data as expected. But when "Ignore" is clicked, and it calls the Ignore() method on the controller, the model is which is passed to the controller is null.
My model, generated by entity framework (with extraneous properties removed):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IgnoreDailyItems.Models
{
[Table("DataChecks.tbl.DailyItems")]
public partial class DataChecksTblDailyItems
{
[Column("entryId")]
public int EntryId { get; set; }
[Column("ignore")]
public bool? Ignore { get; set; }
}
}
The view:
#model IEnumerable<IgnoreDailyItems.Models.DataChecksTblDailyItems>
#{
ViewBag.Title = "Placeholder";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.EntryId)
</th>
</tr>
#{ var item = Model.ToList(); }
#for(int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item[i].EntryId)
</td>
<td>
#using (Html.BeginForm("Ignore", "Home", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item[i].EntryId)
<button type="submit" class="btn btn-danger">Ignore</button>
}
</td>
</tr>
}
</table>
And the Ignore() method on the controller:
[HttpPost]
public ActionResult Ignore(DataChecksTblDailyItems modelData)
{
using (var context = new IgnoreDailyItemsContext())
{
var query = context.DataChecksTblDailyItems
.Where(b => b.EntryId.Equals(modelData.EntryId));
foreach (var q in query)
{
q.Ignore = true;
}
context.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
You're generating the form in wrong way.
#Html.HiddenFor(modelItem => item[i].EntryId)
It will generate an input hidden with item[0].EntryId, item[1].EntryId... as name/id for each row in the table, for that reason the post model definition does not match.
To solve it, set the input hidden name manually:
#Html.Hidden("EntryId", item[i].EntryId)
You need to pass IEnumerable<IEnumerable> as a parameter.
public ActionResult Ignore(IEnumerable<DataChecksTblDailyItems> modelData)
{

Showing multiple tables with partial views. Asp.Net Mvc

I want to use 3 partial views to display my search result(Client name) in 3 different tables. I do not want to show any info from the tables before the search has been completed. As i have understood you can only use one model per view, unless you make some workarounds.
I have tried to make a view model so i can reference the multiple tables from both the index and the partial views. But can't figure it out. Since the methods for the 3 different tables will be the pretty much same i am only going to post the code for the client table. Any help or pointers would be much appreciated
ViewModel:
public class ViewModel
{
public List<Clients> allClients { get; set; }
public List<OrderLines> allOrders { get; set; }
public List<ViewNewOrderSum> allViewNewOrderSum { get; set; }
}
HomeController:
using testForAutofill.Models;
public class HomeController : Controller
{
test_Db_Context db = new test_Db_Context();
// GET: Home
public ActionResult Index()
{
ViewModel vm = new ViewModel();
vm.allClients = GetClients();
return View();
}
private List<Clients> GetClients()
{
List<Clients> clientList = new List<Clients>();
clientList = db.Clients.ToList();
return clientList;
}
[HttpPost]
public ActionResult Index(string searchTerm)
{
Scaleit_Db_Context db = new Scaleit_Db_Context();
List<Clients> orderSums;
if (string.IsNullOrEmpty(searchTerm))//Fix this!
{
orderSums = db.Clients.ToList();
}
else
{
orderSums = db.Clients.Where(x => x.Name.Equals(searchTerm)).ToList();
}
return View(orderSums);
}
IndexView:
#using testForAutofill.Models;
#model testForAutofill.Models.ViewModel
#if (Model.allClients != null && Model.allClients.Count() > 0)
{
#using (Html.BeginForm())
{
<b>Kundenavn:</b>
#Html.TextBox("searchTerm", null, new { id = "txtSearch" })
<input type="submit" value="🔍 Search" class="btn btn-primary" id="btn-search" />
#using (Html.BeginForm())
{
<div class="card-container">
<div class="card border-primary mb-3 card-client" style="max-width: 40rem;">
<div class="card-header">Kunde</div>
<div class="card-body">
<table class="table table-hover">
#foreach (Clients clients in Model.allClients)
{
#Html.Partial("_Client", clients)
}
</table>
</div>
</div>
</div>
}
_Client:
#model testForAutofill.Models.ViewModel
//Tried both DisplayFor and not.
<tr>
<th>Kunde:</th>
<td>#Html.Model.allClients.Name</td>// This is where the error gets
// thrown.
</tr>
<tr>
<th>Org.nr:</th>
<td>#Html.DisplayFor(modelItem => clients.OrgNr)</td>
</tr>
<tr>
<th>Adresse:</th>
<td>#Html.DisplayFor(modelItem => clients.Address1)</td>
#if (clients.Address2 != null)
{
<td>#Html.DisplayFor(modelItem => clients.PostNr)</td>
}
</tr>
The program/webpage doesnt run, and i get the error message:
" CS1061: 'HtmlHelper' does not contain a definition for
'Model' and no extension method 'Model' accepting a first argument of
type 'HtmlHelper' could be found (are you missing a using
directive or an assembly reference?)".
At the commented line in the Partial view.
TRY:
Set object on viewData
inside controller:
ActionResult SomeView(){
ViewData["object"] = theObj;
return View();
}
inside cshtml:
#using objectNamespace
#(((objectType)ViewData["object"]).name)

IEnumerable<Model> returning null

I am having difficulty passing an IEnumerable as a model. The data is populating a form on one page - and doing so correctly. Upon submission the model returns as null.
I've seen various posts on this and they mostly reference naming-conventions so I have attempted different methods of naming the parameters to try to avoid any confusion in the model binding.
I have also tried various models and helpers to try and pass the data and all have the same result.
Current implementation:
Models:
public class UserProfileListModel
{
public IEnumerable<UserProfileViewModel> UserProfileViewModels { get; set; }
}
public class UserProfileViewModel
{
public UserProfile UserProfile { get; set; }
public Role UserRole { get; set; }
public Team UserTeam { get; set; }
public Scope UserScope { get; set; }
}
View:
#model Project.WebUI.Models.UserPRofileListModel
SNIP
<fieldset>
<legend>Administrate Users:</legend>
<table class="adminTbl">
<thead>
<tr>
<th>UserName:</th>
<th>Role:</th>
<th>Team:</th>
<th>Scope:</th>
<th>Update:</th>
<th>Delete:</th>
</tr>
</thead>
<tbody>
#{foreach (var user in Model.UserProfileViewModels)
{
<tr>
<td>
<p>#user.UserProfile.UserName
#{if (!user.UserProfile.Membership.IsConfirmed)
{
using (Html.BeginForm("Confirm", "Account", FormMethod.Post, null)){
#Html.AntiForgeryToken()
#Html.Hidden("Token", user.UserProfile.Membership.ConfirmationToken)
#Html.Hidden("Name", user.UserProfile.UserName)
}
<input type="submit" value="Confirm" />}
}
</p>
</td>
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(u => user.UserProfile)
if (user.UserProfile.UserName != User.Identity.Name && user.UserProfile.Membership.IsConfirmed)
{
<td>
#Html.DropDownListFor(u => user.UserRole, Project.WebUI.Controllers.AccountController.RoleList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserTeam, Project.WebUI.Controllers.AccountController.TeamList, new { #class = "formdrop" })
</td>
<td>
#Html.DropDownListFor(u => user.UserScope, Project.WebUI.Controllers.AccountController.ScopeList, new { #class = "formdrop" })
</td>
<td>
<input type="submit" value="Save Changes" onclick="return confirm('Are you sure you wish to update this user? ')" />
</td>
}
else
{
/*If user is self or not yet confirmed these are here to buffer the delete button into the last cell*/
<td></td>
<td></td>
<td></td>
<td></td>
}
}
}
<td>
#Html.ActionLink("Delete", "Delete", new { user.UserProfile.UserId }, new
{
onclick = "return confirm('Warning: Action cannot be undone. Are you sure you wish to permanently delete this entry?')"
})
</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
Controller:
Populate View:
public ActionResult AdministrateUsers()
{
populateLists();
var query = repository.UserProfiles.OrderBy(e => e.UserName);
List<UserProfileViewModel> list = new List<UserProfileViewModel>();
foreach(UserProfile up in query)
{
UserProfileViewModel vm = new UserProfileViewModel() { UserProfile = up };
list.Add(vm);
}
UserProfileListModel models = new UserProfileListModel()
{
UserProfileViewModels = list.OrderBy(up => up.UserProfile.UserName)
};
return View(models);
}
Accept Post:
public ActionResult SaveUserChanges(UserProfileListModel model)
{
foreach (UserProfileViewModel upvm in model.UserProfileViewModels)
{
UserProfile up = new UserProfile()
{
UserId = upvm.UserProfile.UserId,
UserEmail = upvm.UserProfile.UserName,
UserName = upvm.UserProfile.UserName
};
if (ModelState.IsValid)
{
repository.SaveUserProfile(up);
}
else
{
return View(model);
}
}
return RedirectToAction("Index", "Admin");
}
The code does still need a lot of work but I can't get past getting the model back to the controller on post. I have also tried returning the UserProfileViewModel instead of the entire list.
Can anyone tell what I am doing wrong?
Thanks!
You have a lot of invalid html including form elements as child elements of tr elements and duplicate id attributes. If you want to post back UserProfileListModel then you need a single form element and use an EditorTemplate or a for loop (not foreach) to render the controls so they are correctly named with indexers.
You are also trying to bind your dropdown lists to complex objects (for example UserProfile, Role etc.). <select> elements (and all form controls) only post back key/value pairs so you need to bind to a value type (for example UserProfile.UserId).
Your SaveUserChanges() post method is also trying access properties of UserProfile but you don't even have controls for properties of UserProfile in the form that post back to this method (for example UserId = upvm.UserProfile.UserId, UserEmail = upvm.UserProfile.UserName, ...) so they will always be null.
You probalby need to bind properties in POST method like here:
public ActionResult Create([Bind(Include = "Id,Subject,Text,IsImportant")] Announcment announcment) {... }
So it will be:
public ActionResult SaveUserChanges([Bind(Include = "UserProfile,Role,UserTeam,UserScope")]UserProfileListModel model)
Have you specified your action method is for HTTP Post? And change your action method to accept UserProfileViewModels instead.
[HttpPost]
public ActionResult SaveUserChanges(UserProfileViewModels model)
{
You are also only posting back one model: UserProfileViewModels.
You have your form in your foreach loop, so each UserProfileViewModels has its own form. If you want to change it to post back your UserProfileListModel, move
#{using (Html.BeginForm("SaveUserChanges", "Account", FormMethod.Post, null))
outside of your foreach.

Pass entity, from view to controller

In my view this is what I have
#foreach (var match in Model.CommonMatches)
{
<tr>
<td>#match.StartDateTime</td>
<td>#match.EndDateTime</td>
<td>#match.AvailableAttendees.Count()</td>
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
</tr>
}
Model.CommonMatches is of type List<Window>
public class Window
{
public DateTime StartDateTime { get; set; }
public DateTime EndDateTime { get; set; }
public IEnumerable<DataModels.Attendee> AvailableAttendees { get; set; }
}
This is how the value is being passed from controller
[HttpGet]
public ActionResult ViewStatus(Guid appointmentId)
{
var status = new ViewStatus
{
AttendeesWhoResponded = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true).ToList(),
NotAttending = _appointmentRepository.GetAppointmentDetails(appointmentId).Attendees.Where(a=>a.HasResponded == true && a.Responses == null).ToList(),
CommonMatches = _appointmentRepository.FindCommonMatches(appointmentId)
};
return View(status);
}
ViewStatus class
public class ViewStatus
{
public ViewStatus()
{
AttendeesWhoResponded = new List<DataModels.Attendee>();
NotAttending = new List<DataModels.Attendee>();
}
public List<DataModels.Attendee> AttendeesWhoResponded { get; set; }
public List<DataModels.Attendee> NotAttending { get; set; }
public IEnumerable<Window> CommonMatches { get; set; }
}
The action in controller that ActionLink of view is calling is:
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
When I inspect the value of commonMatch in controller's action. I'm getting the StartDateTime and EndDateTime but i'm not getting all the value of AvailableAttendees. It is currently shown as null.
AvailableAttendees that I'm expecting is of type IEnumerable<Attendee>. Is is not possible to pass the object the way I'm passing?
What should I do, to also get all the values of AvailableAttendees in controller along with dates?
Edit 1:
<table class ="table-hover table-striped">
<thead>
<tr>
<td>Start time</td>
<td>End time</td>
<td>Number of Attendees</td>
</tr>
</thead>
#for (var count = 0; count < Model.CommonMatches.Count();count++ )
{
using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post))
{
<tr>
<td>#Model.CommonMatches[count].StartDateTime</td>
<td>#Model.CommonMatches[count].EndDateTime</td>
<td>#Model.CommonMatches[count].AvailableAttendees.Count()</td>
#*<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new { commonMatch = #match })</td>*#
#for(var j=0;j<Model.CommonMatches[count].AvailableAttendees.Count();j++)
{
<td>#Model.CommonMatches[count].AvailableAttendees[j].FirstName</td>//to check if the value is null or not, just a test
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].FirstName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].LastName)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].Email)</td>
<td>#Html.HiddenFor(m=>Model.CommonMatches[count].AvailableAttendees[j].AttendeeId)</td>
}
<td><input type="submit" value="Accept"/></td>
</tr>
}
}
</table>
You need to post your model back, this would involve changing your controller method to this:
Controller
[HttpPost]
public ActionResult AcceptAppointment(List<Window> model)
{
return Content("ac");
}
View
You view would need a form and a submit button rather than an ActionLink. I have take the table formatting out to simplify the below.
Indexing your collections with a for loop so the model binder knows how to handle them, this is actually two loops as it is a collection within a collection. The hidden values have to be rendered too in order to be posted back (please forgive any typos).
#for(var i = 0; i < Model.CommonMatches.Count; i ++)
{
<div>
#using (Html.BeginForm("AcceptAppointment", "Appointment", FormMethod.Post)
{
#Html.HiddenFor(m => Model.CommonMatches[i].StartDateTime)
#Html.HiddenFor(m => Model.CommonMatches[i].EndDateTime)
#Model.CommonMatches[i].StartDateTime <br/>
#Model.CommonMatches[i].EndDateTime <br/>
#for(var j = 0; Model.CommonMatches[i].AvailableAttendees.Count; j++)
{
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop1)<br/>
#Html.HiddenFor(m => Model.CommonMatches[i].AvailableAttendees[j].Prop2)<br/>
}
<input type="submit" value="Accept" />
</div>
}
}
There are plenty of things you need to taken care
<td>#Html.ActionLink("Accept", "AcceptAppointment", "Appointment", new {commonMatch = #match })</td>
Calls
[HttpGet]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}
Here you are navigating using a link <a href>. Basically you are issuing a get request. In get request, you can pass the data to server only via Query String. But your case, preparing a query string dynamically before navigating to url is bit more complex. But you can do it with a JavaScript like onclick=prepareHref(this);
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=prepareHref(this)})
Then in Javascript
function prepareHref(obj)
{
var qsData="?StartDateTime='2014-02-25'&EndDateTime='2014-02-25'&AvailableAttendees[0].prop1=value1, etc"; // data should be obtained from other td elements
obj.href=obj.href+qsData;
}
But this is not a suggested way of doing it.
In case, if you want to open other page and show the url, better pass the id and load data again.
Option 1:
The better way could be submit the detail in the hidden field as explained by #hutchonoid.
Option 2:
or submit the details in jQuery ajax $.post method. Either way you need to use POST
#Html.ActionLink("Accept", "AcceptAppointment", "Appointment",
new {commonMatch = #match }, new {onclick=postMyData()})
function postMyData(){
var postData={};
postData.StartDateTime='';
postData.EndDateTime='';
postData.AvailableAttendees=[];
//for each AvailableAttendees prepare object
postData.AvailableAttendees[0]= {};
postData.AvailableAttendees[0].prop1=value1;
$.post('/Appointment/AcceptAppointment/',{data:postData},function(data){
});
return false;
}
[HttpPost]
public ActionResult AcceptAppointment(Window commonMatch)
{
return Content("ac");
}

Mass edit ISingleResult

I got a stored procedure which i show in the view for edits. I made a strong type of the stored procedure. When i edit the fields and then press the save button, the parameter "cm" is always empty. And it's not showing a list but just 1 record.
The custom model:
public class CustomModel
{
public string Description { get; set; }
public System.Data.Linq.ISingleResult<GetItems_ListResult> ItemList { get; set;}
}
This part of the controller sends it to the view:
public ActionResult Details(int id)
{
var row = dataContext.Items.FirstOrDefault(x => x.ItemID == id);
var cm = new CustomModel();
cm.ItemList = dataContext.GetItem_List(row);
cm.Description = row.Description;
return View(cm);
}
This controller receives data from the view:
[HttpPost]
public ActionResult UpdateItems(CustomModel cm)
{
return RedirectToAction("Index");
}
This is the view:
#model TestWeb.Models.CustomModel
#using (Html.BeginForm("UpdateItems", "Item", FormMethod.Post))
{
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
#foreach (var p in Model.ItemList.ToList())
{
<tr>
<td>
#Html.HiddenFor(mdl => p.ItemId)
</td>
<td>#p.Name</td>
<td>
#Html.EditorFor(mdl => p.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="save" />
</p>
}
What am i doing wrong here?
Try the following:
Make a GetItems_ListResult.cshtml like this:
<tr>
<td>
#Html.HiddenFor(mdl => mdl.ItemId)
</td>
<td>#Model.Name</td>
<td>
#Html.EditorFor(mdl => mdl.Description)
</td>
</tr>
Then in your for loop do this:
#for (int i = 0; i < Model.ItemList.Count(); i++)
{
#Html.EditorFor(m => m.ItemsList[i])
}
Update: I didn't quite notice you were using an ISingleResult. You could do this instead:
//Since it'll have none or one element..
if(Model.ItemList != null && Model.ItemList.Any())
{
#Html.EditorFor(m => m.ItemList.First())
}
Have you read this blog post? http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ Steve covers editing lists in asp mvc.
Once your read that take a look at this nuget package http://nuget.org/packages/BeginCollectionItem

Categories

Resources