MVC Asp.net How to pass a list of complex objects from the view to the controller using Actionlink? - model-view-controller

I have a model which contains a list of another model.
Let's say I have a MovieModel:
public class MovieModel
{
public int MovieId { get; set; }
public string Title { get; set; }
public string Director { get; set; }
}
Then I have the RentalModel:
public class RentalModel
{
public int RentalId { get; set; }
public string CustomerId { get; set; }
public List<MovieModel> Movies { get; set; }
}
Then I have a place where all the rentals are displayed, which by clicking on the rental, its details will be displayed, from the "ShowRentals.aspx" to "ShowRentalDetails.aspx"
<% using (Html.BeginForm()) { %>
<% foreach(var rent in Model) { %>
<div class="editor-label">
<div class="editor-field">
<%: rent.RentalId %>
<%: Html.ActionLink("Details", "ShowRentalDetails",
new {rentalId = rent.RentalId,
customerId = rent.CustomerId,
movies = rent.Movies,
})%>
When I debug, I see that the Movies list is always null. This is because only primitive parameters are passed successfully, such as the Ids. I was never able to pass complex types. I really need this list to be passed on to the controller. Is it maybe because the actionlink is not capable? What other work-arounds can I do? I've been stuck on this for a while.
Nevermind the bare code here, this is just to show you what I'm doing with the list. Please help.
(follow up)
In the Controller, here's the two actions, ShowRentals and ShowRentalDetails:
public ActionResult ShowRentals()
{
MembershipUser user = Membership.GetUser(User.Identity.Name, true);
Guid guid = (Guid)user.ProviderUserKey;
Entities dataContext = new Entities();
Member member = dataContext.Members.Where(m => m.UserID == guid).First();
IEnumerable<RentalModel> toReturn = from r in member.Rentals
select new RentalModel
{
RentalId = m.RentalID,
CustomerId = m.CustomerID,
};
return View(toReturn);
}
[Authorize]
public ActionResult ShowRentalDetails(RentalModel model, List<MovieModel> movies)
{
return View("ShowRentalDetails", model);
}
I can't set it in ShowRentals because the array of movies in the database is of Movie type and not MovieModel, so the two lists are not compatible. It is null in the model when passed from ShowRentals view and the model is reconstructed by mvc, and it also doesn't work when explicitly passed from the actionlink as a parameter. help!

I believe Html.ActionLink performs a GET and you can't pass complex data types using a GET.
If you could refetch the movie list in your ShowRentDetails controller by using the rental id I think that would be best.
Otherwise, you could look up EditorFor templates. If you make an editorfor template for MovieModel and post a RentalModel to ShowRentDetails then you could get the MovieModel list that way.
See http://weblogs.asp.net/shijuvarghese/archive/2010/03/06/persisting-model-state-in-asp-net-mvc-using-html-serialize.aspx for another way.
On a side note, theres no need to make
List<MovieModel> movies
a second parameter in ShowRentDetails when it's already included in the model
Source: ASP.NET MVC - Trouble passing model in Html.ActionLink routeValues

It is clear you cant pass complex view models through action link. There is a possibility to pass simple objects which does not have any complex properties. There is another way you can do as multiple submit buttons and do a post to controller. Through the submit you have possibilities to post complex view models

Related

multiple models to partial view

I am teaching myself asp .net mvc3.
I have a partial view which uses various models and lookup type db. I want to keep it strongly typed and use it in multiple places but I am not sure how to implement it.
The example below should explain it better. This question might get a bit long and I appreciate your patience.
The partial view basically gives a small description of the property. A snippet of the ‘_PropertyExcerptPartial’ is below:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>#Html.DisplayFor(model => model.Property.NoOfBedrooms) bedroom #Html.DisplayFor(model => model.FurnitureType.FurnitureTypeDescription) flat to Rent </h3>
…
</div>
I want to keep this partial view strongly typed and use it in multiple places. The model that it is strongly typed to is as follows:
public class PropertyExcerptViewModel
{
public Property Property { get; set; }
public FurnitureType FurnitureType { get; set; }
}
The two 2 database that this model looks up is as follows:
public class Property
{
public int PropertyId {get; set; }
...
public int NoOfBedrooms {get; set;}
public int FurnishedType { get; set; }
...
}
public class FurnishedType
{
public int FurnishedTypeId { get; set; }
public string FurnishedTypeDescription { get; set; }
}
The furnished type database is basically just a lookup table with the following data:
1 - Furnished
2 - Not Furnished
3 - Part Furnished
4 - Negotiable
I have many such lookups in that I only store an int value in the property database which can be used to look up the description. These databases are not linked to property database and the value of furniture type is read via a function GetFurnitureType(id). I pass stored int value of Property.FurnitureType as the id.
However, I encounter a problem when I try to use this partial view as I am not sure how to pass these multiple models from a view to partial view.
Say I am trying to create an ‘added property’ page. This page basically list the properties added by the logged in user. To facilitate this, I have created another function called GetAddedProperties(userId) that return the properties added by a particular user. In the ‘added property’ view, I can call a foreach function to loop through all the properties returned by GetAddedProperties and display the _PropertyExcerptPartial. Something like this:
<div>
#foreach (var item in //Not sure what to pass here)
{
#Html.Partial("_PropertyExcerptPartial",item)
}
</div>
However, I can’t use the partial view to display information as it will display the int value of furniture type stored in the property database and I am not sure how to get the corresponding FurnitureTypeDescription and pass it to the partial view from the ‘added property’ page.
Any help would be appreciated. Thanks a lot!
You should start by designing a real view model, not some class that you suffix its name with ViewModel and stuff your domain models inside. That's not a view model.
So think of what information you need to work with in your view and design your real view model:
public class PropertyExcerptViewModel
{
public int NoOfBedrooms { get; set; }
public string FurnishedTypeDescription { get; set; }
}
and then adapt your partial to work with this view model:
#model Test.ViewModels.PropertyExcerptViewModel
<div>
<h3>
#Html.DisplayFor(model => model.NoOfBedrooms)
bedroom
#Html.DisplayFor(model => model.FurnitureTypeDescription)
flat to Rent
</h3>
...
</div>
OK, now that we have a real view model let's see how we could populate it. So basically the main view could be strongly typed to a collection of those view models:
#model IEnumerable<Test.ViewModels.PropertyExcerptViewModel>
<div>
#foreach (var item in Model)
{
#Html.Partial("_PropertyExcerptPartial", item)
}
</div>
and the last bit of course is the main controller that will do all the db querying and building of the view model that will be passed to the main view:
[Authorize]
public ActionResult SomeAction()
{
// get the currently logged in username
string user = User.Identity.Name;
// query the database in order to fetch the corresponding domain entities
IEnumerable<Property> properties = GetAddedProperties(user);
// Now let's build the view model:
IEnumerable<PropertyExcerptViewModel> vm = properties.Select(x => new PropertyExcerptViewModel
{
NoOfBedrooms = x.NoOfBedrooms,
FurnishedTypeDescription = GetFurnitureType(x.FurnishedType).FurnishedTypeDescription
});
// and finally pass the view model to the view
return View(vm);
}
Be careful with the lazy nature of EF if that is the ORM that you are using in order not to fall into the SELECT N + 1 trap.

Binding DropdownList and maintain after postback

I am using MVC3. I'm binding the dropdown with the Data coming from a service. But after the page posts back and a filter applies to list, the dropdown shows the filter record value in the grid because I always bind the list coming from the service.
However, I want the dropdown to always show all the Records in the database.
I don't understand your question that clearly. But it seems that it is a dropdown that you have on your view? I also have no idea what you are trying to bind so I created my own, but have a look at my code and modify it to fit in with your scenario.
In your view:
#model YourProject.ViewModels.YourViewModel
On the view there is a list of banks in a dropdown list.
Your banks dropdown:
<td><b>Bank:</b></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
Your view model that will be returned to the view:
public class YourViewModel
{
// Partial class
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
Your create action method:
public ActionResult Create()
{
YourViewModel viewModel = new YourViewModel
{
// Get all the banks from the database
Banks = bankService.FindAll().Where(x => x.IsActive)
}
// Return the view model to the view
// Always use a view model for your data
return View(viewModel);
}
[HttpPost]
public ActionResult Create(YourViewModel viewModel)
{
if (!ModelState.IsValid)
{
// If there is an error, rebind the dropdown.
// The item that was selected will still be there.
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
return View(viewModel);
}
// If you browse the values of viewModel you will see that BankId will have the
// value (unique identifier of bank) already set. Now that you have this value
// you can do with it whatever you like.
}
Your bank class:
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
This is simple as it gets. I hope this helps :)
PS: Please remember with future posts, always give as much detail as possible so that we can help you better. Also don't forget to display code samples so that we can see what you have already done. The more details that we can have the better.
When you post the model back to the create[Http Post] action is it not possible to have the list of dropdown list values for the banks binded back to the model. I see that if the model is invalid, you call the code
viewModel.Banks = bankService.FindAll().Where(x => x.IsActive);
to get a list of all the banks again which I assume you need to hit the database again.
Thanks

IEnumerable property with MVC3 EditorTemplate

Similar to this post IEnumerable model property in an ASP.NET MVC 3 Editor Template, I have
Model
public class Student
{
public int StudentId { get; set; }
public string StudentName{ get; set; }
//FYI..Its virtual because of EF relationship
public virtual ICollection<Class> Classes{ get; set; }
}
public class Class
{
public int ClassId { get; set; }
public string ClassName{ get; set; }
}
View - EditStudent
#model Student
#Html.TextBoxFor(m => m.StudentName)
//I get the error for following..see below
#Html.EditorFor(m => m.Classes);
Student/EditorTemplates/Class
#model Class
<div>
#*checkbox here*#
#Html.LabelFor(x => x.ClassName)
</div>
Controller
public ActionResult EditStudent(int id)
{
ViewBag.Classes = repository.GetClasses();
Student student = repository.GetStudent(id);
return View("EditStudent", student);
}
Error in View on statement #Html.EditorFor(m => m.Classes); is..
The model item passes into the dictionary is of type
'System.Collections.Generic.HashSet`1[Class]', but this dictionary
required a model item of type 'Class'.
Basically, what I am trying to achieve is to display the list of all classes available with a checkbox next to it ( I have not reached to that part of code yet). Then check all classes to a student is enrolled and allow to change the selections.
How do I display the list of checkboxes with the given Model.
Should I bind my EditorTemplate with ViewBag.Classes (How?) or ?
I need to get selected checkbox values in Post ActionMethod as well.
I read some posts those suggest to create CheckBoxListHelper, but it should be possible to do with EditorTemplate as I need to display a simple list.
Please suggest. Thanks.
Okay, I figured it out. Thanks to very precise post here
How to provide an EditorTemplate for IEnumerable<MyModel>?
First, I renamed the EditorTemplate to StudentClass - not sure if this has anything to do with binding or not, but I did.
Second, modified EditorTemplate to bind with IEnumerable
#model IEnumerable<Class>
var checked = "";
#foreach (Class class in ViewBag.Classes)
{
if (Model != null)
{
Class class = Model.FirstOrDefault(c => c.ClassId.Equals(class.ClassId));
if (class != null)
{
checked = "checked=checked";
}
}
<input type="checkbox" name="Classes" value="#class.ClassId" #checked />
#class.ClassName
}
And I call the template with name
#Html.EditorFor(m => m.Classes, "StudentClass");
Now in controller's Post method I can get the array of Classes (name of checkboxes).

passing form data to controller mvc3

I'm brand new to .net MVC3 so pardon my ignorance. I have a relatively large form (lots of fields) and I'm just wondering if I really need to reference each one of my fields as arguments to my action method on the back end or if it's possible to pass them all in as some sort of collection then reference the collection to obtain the values.
If that's possible could someone please provide a short example of how?
thanks
Shortest example I can come up with...
View model:
public class ViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
View:
<%: Html.EditorForModel() %>
Controller
[HttpGet]
public ActionResult Person()
{
return View(new ViewModel());
}
[HttpPost]
public ActionResult Person(ViewModel formData)
{
// formData is bound already -- just use it!
}
You can pass all the data to the controller as a custom type.
public ActionResult MyControllerMethod(MyCustomType formData)
If you strongly type your view then you'll be able to render the form fields using the HtmlHelper such as:
<%= Html.TextBoxFor(m => m.FirstName) %>
This was the ID of the form fields, which is used to associate the form field with the model property, will already be set for you.

Two models in one view in ASP MVC 3

I have 2 models:
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
}
public class Order
{
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
I want edit objects of BOTH classes in SINGLE view, so I need something like:
#model _try2models.Models.Person
#model _try2models.Models.Order
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x=>x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
This, of course, don't work: Only one 'model' statement is allowed in a .cshtml file. May be there is some workaround?
Create a parent view model that contains both models.
public class MainPageModel{
public Model1 Model1{get; set;}
public Model2 Model2{get; set;}
}
This way you can add additional models at a later date with very minimum effort.
To use the tuple you need to do the following, in the view change the model to:
#model Tuple<Person,Order>
to use #html methods you need to do the following i.e:
#Html.DisplayNameFor(tuple => tuple.Item1.PersonId)
or
#Html.ActionLink("Edit", "Edit", new { id=Model.Item1.Id }) |
Item1 indicates the first parameter passed to the Tuple method and you can use Item2 to access the second model and so on.
in your controller you need to create a variable of type Tuple and then pass it to the view:
public ActionResult Details(int id = 0)
{
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
var tuple = new Tuple<Person, Order>(person,new Order());
return View(tuple);
}
Another example : Multiple models in a view
Another option which doesn't have the need to create a custom Model is to use a Tuple<>.
#model Tuple<Person,Order>
It's not as clean as creating a new class which contains both, as per Andi's answer, but it is viable.
If you are a fan of having very flat models, just to support the view, you should create a model specific to this particular view...
public class EditViewModel
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
Many people use AutoMapper to map from their domain objects to their flat views.
The idea of the view model is that it just supports the view - nothing else. You have one per view to ensure that it only contains what is required for that view - not loads of properties that you want for other views.
ok, everyone is making sense and I took all the pieces and put them here to help newbies like myself that need beginning to end explanation.
You make your big class that holds 2 classes, as per #Andrew's answer.
public class teamBoards{
public Boards Boards{get; set;}
public Team Team{get; set;}
}
Then in your controller you fill the 2 models. Sometimes you only need to fill one. Then in the return, you reference the big model and it will take the 2 inside with it to the View.
TeamBoards teamBoards = new TeamBoards();
teamBoards.Boards = (from b in db.Boards
where b.TeamId == id
select b).ToList();
teamBoards.Team = (from t in db.Teams
where t.TeamId == id
select t).FirstOrDefault();
return View(teamBoards);
At the top of the View
#model yourNamespace.Models.teamBoards
Then load your inputs or displays referencing the big Models contents:
#Html.EditorFor(m => Model.Board.yourField)
#Html.ValidationMessageFor(m => Model.Board.yourField, "", new { #class = "text-danger-yellow" })
#Html.EditorFor(m => Model.Team.yourField)
#Html.ValidationMessageFor(m => Model.Team.yourField, "", new { #class = "text-danger-yellow" })
And. . . .back at the ranch, when the Post comes in, reference the Big Class:
public ActionResult ContactNewspaper(teamBoards teamboards)
and make use of what the model(s) returned:
string yourVariable = teamboards.Team.yourField;
Probably have some DataAnnotation Validation stuff in the class and probably put if(ModelState.IsValid) at the top of the save/edit block. . .
In fact there is a way to use two or more models on one view without wrapping them in a class that contains both.
Using Employee as an example model:
#model Employee
Is actually treated like.
#{ var Model = ViewBag.model as Employee; }
So the View(employee) method is setting your model to the ViewBag and then the ViewEngine is casting it.
This means that,
ViewBag.departments = GetListOfDepartments();
return View(employee);
Can be used like,
#model Employee
#{
var DepartmentModel = ViewBag.departments as List<Department>;
}
Essentially, you can use whatever is in your ViewBag as a "Model" because that's how it works anyway. I'm not saying that this is architecturally ideal, but it is possible.
Just create a single view Model with all the needed information in it, normaly what I do is create a model for every view so I can be specific on every view, either that or make a parent model and inherit it. OR make a model which includes both the views.
Personally I would just add them into one model but thats the way I do it:
public class xViewModel
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
#model project.Models.Home.xViewModel
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x => x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
You can use the presentation pattern http://martinfowler.com/eaaDev/PresentationModel.html
This presentation "View" model can contain both Person and Order, this new
class can be the model your view references.
Another way that is never talked about is
Create a view in MSSQL with all the data you want to present. Then use LINQ to SQL or whatever to map it. In your controller return it to the view. Done.
you can't declare two model on one view, try to use Html.Action("Person", "[YourController]") & Html.Action("Order", "[YourController]").
Good luck.
Beside of one view model in asp.net you can also make multiple partial views and assign different model view to every view, for example:
#{
Layout = null;
}
#model Person;
<input type="text" asp-for="PersonID" />
<input type="text" asp-for="PersonName" />
then another partial view Model for order model
#{
Layout = null;
}
#model Order;
<input type="text" asp-for="OrderID" />
<input type="text" asp-for="TotalSum" />
then in your main view load both partial view by
<partial name="PersonPartialView" />
<partial name="OrderPartialView" />
I hope you find it helpfull !!
i use ViewBag For Project and Model for task so in this way i am using two model in single view and in controller i defined viewbag's value or data
List<tblproject> Plist = new List<tblproject>();
Plist = ps.getmanagerproject(c, id);
ViewBag.projectList = Plist.Select(x => new SelectListItem
{
Value = x.ProjectId.ToString(),
Text = x.Title
});
and in view tbltask and projectlist are my two diff models
#{
IEnumerable<SelectListItem> plist = ViewBag.projectList;
}
#model List

Resources