asp.net mvc. Bind ViewModel to items in a collection - asp.net-mvc

I can bind to properties in the ViewModel fairly simply like so:
<%=Html.RadioButtonFor(m => m.isCool, true%>cool<br/>
<%=Html.RadioButtonFor(m => m.isCool, false)%>not cool<br/>
but what if I wanted to bind to collection items within the ViewModel. I'm not sure if it's possible. Here's what I have:
My ViewModel:
namespace MvcApplication3.ViewModels
{
public class cpApprovalViewModel
{
// My internal class
public class PersonImage
{
public bool isApproved { get; set; }
public bool isProFilePic { get; set; }
public string uriId { get; set; }
public string urlPath { get; set; }
}
public string displayName { get; set; }
public bool isCool { get; set; }
public List<PersonImage> personImages { get; set; }
}
}
My Controller:
public class cpApprovalController : Controller
{
//
// GET: /cpApproval/
public ActionResult Index()
{
cpApprovalViewModel viewModel = new cpApprovalViewModel();
viewModel.displayName = "jon doe";
viewModel.personImages = new List<cpApprovalViewModel.PersonImage>()
{
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "1", isProFilePic = false, urlPath="someImagePath1.jpg" },
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "2", isProFilePic = false, urlPath="someImagePath2.jpg" },
new cpApprovalViewModel.PersonImage(){isApproved = false, uriId = "3", isProFilePic = false, urlPath="someImagePath2.jpg" }
};
return View(viewModel);
}
//cpApprovalViewModel viewModel
[HttpPost]
public void formReceiver(cpApprovalViewModel viewModel)
{
// This is where I'd like to get access to the changed personImages (approved/disapproved )
}
}
My View:
<%: Model.displayName %><br /><br />
<% using (Html.BeginForm("formReceiver", "cpApproval", FormMethod.Post )){ %>
<% foreach (var anImage in Model.personImages){ %>
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), true, new { id = anImage.uriId })%> Approve <br />
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), false, new { id = anImage.uriId })%> Deny <br />
<hr />
<% } %>
<input type="submit" value="Save" />
<%} %>
I'm getting the following error:
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
Am I trying to do something impossible here? I hope this makes sense. Thanks.

Yes - what you're trying to do is possible.
I think your problem is that you're trying to get the relevant image again using .Single, when you already have the item in the foreach loop.
Try changing:
<%: Html.RadioButtonFor(model => model.personImages.Single(i => i.uriId == anImage.uriId), true, new { id = anImage.uriId })%> Approve <br />
To:
<%: Html.RadioButtonFor(model => anImage, true, new { id = anImage.uriId })%> Approve <br />
More info on model-binding to a Collection here.

Related

SelectList does not select items when generated inside View

I am having problems to preselect the items when creating SelectList inside View. Here is the Razor code:
#{
ViewBag.Title = "RoleGroupMappings";
}
<h2>Map roles to groups:</h2>
<table>
#foreach (var role in Model.Roles)
{
<tr>
<td>
<p>#role.RoleName: </p>
</td>
<td>
#using (Html.BeginForm("MapGroupsToRole", "Role", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Hidden("RoleId", role.RoleId)
#Html.DropDownList("Groups", new SelectList(role.Groups, "GroupId", "GroupName", role.Groups.Where(g => g.Mapped).Select(g => g.GroupId)), new { #class = "directory-groups", multiple = "multiple", style = "display: none;" })
<input type="button" value="Clear All" class="btn btn-danger clear-selection" />
<input type="submit" value="Save Changes" class="btn btn-success save-mappings" />
}
</td>
</tr>
}
</table>
Does anybody know what is the problem here?
I think you want to be using a MultiSelectList instead of the normal SelectList. Try this:
#Html.DropDownList("Groups", new MultiSelectList(role.Groups, "GroupId", "GroupName", role.Groups.Where(g => g.Mapped).Select(g => g.GroupId)), new { #class = "directory-groups", multiple = "multiple", style = "display: none;" })
Update
Being as this is working for me here, I'll show you how I've setup this test project. Hopefully that will help you to narrow down what the problem is. Test model setup:
public class Role
{
public int RoleId { get; set; }
public string RoleName { get; set; }
public List<Group> Groups { get; set; }
}
public class Group
{
public Guid GroupId { get; set; }
public string GroupName { get; set; }
public bool Mapped { get; set; }
}
The view model:
public class TestViewModel
{
public List<Role> Roles { get; set; }
}
public ActionResult Index()
{
var model = new TestViewModel
{
Roles = new List<Role>
{
new Role
{
RoleId = 1,
RoleName = "Test",
Groups = new List<Group>
{
new Group
{
GroupId = new Guid("12345678-1234-1234-1234-123412341234"),
GroupName = "Group 1",
Mapped = false
},
new Group
{
GroupId = new Guid("12345678-5678-6789-1234-123412341234"),
GroupName = "Group 2",
Mapped = true
},
new Group
{
GroupId = new Guid("12345678-0000-6789-1234-123412341234"),
GroupName = "Group 3",
Mapped = true
}
}
}
}
};
return View(model);
}
My view is the same as yours, except with using MultiSelect and I also removed #class = "directory-groups" and the display: none; style. Both group 2 and group 3 have their Mapped property set to true. This is the result from loading the view:
I found a problem in my controller. I was setting the ViewBag.Groups to some values, and obviously it was interfering with my DropDownList which had the same name. Sorry for that, I was staring whole day and I could not find the error, so I become little desperate. I should have looked at my controller, too.

ASP.NET MVC Generic List of Different SubClasses

I have an MVC model with a property that contains a generic collection of types that inherit from a single type. It displays the editor exactly as I would expect, but when I post back the types of all the items are the base type. How do I get it to return the correct types?
Model...
public class PageEM {
public long Id { get; set; }
public virtual IList<FieldEM> Fields { get; set; }
}
public class FieldEM { // I'd really like this to be abstract.
public long Id { get; set; }
public string Caption { get; set; }
public string Value { get; set; }
}
public class TextFieldEM : FieldEM {
}
public class CheckBoxFieldEM : FieldEM {
public bool ValueData {
get { return (bool)Value; }
set { Value = (string)value; }
}
PageEM View...
#model PageEM
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Fields</legend>
#Html.HiddenFor(m => m.Id)
#Html.EditorFor(m => m.Fields)
<input type="submit" value="Submit" title="Submit" />
</fieldset>
}
TextFieldEM Editor...
#model TextFieldEM
<div>
#Html.HiddenForFor(m => m.Id)
<div>
#Html.LabelFor(m => m.Value, Model.Caption)
</div>
<div class="editor-field">
#Html.TextBoxFor(m => m.Value)
#Html.ValidationMessageFor(m => m.Value)
</div>
</div>
CheckBoxFieldEM Editor...
#model CheckBoxFieldEM
<div>
#Html.HiddenForFor(m => m.Id)
<div class="editor-field">
#Html.EditorFor(m => m.DataValue)#Html.LabelFor(m => m.DataValue, Model.Caption, new { #class = "checkbox" })
</div>
</div>
Controller...
public partial class PageController : Controller {
public virtual ActionResult Edit() {
PageEM em = new PageEM() {
Id = 123,
Fields = new List<FieldEM>() {
new TextFieldEM() { Id = 1, Caption = "Text Line", Value = "This is test" },
new CheckBoxEM() { Id = 2, Caption = "Check here", ValueData = true }
}
};
return View(em);
}
[HttpPost]
public virtual ActionResult Edit(PageEM em) {
if (!ModelState.IsValid)
return View(em);
// but all of the em.Fields are FieldEM.
}
}
So how do I get it to post back with the subclassed FieldEMs?
You can't do that with the DefaultModelBinder. You'll have to create your own custom model binder in order to do what you want to do.
These might be helpful:
https://gist.github.com/joelpurra/2415633
ASP.NET MVC3 bind to subclass
ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

MVC trying to pass model from razor view to controller

So my story is that I am having trouble with the post to the controller, the view seems to work fine. When the postback happens the tm.BookId is 0 (should be 1) and the list count is 0. First I will display the model:
public class TransferModel
{
public TransferModel()
{
cbItems = new List<CheckBoxItem>();
}
public List<CheckBoxItem> cbItems {get;set;}
public int BookId;
public class CheckBoxItem
{
public int AttributeId { get; set; }
public string Attribute { get; set; }
public bool Selected { get; set; }
}
}
The Controller part:
public ActionResult AddAttributes(int id = 0)
{
db.transMod.BookId = id;
BookInfo book = db.BookInfoes.Find(id);
var latts = db.BookAtts.ToList();
foreach (BookAtt ba in latts)
{
db.transMod.cbItems.Add(new TransferModel.CheckBoxItem { Attribute = ba.Attribute, AttributeId = ba.BookAttId, Selected = false });
}
List<BookAtt> atInList = book.BookAtts.ToList();
foreach (TransferModel.CheckBoxItem cb in db.transMod.cbItems)
{
if (atInList.Exists(item => item.Attribute == cb.Attribute))
cb.Selected = true;
}
return View(db.transMod);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddAttributes(TransferModel tm)
{
List<BookAtt> atPool = db.BookAtts.ToList();
BookInfo book = db.BookInfoes.Find(tm.BookId);
foreach (TransferModel.CheckBoxItem sel in tm.cbItems)
{
if (sel.Selected)
book.BookAtts.Add(atPool.Find(item1 => item1.Attribute == sel.Attribute));
}
db.SaveChanges();
return RedirectToAction("AddAttributes");
}`enter code here`
And finally the view:
#model BrightStar.Models.TransferModel
#{
ViewBag.Title = "Update Attributes";
}
<h2>Add Attributes</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<table>
#Html.HiddenFor(model => Model.BookId)
#Html.HiddenFor(model => Model.cbItems)
#foreach (var itm in Model.cbItems)
{
<tr>
<td>#Html.HiddenFor(mo => itm.AttributeId)</td>
<td>#Html.CheckBoxFor(mo => itm.Selected)</td>
<td>#Html.DisplayFor(mo => itm.Attribute)</td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
}
enter code here
Model binding doesn't happen automatically, items needs to be in certain format to get binded to list properties in POST actions. Check this out.
Try checking out the value of BookId property in the DOM to confirm it is 1, otherwise it should bind normally.
You should reference your model's properties in helpers to correctly generate names for your controls:
#Html.HiddenFor(model => Model.cbItems)
should be
#Html.HiddenFor(model => model.cbItems)

Model returning null value

This is my code for my model
public class ManufacturerModel
{
public int ProductTYpeId { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public List<SelectListItem> manf { get; set; }
public Manufacturer manufacturer { get; set; }
}
This is my code in cshtml file
#using (Html.BeginForm("addmanufacturer", "Admin", FormMethod.Post, new { id = "formPageID" }))
{
<div class="row">
<label>Select Existing Manufacturer<span style="color: Red">*</span>:</label>
<div class="formRight">
#Html.DropDownList("Id", Model.manf)
</div>
</div>
<div class="row">
<label>Manufacturer Name<span style="color: Red">*</span>:</label>
<div class="formRight">
#Html.TextBoxFor(m => m.manufacturer.name)
#Html.ValidationMessageFor(m => m.manufacturer.name)
</div>
</div>
}
I am posting this form and when I am trying to fetch the value from the manfacurer ie - ManufacturerModel manufacturer = new ManufacturerModel();
using a model object all the value are coming out null.
in the text box If I replace it with m => m.Name then I am able to get proper value of Name.
can any one suggest what the problem is
I am using the manf to bind a dropdown. If In case I post back the form and the if it is return the value becomes blank, I need to refill the value..
public ActionResult addmanufacturer(string id)
{
if (id == null)
id = "0";
ManufacturerModel manufacturer = new ManufacturerModel();
manufacturer.ProductTYpeId = Convert.ToInt32(id);
manufacturer.manf = GetManf();
manufacturer.Id = -1;
return View(manufacturer);
}
I think problem will be becouse of:
#using (Html.BeginForm("Action", "Controller", FormMethod, HTML ATTRIBUTES )) { }
You probably want this overload:
#using (Html.BeginForm("Action", "Controller", Route Values, FormMethod, html attributes )) { }
Important is say him that you want route values and not a html atrributes, so try this
#using (Html.BeginForm("addmanufacturer", "Admin", new { id = "formPageID" }, FormMethod.Post, null )) { }
Hope, it helps.
M.

MVC2 Binding isn't working for Html.DropDownListFor<>

I'm trying to use the Html.DropDownListFor<> HtmlHelper and am having a little trouble binding on post. The HTML renders properly but I never get a "selected" value when submitting.
<%= Html.DropDownListFor( m => m.TimeZones,
Model.TimeZones,
new { #class = "SecureDropDown",
name = "SelectedTimeZone" } ) %>
[Bind(Exclude = "TimeZones")]
public class SettingsViewModel : ProfileBaseModel
{
public IEnumerable TimeZones { get; set; }
public string TimeZone { get; set; }
public SettingsViewModel()
{
TimeZones = GetTimeZones();
TimeZone = string.Empty;
}
private static IEnumerable GetTimeZones()
{
var timeZones = TimeZoneInfo.GetSystemTimeZones().ToList();
return timeZones.Select(t => new SelectListItem
{
Text = t.DisplayName,
Value = t.Id
} );
}
}
I've tried a few different things and am sure I am doing something stupid... just not sure what it is :)
Here's an example I wrote for you illustrating the usage of DropDownListFor helper method:
Model:
public class SettingsViewModel
{
public string TimeZone { get; set; }
public IEnumerable<SelectListItem> TimeZones
{
get
{
return TimeZoneInfo
.GetSystemTimeZones()
.Select(t => new SelectListItem
{
Text = t.DisplayName, Value = t.Id
});
}
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new SettingsViewModel());
}
[HttpPost]
public ActionResult Index(SettingsViewModel model)
{
return View(model);
}
}
View:
<% using (Html.BeginForm()) { %>
<%= Html.DropDownListFor(
x => x.TimeZone,
Model.TimeZones,
new { #class = "SecureDropDown" }
) %>
<input type="submit" value="Select timezone" />
<% } %>
<div><%= Html.Encode(Model.TimeZone) %></div>

Resources