Binding list of nested viewmodels on post ASP.NET MVC - asp.net

I have the following viewmodel structure:
ItemViewModel containing a property Model of type BaseViewModel.
In this concrete example, Model is of type WeekManagerWorkScheduleViewModel. This viewmodel in turns consists of an IEnumerable of type ManagerWorkScheduleViewModel.
Inside the ItemView.cshtml the following is used:
<form class="form-horizontal" id="#Model.FormName" name="#Model.FormName" onsubmit="#Model.OnSubmitFunction">
<div class="form-group form-group-sm">
#Html.EditorFor(model => model.Model, Model.ItemViewName + "View")
</div>
<button class="btn pull-left" type="submit" style="margin: 2px;">
<span class="glyphicon glyphicon-save"></span>
#Model.ViewConfiguration.SaveText #Model.ItemTitle
</button>
</form>
I have omitted some details (basically a bunch of if's determining whether or not to add CRUD buttons. The Model.ItemViewName is the typename (in this case its WeekManagerWorkScheduleViewModel).
public class WeekManagerWorkScheduleViewModel : BaseViewModel
{
[HiddenInput]
public int RegionId { get; set; }
[HiddenInput]
public IEnumerable<DateTime> Dates { get; set; }
public IEnumerable<ManagerWorkScheduleViewModel> WorkSchedules { get; set; }
}
The WeekManagerWorkScheduleView.cshtml looks like this:
#using DRSTransportPortal.ViewModels
#model WeekManagerWorkScheduleViewModel
#{
ViewBag.Title = "Ugentlig arbejdsplan - ledere";
}
#Html.HiddenFor(m => m.RegionId)
<table class="table">
<thead>
<tr>
<th><b>Leder</b></th>
<th><b>Uge</b></th>
<th><b>Mandag</b></th>
<th><b>Tirsdag</b></th>
<th><b>Onsdag</b></th>
<th><b>Torsdag</b></th>
<th><b>Fredag</b></th>
<th><b>Lørdag</b></th>
<th><b>Søndag</b></th>
</tr>
<tr>
<th></th>
<th></th>
#foreach (var date in Model.Dates)
{
<th><i><small>#date.Date.ToString("dd-MM-yyyy")</small></i></th>
}
</tr>
</thead>
<tbody>
#Html.EditorFor(m => m.WorkSchedules)
</tbody>
</table>
I have a view called ManagerWorkScheduleViewModel.cshtml residing in Views/imalazysod/EditorTemplates (MVC knows this location, as I am using a custom view engine, derived from Razor):
#using DRSTransportPortal.ViewModels
#model ManagerWorkScheduleViewModel
<tr id="#Model.Id">
<td>#Html.HiddenFor(m => m.Id)</td>
<td>#Html.DisplayFor(m => m.ManagerName, new { htmlAttributes = new { #class = "form-control editoritem" } })</td>
<td>#Html.DisplayFor(m => m.DateWeekText, new { htmlAttributes = new { #class = "form-control editoritem" } })</td>
<td>#Html.EditorFor(m => m.MondayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.TuesdayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.WednesdayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.ThursdayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.FridayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.SaturdayCodeChoice, new { #class = "form-control editoritem" })</td>
<td>#Html.EditorFor(m => m.SundayCodeChoice, new { #class = "form-control editoritem" })</td>
</tr>
The "Choice" properties are all of type ChoiceViewModel using a ChoiceViewModel.cshtml.
Now, everything renders fine:
Screenshot (names omitted). Red box indicates 1 (one) nested viewmodel
The generated HTML looks like this (only the first row and first few cells are shown here):
<tr id="134">
<td><input name="Model.WorkSchedules[0].Id" id="Model_WorkSchedules_0__Id" type="hidden" value="134" data-val-required="Feltet Id skal udfyldes." data-val="true" data-val-number="The field Id must be a number."></td>
<td>OMITTED</td>
<td>2016 - 36</td>
<td>
<select name="Model.WorkSchedules[0].MondayCodeChoice.SelectedValue" class="form-control editoritem dropdown" id="SelectChoices" onchange="">
<option value="1">06:00 - 14:00</option>
<option value="19">06:00 - 18:00</option>
<option value="31">08:00 - 16:00</option>
<option value="2">10:00 - 18:00</option>
<option value="32">10:00 - 18:00</option>
<option value="23">Bagvagt</option>
<option value="22">Ferie</option>
<option value="8">Fri</option>
<option value="3">Kontor</option>
<option value="15">Kussus</option>
<option value="16">Syg</option>
</select>
</td>
REST OF HTML IS OMMITTED (CONTINUES FOR 12 ROWS WITH 10 CELLS EACH)
</tr>
However, when I post back (using jQuery, ajax btw) this is what I get:
Controller breakpoint, after modelbinding
I have tried putting in a custom modelbinder on the BaseViewModel, and debugging that haven't seen anything that doesn't look alright. It resolves the correct types (WeekManagerWorkScheduleViewModel, etc.).
Using that, my Forms element from ControllerContext->...->Request is:
{Model.Id=141&ModelType=DRSTransportPortal.ViewModels.WeekManagerWorkScheduleViewModel%2c+DRSTransportPortal%2c+Version%3d1.0.0.0%2c+Culture%3dneutral%2c+PublicKeyToken%3dnull&Model.RegionId=1&Model.WorkSchedules%5b0%5d.Id=134&Model.WorkSchedules%5b0%5d.MondayCodeChoice.SelectedValue=22&Model.WorkSchedules%5b0%5d.TuesdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.WednesdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.ThursdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.FridayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.SaturdayCodeChoice.SelectedValue=1&Model.WorkSchedules%5b0%5d.SundayCodeChoice.SelectedValue=1&..... CONTINUES FOR ALL 12 NESTED VIEWMODELS}
I have made sure the following is true:
All bindings are made to properties (default ie. {get;set;})
No viewmodels, including BaseViewModel, have constructors (ie. default constructors should be created)
All properties and classes are public
So...my question is, why is the modelbinder unable to create the viewmodels of the list? I have no problem using the template elsewhere, editing pr. person, ie. one row at a time).
Yes, I know there are MANY questions and MANY answers related to MVC modelbinding, but none seem to quite fall into this category (same goes for books). What really puzzles me, is that it is recognized that a list of 12 items is needed, but it just isn't populated.
EDIT:
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
var type = Type.GetType((string) typeValue.ConvertTo(typeof (string)), true);
if(!typeof(BaseViewModel).IsAssignableFrom(type))
throw new InvalidOperationException("NOT A BASEVIEWMODEL");
var model = Activator.CreateInstance(type);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
return model;
}
}
[ModelBinder(typeof(BaseViewModelBinder))]
public class BaseViewModel
{
[HiddenInput]
public int Id { get; set; }
[HiddenInput]
public DateTime Created { get; set; }
[HiddenInput]
public DateTime Edited { get; set; }
public virtual string SelectionName { get; set; }
}
EDIT 2:
[HttpPost]
public override async Task<JsonResult> Save(ItemViewModel item, string parentName, int? parentId)
{
if (item?.Model == null)
{
const int result = 0;
return Json(new { result });
}
var model = item.Model as WeekManagerWorkScheduleViewModel;
if (model == null)
{
const int result = 0;
return Json(new { result });
}
foreach (var inner in model.WorkSchedules)
{
}
return await base.Save(item, parentName, parentId);
}
public class ManagerWorkScheduleViewModel : BaseViewModel
{
[HiddenInput]
public int? ManagerId { get; set; }
[DisplayName("Leder")]
public string ManagerName { get; set; }
[HiddenInput]
public int? DateWeekId { get; set; }
[DisplayName("År / Uge")]
public string DateWeekText { get; set; }
[HiddenInput]
public int? MondayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Mandag")]
public ChoiceViewModel MondayCodeChoice { get; set; }
[HiddenInput]
public int? TuesdayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Tirsdag")]
public ChoiceViewModel TuesdayCodeChoice { get; set; }
[HiddenInput]
public int? WednesdayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Onsdag")]
public ChoiceViewModel WednesdayCodeChoice { get; set; }
[HiddenInput]
public int? ThursdayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Torsdag")]
public ChoiceViewModel ThursdayCodeChoice { get; set; }
[HiddenInput]
public int? FridayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Fredag")]
public ChoiceViewModel FridayCodeChoice { get; set; }
[HiddenInput]
public int? SaturdayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Lørdag")]
public ChoiceViewModel SaturdayCodeChoice { get; set; }
[HiddenInput]
public int? SundayCodeId { get; set; }
[UIHint("ChoiceViewModel")]
[DisplayName("Søndag")]
public ChoiceViewModel SundayCodeChoice { get; set; }
}

Ah, stupid me.
I forgot how I actually made this framework to begin with.
I created this mainly for CRUD operations, so I only need to define viewmodel (derived from BaseViewModel), view, repository and dto types for each business entity as well as an empty controller. Then everything else is using a standard set of controllers (three levels of derivation, depending on functionality needed) and all I need to do is create a config entry for the business entity, defining the vm, view, repo types as well as some CRUD options for buttons and labels.
All top level viewmodels and view are the same, like ItemView, ItemViewModel, ItemListView, SingleSelector, MultipleSelector etc etc.
What I had FORGOTTEN was that I limited myself to one business entity at a time, hence outputting the class (see the modelbinder), so I COULD use derived viewmodels...this works great, except obviously in this case.
What happens is, as the binding tries to populate the list, it keeps trying to populate it using the first viewmodel type (as this is the only one known after POST) and not the derived. DAMMIT! So back to the drawing table (no nesting :( )

Related

ASP.NET MVC - Entity framework Lazing Loading is not retrieving data from related classes, using fluent API

I have two classes, with 1 to many relationship, but when trying to retrieve the list of objects that is related to the Support class, that list comes empty.
public class Support
{
public int SupportId { get; set; }
public string Name { get; set; }
public string Summary { get; set; }
public virtual ICollection<FileDetail> FileDetails { get; set; }
}
public class FileDetail
{
public Guid Id { get; set; }
public string FileName { get; set; }
public string Extension { get; set; }
public int SupportId { get; set; }
public virtual Support Support { get; set; }
}
My Repository:
public abstract class Repository<TEntity> : IRepositoryRead<TEntity>, IRepositoryWrite<TEntity> where
TEntity : Entity, new()
{
public virtual IEnumerable<TEntity> GetAll()
{
return Dbset.ToList();
}
}
My Fluent API config
public class FileDetailConfig : EntityTypeConfiguration<FileDetail>
{
public FileDetailConfig()
{
HasKey(c => c.Id);
Property(c => c.FileName);
Property(c => c.Extension);
HasRequired(c =>c.Support )
.WithMany(c => c.FileDetails )
.HasForeignKey(c => c.SupportId );
ToTable("Support");
}
}
My Controller
public ActionResult Index()
{
return View(__supportRepository.GetAll());
}
My View:
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Summary)
</td>
<td>
#if (item.FileDetails.Count() == 0)
{
<span>No File</span>
}
else
{
<span>#item.FileDetails.Count() File(s)</span>
}
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
Delete
</td>
</tr>
}
also tried to configure the fluent api in the FileDetail class, but the same thing remains. The list of filedetains returns empty in view
EDIT:
Thats my Support table
Support Table
Ands my FileDetail table:
FileDetain Table
EDIT:
I think the problem is the query, it looks like the query is not looking for the related data in the other table. I just can not understand why
{SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Summary] AS [Summary] FROM [dbo].[Support] AS [Extent1]}
Since FileDetails is virtual, I'm pretty sure it will be lazy loaded. So it wont be in the initial query.
I don't understand this mapping:
HasRequired(c =>c.Support)
.WithMany(c => c.FileDetails)
.HasForeignKey(c => c.SupportId)
I don't see a Support property on your model, can you change it to just:
HasMany(c => c.FileDetails).HasForiegnKey(c => c.SupportId);
And see what you get?
You also can try Include in your query (assuming your not calling ToList(), see below):
return DBset.Include(x => x.FileDetails);
Another thing, this will load the entire table into memory:
return Dbset.ToList();
I'd highly recommend just returning the DBSet instead, further filtering will get passed to the DB:
return DBset;

[Bind(Include = does not work with collection

I have a controller that looks like below:
public async Task<ActionResult> CreateProduct([Bind(Include = "Id,MyCollection")] MyClass myClass)
and this is the view:
<div class="form-group">
#Html.LabelFor(model => model.Id, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
</div>
<table>
<tr>
<th>#Html.LabelFor(model => model.MyCollection.First().A)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().B)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().C)</th>
</tr>
#foreach (var item in this.Model.Warnings)
{
<tr>
<td>#Html.ValueFor(model => item.A)</td>
<td>#Html.EditorFor(model => item.B)</td>
<td>#Html.EditorFor(model => item.C)</td>
</tr>
}
</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>
when I click Save, it posts to the action but only Id is assigned to the object, not myCollection.
What do I need to do to include the collection when posting them to controller?
Update
Model is generated by Entity Framework
public abstract partial class MyBaseClass
{
public Module()
{
this.MyCollection= new HashSet<Warning>();
}
public int Id { get; set; }
public virtual ICollection<Warning> MyCollection { get; set; }
}
public partial class MyClass : MyBaseClass
{
// more properties that aren't used on this controller
}
In order to get this to work you need to understand how ASP.NET MVC handles model binding for a List. Scott Hansleman has a good post explaining this.
Since your question was rather vague in terms of what the actual properties you're dealing with are, I put together a little example which successfully binds to a list:
Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//Initial test data
var zoo = new Zoo()
{
Id = 1,
Name = "Vilas Zoo",
Animals = new List<Animal>()
{
new Animal() {
Id = 1,
Name = "Red Panda"
},
new Animal() {
Id = 2,
Name = "Sloth"
},
new Animal() {
Id = 3,
Name = "Badger"
},
}
};
return View(zoo);
}
[HttpPost]
public JsonResult Index(Zoo zoo)
{
return Json(zoo);
}
}
Model
public class Zoo
{
public int Id { get; set; }
public string Name { get; set; }
public List<Animal> Animals { get; set; }
}
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
View
<h1>#Model.Id - #Model.Name</h1>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
for (var i=0; i < Model.Animals.Count; i++)
{
#Html.EditorFor(m => Model.Animals[i])
}
<button type="submit">Save it, yo</button>
}
Notice how I'm using a for loop instead of a foreach and the actual index of the loop is being used in the call to EditorFor.

Generating a dropdown list with database first

In my mvc3 application i want to populate the dropdownlist for the data which is coming from database here am using entityframework with database first approach so please help me to do this
You did not provide any code of what you have done so far so I know nothing about the data that you have been working with. I will post some code and all that you will have to do is to modify it to fit in with your scenario.
Lets work with a simple solution of loan applications. A client needs to apply for a loan and he needs to supply banking details. He will have to select a bank from a drop down list.
Lets start with the domain model called Bank. This represents your data coming from your database table. Lets call the table Banks.
public class Bank
{
public int Id { get; set; }
public string Name { get; set; }
}
Your table called Banks will look like this:
Id | int | not null | primary key
Name | varchar(50) | not null
Depeneding on you what you need to do I normally have a service layer that calls my bank repository to bring back the data. But seeing that you only need to bring back data and nothing else we can skip the service layer.
public class BankRepository : RepositoryBase<Bank>, IBankRepository
{
public IEnumerable<Bank> FindAll()
{
return DatabaseContext.Banks;
}
}
You database context will look like this:
public class DatabaseContext : DbContext
{
public DbSet<Bank> Banks { get; set; }
}
This is how your data retrieval methods could look like. It might not be the full solution but there are many samples online. Just go and Google.
Lets move onto the web application.
Your view/page will work with a view model and not your domain model. You use view models to represent your data on the view/page. So on your create view you will pass in a create application view model with a list of your banks. An instance of IBankRepository will be supplied through a technique called dependency injection. I use Autofac for this.
public class ApplicationViewModel
{
public int BankId { get; set; }
public IEnumerable<Bank> Banks { get; set; }
}
Your controller's action method will populate the view model and send it to your view.
public class ApplicationController : Controller
{
private readonly IBankRepository bankRepository;
public ApplicationController(IBankRepository bankRepository)
{
this.bankRepository = bankRepository;
}
public ActionResult Create()
{
ApplicationViewModel viewModel = new ApplicationViewModel
{
Banks = bankRepository.FindAll()
};
return View(viewModel);
}
}
Your view will then receive this view model and do with it what it needs to do. In this case it will populate your bank drop down.
#model YourProject.ViewModels.Applications.ApplicationViewModel
#using (Html.BeginForm())
{
<tr>
<td class="edit-label">Bank: <span class="required">**</span></td>
<td>
#Html.DropDownListFor(
x => x.BankId,
new SelectList(Model.Banks, "Id", "Name", Model.BankId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.BankId)
</td>
</tr>
}
I don't what your experience is but judging from your question it seems like you still need to do a lot of research. There are tonnes of examples online. You will need to work through samples of Entity Framework code first and ASP.NET MVC. Invest some time and you will reap the rewards later.
I hope this works and best of luck. The solution might not be what you want but it can help guide you in the right direction.
Let assume you have a class like so
public class ProductBrand
{
//Eg. Nokia, Hp, Dell
/// <summary>
/// Second Level to Category
/// Has Foreign Key to category table
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductBrandId { get; set; }
[Display(Name = "Category")]
public int ProductCategoryId { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
[Required(ErrorMessage = "This field is required")]
[StringLength(300, ErrorMessage = "This field must not be greater than 300 xters long")]
[Display(Name="Name")]
public string BrandName { get; set; }
public IList<Product> Products { get; set; }
[Display(Name = "Status")]
public bool ActiveStatus { get; set; }
}
Which means this model has a foreign key name ProductCategoryId then in your create Product brand view you will need a dropdownlist containing All productcategory to choose from.
Just create an actionresult like so
public ActionResult CreateProductBrand() {
ViewBag.ProductCategoryId = new SelectList(context.ProductCategories, "ProductCategoryId", "CategoryName");
return View();
}
Then call the viewbag in you corrensponding view like so:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<table style="width:400px" class="post-form">
<tr>
<td>Category</td>
<td>
<div class="editor-field">
#Html.DropDownList("ProductCategoryId", String.Empty)
#Html.ValidationMessageFor(model => model.ProductCategoryId)
</div>
</td>
</tr>
</table>
<table style="width:400px" class="post-form">
<tr>
<td>Brand</td>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.BrandName)
#Html.ValidationMessageFor(model => model.BrandName)
</div>
</td>
</tr>
<tr>
<td>Status</td>
<td>
<div class="editor-field">
#Html.EditorFor(model => model.ActiveStatus)
#Html.ValidationMessageFor(model => model.ActiveStatus)
</div>
</td>
</tr>
<tr>
<td></td>
<td>
<input type="submit" class="button blue" value="Save" />
<a href="#Url.Action("ProductBrand", "ProductSetup")" class="button">Cancel
</a>
</td>
</tr>
</table>
}
ViewModel:
public List<Item> _Items { get; set; }
public int_newID { get; set; }
Model Item
public int_id { get; set; }
public string _name { get; set; }
Controller:
Populate _Items with data and send that list to your view
View:
#Html.DropDownListFor(c => c._newID, new SelectList(Model._Items , "_id", "_name"),--Select Item--")

asp net mvc3 post a list of objects to action

I created a page with aspnet mvc3. It show all users info as a list. I want to do something with this list. There are some checkboxes that belong to each items. When I click some checkboxes and press submit button, I want to post the whole list as a collection and save each items of this collection to database. There are several notes on internet but there is no exact solution. I have a UserDto. and want to use this to transfer users data in all sections.
Does anyone have any full solution about this or can they give any idea?
Thanks in advance.
Kerem
I added some of my codes. You can see the lead sentences what they are about.
this is my index view detail:
#model List<DomainModel.UserApprovalDto>
#{
ViewBag.Title = "Manage Users";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Manage Users</h2>
<div>#Html.Partial("_PartialManageUsers", (List<DomainModel.UserApprovalDto>)Model) </div>
this is my partial view detail:
#model List<DomainModel.UserApprovalDto>
#using (Html.BeginForm("ConfirmUsers", "ManageUsers", FormMethod.Post))
{
<table>
<tr>
<th>
Name
</th>
<th>
Is Reported
</th>
</tr>
#for (int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => Model[i].FirstName)
</td>
<td>
#Html.CheckBox("IsReported", Model[i].IsReported.HasValue ? Model[i].IsReported.Value : false)
#*#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);*# #* #if (Model[i].IsReported != null)
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}
else
{
#Html.CheckBoxFor(modelItem => Model[i].IsReported.Value);
}*#
</td>
<td>
</td>
</tr>
}
</table>
<div>
<input name="submitUsers" type="submit" value="Save" />
</div>
}
this is my controller submit method
[HttpPost]
public ActionResult ConfirmUsers(List<DomainModel.UserApprovalDto> collection)
{
if (ModelState.IsValid)
{
//TO-DO
}
return RedirectToAction("Index");
}
this last one is my DTO class detail:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DomainModel
{
public class UserApprovalDto
{
public long UserId { get; set; }
public Guid CarUserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhotoPath { get; set; }
public string PhotoSmallPath { get; set; }
public string PhotoSquarePath { get; set; }
public string PhotoBigPath { get; set; }
public bool IsBlocked { get; set; }
public bool IsDeleted { get; set; }
}
}
when I submit this code my list return null collection to my controller method.
thanks for your comments.
Assuming you are creating a screen which adds/ remove users to a course. So let's create some viewmodels
public class CourseVM
{
public string Name { set;get;}
public int CourseID { set;get;}
public List<UserVM> Users { set;get;}
public CourseVM()
{
Users=new List<UserVM>();
}
}
public class UserVM
{
public string Name { set;get;}
public int UserID{ set;get;}
public bool IsSelected { set;get;}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult Add()
{
var vm = new CourseVM();
//The below code is hardcoded for demo. you may replace with DB data.
vm.Users.Add(new UseVM { Name = "Jon" , UserID=1});
vm.Users.Add(new UseVM { Name = "Scott", UserID=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(UserVM.cshtml)
Add this code to your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model CourseVM
#using (Html.BeginForm())
{
<div>
#Html.EditorFor(m=>m.Users)
</div>
<input type="submit" value="Submit" />
}
Now when you post the form, Your Model will have the Users Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult Add(CourseVM model)
{
if(ModelState.IsValid)
{
//Check for model.Users collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}

Binding model with child list MVC 3 razor

I have the following model :
public class ContratoDetailsViewModel
{
[StringLength(50)]
[RegularExpression("^[a-z0-9_\\+-]+(\\.[a-z0-9_\\+-]+)*#[a-z0-9-]+(\\.[a-z0-9-]+)*\\.([a-z]{2,4})$")]
[DisplayName("E-Mail Adm.")]
public string emailAdm { get; set; }
}
public class ContratoDetailContainerViewModel
{
public ContratoDetailsViewModel contrato { get; set; }
public IList<ContratoModels.CCasinoViewModel> ccasinos { get; set; }
}
public class CCasinoViewModel
{
public short codigo { get; set; }
public List<SelectListItem> listCasinos { get; set; }
}
the following view :
#model ContratoModels.ContratoDetailContainerViewModel
#{
...
}
#using (Html.BeginForm(new { currentaction = ViewBag.mode }))
{
...
#Html.EditorFor(m => m.contrato.emailAdm, state1)<br />
#Html.EditorFor(m => m.ccasinos,"test")
<input type="submit" value="Save" />
}
in the folder "EditorTemplates" i have a template called "test.cshtml" :
#model List<ContratoModels.CCasinoViewModel>
#for (int i = 0; i < Model.Count(); i++)
{
#Html.DropDownListFor(m => m[i].codigo,Model[i].listCasinos)
}
My Controller post action is like this :
[HttpPost]
public ActionResult Details(ContratoModels.ContratoDetailContainerViewModel model, FormCollection form)
{
var contrato = model.contrato;
var casinos = model.ccasinos;
}
Before send the view ccasinos,codigo and listCasinos are initialised
when i am in debug mode i see the value of them... the form display work like a charm.
BUT ... when i submit the form the model.ccasinos is always null !! why ?
thank you very much for your reply.
note : I use a EditorFor with the child of my main model but if there is a better solution
for display and submit with MCV 3 I am interested ...
Try replacing:
#Html.EditorFor(m => m.ccasinos, "test")
with this:
#Html.EditorFor(m => m.ccasinos)
and then rename your test.cshtml template to CCasinoViewModel.cshtml and replace its contents with this:
#model CCasinoViewModel
#Html.DropDownListFor(x => x.codigo, Model.listCasinos)
Because the editor template is now named the same way as the type of the list, ASP.NET MVC will automatically render it for each element of this list so that you don't have to write loops.
Also you can safely remove the FormCollection argument from your action. It's completely useless when you are working with view models:
[HttpPost]
public ActionResult Details(ContratoModels.ContratoDetailContainerViewModel model)
{
var contrato = model.contrato;
var casinos = model.ccasinos;
...
}

Resources