How to get two different models on one page with Partial Views? - c#

I have a UserManager page where an admin is able to get all the accounts that have currently been registered, delete accounts, update accounts, and register new accounts all from the same page.
I have a controller which reutrns all the users in db.users in a list.
public ActionResult UserManager()
{
if (User.IsInRole("1"))
{
var db = new ReviewLogsTestDBEntities();
return View(db.Users.ToList());
}
else
{
return RedirectToAction("Index", "Home");
}
}
That's working fine, and I have the delete working.
The problem comes when I want to get a registration form on the same page.
This what my view Looks like right now:
#model IEnumerable<ReviewLogs.User>
#{
ViewBag.Title = "UserManager";
}
<h2>UserManager</h2>
<div id="userDetails">
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName);
</th>
<th>
#Html.DisplayNameFor(model => model.Role)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Role)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Delete", "Del", new { id = item.ID}, new { onclick = "return confirm('Are you sure you wish to delete this article?')"});
</td>
</tr>
}
</table>
</div>
As you can see there is a model:
#model IEnumerable<ReviewLogs.User>
But I want my form to use its own RegisterModel which has required fields, regex statements, etc, that need to be filled. When the user presses submit they the UserManager page is reloaded and now shows the newly added user.
I thought I could create a partial view:
#model ReviewLogs.Models.RegisterModel
#{
ViewBag.Title = "Register";
ViewBag.SubHead = "Register";
}
#Html.ValidationSummary(true, "Creation of new Account has failed please check your fields.")
<p id="success">#ViewBag.SuccessMessage</p>
<br />
#using (Html.BeginForm())
{
//The Registration Page the user sees
//The userName label and textbox
<div class="inputFields">
#Html.LabelFor(model => model.userName)
#Html.TextBoxFor(model => model.userName)
#Html.ValidationMessageFor(model => model.userName)
</div>
//The accountType label and radioButton
<div class="radioButtonAccountType">
#Html.LabelFor(model => model.accountType)
#Html.RadioButtonFor(model => model.accountType, "1")<span class="adminLabel">Admin</span>
#Html.RadioButtonFor(model => model.accountType, "2")<span class="userLabel">User</span>
#Html.ValidationMessageFor(model => model.accountType)
</div>
<input class="submitButton" type="submit" value="Create User" style="margin-left:140px;margin-bottom: 20px;" />
}
And In my UserManager View I added:
#Html.Partial("Register");
But then I got this error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ReviewLogs.User]', but this dictionary requires a model item of type 'ReviewLogs.Models.RegisterModel'.
How can I get pass this so I am able to POST my registration?

Look for the #Html.Partial method overload which accepts a model, and call it via something like:
#Html.Partial("Register", Model.RegisterModel)
In order for this to work, probably the best bet would be to create a new Model that you pass into the UserManager view. This UserManagerModel will have two properties: IEnumerable<ReviewLogs.User>, used by the UserManagerView itself, and RegisterModel which you'll pass into the Partial view.

Just new up a copy of register model in your view and pass it to the partial:
#Html.Partial("Register", new Namespace.To.RegisterModel())
Just make sure to make the form for registering post to a separate action. You should not try to have this one action handle multiple different types of posts. You can pass a return URL (basically just Request.RawUrl) in a hidden field in your form, and then if this value is present, you can use it to redirect back to the same page. For example:
#Html.Hidden("returnUrl", Request.RawUrl)
Then,
[HttpPost]
public ActionResult Register(RegisterModel model, string returnUrl)
{
// do the register thing
if (!String.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Index"); // or whatever should be default
}

Related

Failing to pass data from view to the Action by Html.BeginForm()

I am very new at asp.net mvc, so the reason behind my failure might be something basic as well, but I can't seem to find it after nearly a days work now.
What I am trying to do is to get the edited Model from the Index view and pass it to a second action which does not have view and returns return RedirectToAction("Index") in the related controller. In OrdersItemsController my Action is as the following:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MarkedShipped(IEnumerable<orders_items> orderItems)
{
if (ModelState.IsValid)
{
foreach (var item in orderItems)
{
unitOfWork.GenericRepository<orders_items>().Update(item);
}
}
return RedirectToAction("Index");
}
And in the Index.cshtml which is in OrdersItems folder in the Views, what I did is as following:
#model IEnumerable<Project.DataAccess.orders_items>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("MarkedShipped", "OrdersItems", new { orderItems = Model }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.quantity)
</th>
<th>
#Html.DisplayNameFor(model => model.itemprice)
</th>
<th>
#Html.DisplayNameFor(model => model.trackingnumber)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.quantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.itemprice)
</td>
<td>
#Html.EditorFor(modelItem => item.trackingnumber)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.itemid })
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="MarkShipped" class="btn btn-default" />
</div>
</div>
}
My problem is, I am not able to get the Model from the view with orderItems parameter, I am not sure if this is the right "syntax" for what I am trying to accomplish; but what I get for orderItems when the action is called is a List of orders_items with Count = 0 not a null value.
I have also checked if there is an application level exception, but got no exception from Application_Error in Global.asax
I am stuck for a day now so If anyone can point me a direction on how to pass the Model (or "the edited data") to the action for me to update the db, I would be very grateful. Thanks.
Firstly you cannot add a model which is a collection (or a model which contains a property which is a complex object or collection) to a forms route parameters. Internally the helper calls the .ToString() method and if you inspect the html generated for your form tag you will see something like
<form action=OrdersItems/MarkedShipped?orderItems=System.Collection.Generic.......
and binding will fail since you collection cannot be bound to a string. Even if it did work, it would be rather pointless because it would just post back the original values of the model.
Next, you cannot use a foreach loop to generate form controls. Again if you inspect the html your generating you will see that the EditorFor() method is generating inputs with duplicate id attributes (invalid html) and duplicate name attributes which do not have the necessary indexers to bind to a collection when you post. You need to use either a for loop of a custom EditorTemplate for typeof orders_items
Using a for loop means that your model must implement IList<T> and the view needs to be
#model IList<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
#using (Html.BeginForm()) // only necessary to add the controller and action if they differ from the GET method
{
....
#for(int i = 0; i < Model.Count; i++)
{
#Html.DisplayFor(m => m[i].quantity)
....
#Html.EditorFor(m => m[i].trackingnumber)
}
....
}
Using an EditorTemplate, create a partial in /Views/Shared/EditorTemplates/orders_items.cshtml (note the name of the file must match the name of the class)
#model Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items
<tr>
<td>#Html.DisplayFor(m => m.quantity)</td>
....
<td>#Html.EditorFor(m => m.trackingnumber)</td>
....
</tr>
and then in the main view (you can use IEnumerable<T>)
#model IEnumerable<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
#using (Html.BeginForm())
{
<table class="table">
<thead>
....
<thead>
<tbody>
#Html.EditorFor(m => m)
</tbody>
</table>
....
}
The EditorFor() method accepts IEnumerable<T> and will generate one row for each item in you collection based on the html in the EditorTemplate
In both cases, it you inspect the html you will now see the correct name attributes necessary for you model to bind when you post
<input type="text" name="[0].trackingnumber" ... />
<input type="text" name="[1].trackingnumber" ... />
<input type="text" name="[3].trackingnumber" ... />
Try For instead of foreach here.
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.quantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.itemprice)
</td>
<td>
#Html.EditorFor(modelItem => item.trackingnumber)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.itemid })
</td>
</tr>
}
Check this :- http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
You can do the following
for (int i = 0; i < #ViewBag.Persons; i++)
{
<li>
<table>
<tr>
<td>
#Html.LabelFor(m => m.Fullname,new { #id = "FullnameLabel" + #i })
</td>
<td>
#Html.TextBoxFor(m => m.Fullname, new { #id = "Fullname" + #i })
</td>
</tr>
In this way each html element gets its own unique id and its possible to retrieve the information back from them individually. You can use for or Foreach. Just make sure that your razor iterator is functioning. You can view the element ids in the source to see if it is working.

List to Controller from View in MVC 4 missing values?

After many years of getting a lot of great advice here, I finally have hit a wall teaching myself MVC4 ASP.net.
I used this post this post to pass a List of Type Class from my controller, to my view, and back to the controller..
public ActionResult SelectProducts()
{
displayProductsList = db.Products.ToList();
displayProductsList.ForEach(delegate(Product p)
{
//get list of recievables for the product
GetReceivablesByProductId(p.ProductID).ForEach(delegate(Receivable r)
{
//Get count of items in inventory for each recievable
p.CurrentInventory += this.CountItemsByReceivableID(r.RecievableID);
});
});
return View(FilterProductInventoryList(displayProductsList));
}
And here is my view code..
#model List<CarePac2.Models.Product>
#{
ViewBag.Title = "SelectProducts";
}
<h2>SelectProducts</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table>
#*row values*#
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].Brand)</td>
<td>#Html.DisplayFor(m => m[i].ProductName)</td>
<td>#Html.DisplayFor(m => m[i].UnitType)</td>
<td>#Html.DisplayFor(m => m[i].SalePrice)</td>
<td>#Html.DisplayFor(m => m[i].CurrentInventory)</td>
<td>
#Html.EditorFor(m => m[i].OrderQuantity)
#Html.ValidationMessageFor(m => m[i].OrderQuantity)
</td>
<td></td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
#*<input type="submit" value="Cancel" />*#
</p>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
Here the view show it has values for the List that was passed from controller to view..
Visually the view displays the data correctly (apparently i can't post an image of it until i have 10 reputation to post images)
when i hit submit and return to the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SelectProducts(List<Product> selectedProducts)
{
if (ModelState.IsValid)
{
}
}
The variable selectedProducts Is NOT NULL. The list has 3 Product items in it, however, as you can see in the image below in the debugger, even though i have 3 product items, none of the values exist from when the List of Product was originally passed to the view...
for example (since i can't post images yet):
selectedProducts[0].ProductID=0
selectedProducts[0].ProductName=null
selectedProducts[1].ProductID=0
selectedProducts[1].ProductName=null
You need to use #Html.HiddenFor():
#Html.HiddenFor(m => m[i].ProductID)
#Html.HiddenFor(m => m[i].ProductName)
This will send the data back to the controller. This creates an <input type="hidden"> that will be part of the form POST.

MVC viewmodel is null on post (editing multiple rows)

I'm new to ASP.NET MVC and have been searching for a solution to this problem for hours. I'm trying to create a site where admins can go in and approve registration user requests and also assign them to a user group. I'm getting data from multiple tables so I created a viewmodel.
I finally have the GET Edit controller working to display the data, but can't figure out how the POST Edit should work. When I was debugging, I realized that the viewmodel I was trying to return had only null values.
I'm not sure if there are many things wrong with this code or just one. On postback, I need to update some values in the Access table. If you need more information from me, just let me know. Thanks!
ViewModel:
using System.Collections.Generic;
using AccessRequests.Models;
using System.ComponentModel.DataAnnotations;
namespace AccessRequests.ViewModels
{
public class UserAccessData
{
public IEnumerable<User> Users { get; set; }
public IEnumerable<Access> Accesses { get; set; }
public IEnumerable<UserGroup> UserGroups { get; set; }
}
}
Controller:
// GET: Accesses/Edit/5
public ActionResult Edit(string brand, string group)
{
var viewModel = new UserAccessData();
viewModel.Users = db.Users
.Include(i => i.Accesses)
.OrderBy(i => i.UserName);
viewModel.UserGroups = db.UserGroups
.Where(i => i.Groups.Contains(group));
if (brand != null)
{
viewModel.Accesses = db.Accesses
.Include(x => x.User)
.Where(x => x.Brand.ToUpper() == brand);
}
return View(viewModel);
}
// POST: Accesses/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Access access, UserAccessData editaccess)
{
//code here
}
View:
#model AccessRequests.ViewModels.UserAccessData
#{
ViewBag.Title = "Edit";
}
<h2>Edit Access</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Company</th>
<th>Role</th>
<th>Region</th>
<th>User Group</th>
<th>Approve</th>
<th>Deny Reason</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Accesses)
{
#Html.HiddenFor(modelItem => item.UserName)
<tr>
<td>
#Html.DisplayFor(modelItem => item.User.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Company)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Role)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Region)
</td>
<td>
#Html.DropDownListFor(m => m.UserGroups, new SelectList(Model.UserGroups, "Groups", "GroupDescription"), "Please select a User Group")
</td>
<td>
#Html.DropDownListFor(modelItem => item.Approved, new SelectList(
new List<Object>{
new { value = 0 , text = "" },
new { value = "YES" , text = "YES" },
new { value = "NO" , text = "NO"}
},"value","text", 2))
</td>
<td>
#Html.EditorFor(modelItem => item.DenyReason, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.DenyReason, "", new { #class = "text-danger" })
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
In order to post to a collection property, your field names need to be in the form of something like: Accesses[0].Approved, Accesses[1].Approved, etc. In order to achieve that, you need to use a for loop rather than foreach. You'll also need to change your property's type from IEnumerable<Access> to List<Access>.
#for (var i = 0; i < Model.Accesses.Count(); i++)
{
...
#Html.DropDownListFor(m => Model.Accesses[i].Approved, ...)
}
Also, bear in mind that only those properties which have actual HTML fields that participate in the the post will have values in your post action. Everything else will either be null or whatever default value the property may have. If you save an entity with properties that have been nulled out because they weren't posted, you will overwrite those properties in your database. You need to take care to either make sure all the necessary data comes through in the post or that you repopulate said data from the the database before attempting to save anything.

Not able to render partial view as a part of view

There are a couple of questions I referred on rendering partial views in mvc4. But my problem is that I have a division(DisplayRecords) in my view and want the partial view to open in that division instead of a different view on click of 'view' button.
But that is not happening because in the controller I am returning the partial view because if I return the view, I cannot pass the list containing data.
On returning view, it throws an error that the view cannot accept that type of parameter and the partial view will not work without the list being passed.
I tried assigning the list to a model property and pass that property value to partial view from client side but that throws a null reference exception.
Can someone suggest how I can fix this.
Here is what I have written:
My view:
#model mvcEmail.Models.EmailModel
#*<script type="text/javascript">
var command = document.getElementById("FirstName");
alert(command);
if (command == "Value")
$('#DisplayRecords').load('/Email/Index2');
</script>*#
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (#Html.BeginForm())
{
<div>
#* #Html.Label(Model.Acknowledge, new { id = "Acknowledge" })
<br />*#
#Html.TextBoxFor(model => model.FirstName, new { id = "FirstName", #class = "ClassName" })
<br />
#Html.TextBoxFor(model => model.PhoneNumber, new { id = "PhoneNumber", #class = "ClassPhoneNumber" })
<br />
<input type="submit" name="Command" value="Submit" />
<input type="submit" name="Command" value="View" />
</div>
<div id="DisplayRecords">
#if (ViewBag.query == "View")
{
#Html.Partial("Index2")
}
</div>
}
My partial view:
#model IEnumerable<mvcEmail.Models.EmailModel>
<p>
#Html.ActionLink("Create New", "Index")
</p>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
#Html.DisplayNameFor(model => model.PhoneNumber)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PhoneNumber)
</td>
#* <td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>*#
</tr>
}
</table>
My controller where I am passing list to view:
if (Command == "View")
{
ViewBag.query = "View";
//var records = new DisplayRecords();
List<EmailModel> recs = new List<EmailModel>();
cmd.CommandText = "DisplayRecords";
cmd.Connection = con;
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
model = new EmailModel();
model.PhoneNumber = reader["phone"].ToString();
model.FirstName = reader["Name"].ToString();
recs.Add(model);
}
con.Close();
con.Dispose();
return PartialView("Index2", recs);
//return View(recs);
}
Your Partial view is not getting its instance of model.You need to provide your model during calling partial view in view as -
#Html.Partial("Index2", new List<mvcEmail.Models.EmailModel>(){this.Model})
or I will recommend to write another controller action which will return your partial view with model as -
public ActionResult PartialView()
{
List<mvcEmail.Models.EmailModel> emailListModel = new List<mvcEmail.Models.EmailModel>();
return PartialView(emailListModel);
}
and then call it using -
#{Html.RenderAction("PartialView", "Controller_name",null);}
This will make your code better as with single responsibility principle.
I hope this will help you.

Listing Models from ASP pages, with Visual Studio and C#

I am creating a very basic Web application in ASP with Visual Studio, I used the default web site then created the 'Employee' Model. This Model can be stored in the Database using:
public class EmployeeDBContext : DbContext
{
public DbSet<Employee> Employees{ get; set; }
}
In the Employee namespace. When I created the Controller for this Model the default Create, Read, Update and Delete methods are created. There is also an index page created that is shown when the page is 1st loaded and this page shows every Employee currently in the database. The code for Index.cshtml looks like this:
#model IEnumerable<AnotherWebApp.Models.Employee>
#{
ViewBag.Title = "Index";
}
<h2>All Employee Options</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<p>
#Html.ActionLink("View All", "ViewAll")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Role)
</th>
<th>
<b>Options</b>
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Role)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
#Html.ActionLink("Details", "Details", new { id = item.ID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>
</tr>
}
</table>
What I am trying to do is display a basic menu on Index.cshtml and link to a ViewAll page containing the table of all Employees. The problem is it says 'Object reference not set to an instance of an object.' and the page is not displayed. I cannot see why this code works on Index.cshtml but will not work on ViewAll.cshtml, anybody have suggestions?? Here is a link to some tutorials doing this: http://www.asp.net/mvc/tutorials/mvc-5/introduction/accessing-your-models-data-from-a-controller
Thanks for any advice.
Just to clear things up this problem was from the EmployeeController.cs where the View associated with the ViewAll page was being returned. When the ViewAll function looked like this:
public ActionResult ViewAll()
{
return View();
}
The list of Employees could not be accessed so
#model IEnumerable<AnotherWebApp.Models.Employee>
was null.The correct version of this function is:
public ActionResult ViewAll()
{
return View(db.Employees.ToList());
}
Now the list of all Employees is accessible and can be easily displayed on the ViewAll page.
Hope this is of help to somebody, if anybody has furthur questions please ask!!

Categories