I have a complex object which has a IEnumerable property and that I want to display in my view.
For that purpose I've created an EditorTemplate for that list.
So, in my view I have the following code:
<div id="tabMed" class="tab-pane">
<fieldset>
<div>
#Html.EditorFor(m => m.MyList)
</div>
</fieldset>
</div>
m.List is an IEnumerable type and the Editor template has inside some fields for displaying the object.
This works fine when the list has 1 or more objects; it will display and repeat the template for every object in that list.
Now the problem:
Let's supose that I want to edit the object that contains that list and the IEnumerable property is null. The Editor template won't appear at all, leaving no possibility to create the first object in that list.
Is there any approach to show an empty template if the list is null without having to something like this?:
<div id="tabMed" class="tab-pane">
<fieldset>
<div>
#if (Model.MyList.Count() > 0)
{
#Html.EditorFor(m => m.MyList)
}
else
{
// empty object form template here...
}
</div>
</fieldset>
</div>
Edit: The EditorTemplate look like this:
#model Jazz.Models.MyList.MyObject
<div class="control-group">
#Html.LabelFor(model => Model.Name, new { #class="control-label"})
<div class="controls">
#Html.TextBoxFor(m => m.Name, new { #class="collection-item", autocomplete = "off", maxlength = 64 })
</div>
</div>
The MyList is the list that I want to display
The MyObject class is declared inside MyList
So, the model would be like this:
public class MyList
{
public MyList()
{
this.list = new List<MyObject>();
}
public int id{ get; set; }
public List<MyObject> list{ get; set; }
public class MyObject
{
public MyObject(){}
public string Name{ get; set; }
}
}
I've put the example in a generic way so it's simplier to show the problem.
Related
So I have the following code:
#model Project.Models.ViewModels.SomeViewModel
#using (Html.BeginForm("SomeAction", "SomeController", new { id = Model.Id}))
{
for(int i = 0; i < Model.SomeCollection.Count(); i++)
{
#Html.HiddenFor(x => Model.SomeCollection.ElementAt(i).Id)
<div class="grid_6">
#Html.TextAreaFor(x => Model.SomeCollection.ElementAt(i).Text, new { #style = "height:150px", #class = "grid_6 input" })
</div>
}
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
On the Controller Side I have the following:
[HttpPost]
public ActionResult SomeAction(int id, SomeViewModel model)
{
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
My View Model is set up like this:
public class SomeViewModel
{
public SomeViewModel()
{
}
public IEnumerable<ItemViewModel> SomeCollection { get; set; }
}
public class ItemViewModel{
public ItemViewModel(){}
public int Id {get;set;}
public string Text{get;set;}
}
The SomeCollection is always empty when SomeAction if performed. What do I have to do in order to show the updated values by users. Text Property and Id field.
Use an EditorTemplate
Create an EditorTemplate folder under your Views/YourcontrollerName and create a view with name ItemViewModel.cshtml
And Have this code in that file
#model Project.Models.ViewModels.ItemViewModel
<p>
#Html.EditorFor(x => x.Text)
#Html.HiddenFor(x=>x.Id)
</p>
Now from your Main view, call it like this
#model Project.Models.ViewModels.SomeViewModel
#using (Html.BeginForm("SomeAction", "Home", new { id = Model.Id}))
{
#Html.EditorFor(s=>s.SomeCollection)
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
Now in your HTTPPOST method will be filled with values.
I am not sure what you want to do with the values( returning the partial view ?) So not making any comments about that.
I am not sure you have posted all the code.
Your action method does not do anything, since it returns a partial view (for some reason from a post call, not an ajax request) using a new model object.
Your effectively passing a model back to the action and then discarding it, and returning a new model object. This is the reason your collection is always empty, its never set anywhere.
Well, for one thing, why do you have both the model AND id, a property of model, sent back to the controller? Doesn't that seem a bit redundant? Also, you're using a javascript for loop in the view. It'd be much easier to just use #foreach.
Anyway, your problem is that when you tell an action to accept a model, it looks in the post for values with keys matching the names of each of the properties of the model. So, lets say we have following model:
public class Employee
{
public string Name;
public int ID;
public string Position;
}
and if I'm passing it back like this:
#using(Html.BeginForm("SomeAction", "SomeController"))
{
<input type="text" name = "name" [...] /> //in your case HtmlHelper is doing this for you, but same thing
<input type="number" name = "id" [...] />
<input type="submit" name = "position" [...] />
}
To pass this model back to a controller, I'd have to do this:
Accepting a Model
//MVC matches attribute names to form values
public ActionResult SomethingPosted(Employee emp)
{
//
}
Accepting a collection of values
//MVC matches parameter names to form values
public ActionResult SomethingPosted(string name, int id, string postion)
{
//
}
or this:
Accepting a FormCollection
//same thing as first one, but without a strongly-typed model
public ActionResult SomethingPosted(FormCollection empValues)
{
//
}
So, here's a better version of your code.
Your new view
#model Project.Models.ViewModels.SomeViewModel
#{
using (Html.BeginForm("SomeAction", "SomeController", new { id = Model.Id}))
{
foreach(var item in Model)
{
#Html.HiddenFor(item.Id)
<div class="grid_6">
#Html.TextAreaFor(item.Text, new { #style = "height:150px", #class = "grid_6 input" })
</div>
}
<div class="grid_6 alpha omega">
<input type="submit" value="Next" class="grid_6 alpha omega button drop_4 gravity_5" />
</div>
}
}
Your new action
[HttpPost]
public ActionResult SomeAction(int Id, string Text)
{
//do stuff with id and text
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
or
[HttpPost]
public ActionResult SomeAction(IEnumerable<ItemViewModel> SomeCollection) //can't use someviewmodel, because it doesn't (directly) *have* members called "Id" and "Text"
{
//do stuff with id and text
return PartialView("_SomeOtherView", new SomeOtherViewModel(id));
}
I have an IList in my model. Which i am displaying as radio buttons.
But when i submit the form the value is not correct and the model state is not valid and where the value for the selected radio button should be there is 'Count =0'
This the option in model:
[Display(Name = "My enquiry is regarding: *")]
public IList<Industry> A1_EnquiryRegarding { get; set; }
controller:
populate list:
Industry blank = new Industry();
blank.Id = 0;
blank.Name = "Other";
IList<Industry> industryList = manager.GetIndustries();
industryList.Insert(industryList.Count, blank);
EnquiryModel.A1_EnquiryRegarding = industryList;
html:
<td>
<div class="editor-label">
<b> #Html.LabelFor(m => m.A1_EnquiryRegarding)</b>
</div>
<div class="editor-field">
#foreach (var radiobutton in Model.A1_EnquiryRegarding) {
#Html.RadioButtonFor(m => m.A1_EnquiryRegarding, radiobutton.Name)
<label>#radiobutton.Name</label>
<br></br>
}
#Html.ValidationMessageFor(m => m.A1_EnquiryRegarding)
</div>
</td>
where am i goign wrong? why am i not getting the correct selected value back?
Edit:
[HttpPost]
public ActionResult EnquiryForm(Enquiry Enquiry)
{
When you post back, your collection of complex object is not recreated. Instead, there is only one string value passed with the selected value of the radio. Your model for the update action should only include one name.
Implement your radiolist as follows:
#foreach (var radiobutton in Model.A1_EnquiryRegarding) {
#Html.RadioButton("selectedIndustry", radioButton.Name);
}
All your radio buttons should have the same name, but different values. That way, when you call your Post action, you just search for parameter "selectedIndustry".
[HttpPost]
public ActionResult MyPostAction(string selectedIndustry) {
}
I am using paged list in a search actionresult to page my results. It returns events within a date range or category.The first page returned is fine, but the second never has any results. There are over 7 results that the query returns when I check a breakpoint. Here is my search controller;
public ActionResult Search(DateTime? q, DateTime? e, int? EventCategoryId, Event model)
{
var SearchEvents = db.Events
.OrderByDescending(r => r.start)
.Where(r =>
r.start >= q &&
r.end <= e &&
r.EventCategoryId == EventCategoryId ||
r.start >= q ||
r.EventCategoryId == EventCategoryId);
ViewBag.QValue = model.start;
ViewBag.EValue = model.end;
ViewBag.ListValue = model.EventCategoryId;
ViewBag.EventCategoryId = new SelectList(db.EventsCategories, "Id", "Name", ViewBag.ListValue);
var pageIndex = model.Page ?? 1;
var results = SearchEvents.ToPagedList(pageIndex, 4);
ViewBag.Names = results;
return View(results);
}
}
And here is my view;
#model IPagedList<RealKaac.Models.Event>
#{
ViewBag.Title = "SearchResults";
}
#using PagedList;
#using PagedList.Mvc;
#using System.Linq;
<div id="Content">
<h2>SearchResults</h2>
<div id="EventSearch">
#using (Html.BeginForm("Search", "Event"))
{
<p class="EventPar">Start Date: </p>
<input id ="datepicker" type ="text" name ="q" value="#ViewBag.QValue" />
<p class="EventPar">End Date: </p>
<input id ="datepickerend" type ="text" name ="e" value ="#ViewBag.EValue" />
<p class="EventPar">Category:</p>
#Html.DropDownList("EventCategoryId")
<input id="EventButton" style="padding:1px;" type ="submit" name ="Search" value = "Search" />
}
</div>
<div id="IndexEvents">
#foreach (var item in Model)
{
<div class ="event">
<div class="eventname">
#*<p>#Html.DisplayFor(modelItem => item.Name)</p> *#
</div>
<div class = "etitle">
<p>#Html.DisplayFor(modelItem => item.title)</p>
</div>
<div class="eventsdesc">
<p>#Html.DisplayFor(modelItem => item.EventDescription)</p>
</div>
<ul class ="datelink">
<li style="margin-right:120px;">This event starts on the
#Html.DisplayFor(modelItem => item.start) and ends on the
#Html.DisplayFor(modelItem => item.end)</li>
<li>Click #Html.DisplayFor(modelItem => item.EventWebsite) for more info.</li>
</ul>
</div>
}
</div>
</div>
#Html.PagedListPager((IPagedList)ViewBag.Names, page => Url.Action("Search", new { page }))
I'm fairly new at this so I'm probably missing something small, any advice is greatly appreciated.
Thanks.
The Url.Action has only one parameter to it (the page #), which doesn't match the controller's method. It isn't providing access to any of the other parameters (such as the start/end dates). So, the second "page" query for the next 4 Events likely isn't working. If you created a SearchModel that had the necessary properties on it, you should easily be able to send the values from one page to the next.
public ActionResult Search(SearchModel search) {
//... (your search code)
// ... then, before returning the View,
ViewBag.SearchParameters = search;
return View(results);
where SearchModel has properties like:
public class SearchModel {
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int Page? { get; set; }
// etc.
}
Then, you could:
#Html.PagedListPager((IPagedList)ViewBag.Names, page => Url.Action("Search", new SearchModel { search = ViewBag.SearchParameters }))
Or you could just use named parameters:
#Html.PagedListPager((IPagedList)ViewBag.Names, page => Url.Action("Search", new { page = page, q = ViewBag.QValue, /* ... etc. */ }))
I'm working on my first ASP.NET MVC 3 application and I've got a View that looks like this:
#model IceCream.ViewModels.Note.NotesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.TextBoxFor(m => m.Name)
foreach (var item in Model.Notes)
{
#Html.EditorFor(m => item);
}
<input type="submit" value="Submit"/>
}
And I have an EditorTemplate that looks like this:
#model IceCream.ViewModels.Note.NoteViewModel
<div>
#Html.HiddenFor(m => m.NoteID)
#Html.TextBoxFor(m => m.NoteText)
#Html.CheckBoxFor(m => m.IsChecked)
</div>
NotesViewModel looks like so:
public class NotesViewModel
{
public string Name { get; set; }
public IEnumerable<NoteViewModel> Notes { get; set; }
}
NoteViewModel looks like this:
public class NoteViewModel
{
public int NoteID { get; set; }
public System.DateTime Timestamp { get; set; }
public string NoteText { get; set; }
public bool IsChecked { get; set; }
}
The NotesViewModel is populated just fine when it is passed to the view. However when the submit button is clicked, the controller action handling the post has only the value for the Name property of the viewmodel. The Notes property - the list of notes that have been checked/unchecked by the user - is null. I've got a disconnect between the populating of those TextBoxFor and CheckBoxFor elements when the view is displayed and the ViewModel being sent back. Guidance on this?
SOLUTION
Thanks go to Mystere Man for setting me straight on this. As I understand it, essentially by changing my loop to
#Html.EditorFor(m => m.Notes)
changes the underlying HTML, which I understand provides for the proper model binding on the post. Looking at the resulting HTML, I see that I get the following generated for one of the Notes:
<div>
<input id="Notes_0__NoteId" type="hidden" value="1" name="Notes[0].NoteId">
<input id="Notes_0__NoteText" type="text" value="Texture of dessert was good." name="Notes[0].NoteText">
<input id="Notes_0__IsChecked" type="checkbox" value="true" name="Notes[0].IsChecked>
</div>
Which is different than this HTML generated by my original code:
<div>
<input id="item_NoteId" type="hidden" value="1" name="item.NoteId>
<input id="item_NoteText" type="text" value="Texture of dessert was good." name="item.NoteText" >
<input id="item_IsChecked" type="checkbox" value="true" name="item.IsChecked">
</div>
By looping through the Notes, the generated HTML essentially loses any references to the viewmodel's Notes property and while the HTML gets populated correctly, the setting of the checkbox values has no way to communicate their values back to the viewmodel, which I guess is the point of the model binding.
So I learned something, which is good.
You're a smart guy, so look at your view. Then, consider how the HTML gets generated. Then, consider how on postback the Model Binder is supposed to know to re-populate Notes based on the generated HTML.
I think you'll find that your HTML doesn't have enough information in it for the Model Binder to figure it out.
Consider this:
#EditorFor(m => Model.Notes)
Rather than the for loop where you are basically hiding the context from the EditorFor function.
And for those that just want the answer as a for loop:
#for (int x = 0; x < Model.Notes.Count(); x++) {
#Html.HiddenFor(m => m.Notes[x].NoteId)
#Html.EditorFor(m => m.Notes[x].NoteText)
#Html.EditorFor(m => m.Notes[x].IsChecked)
}
I am trying to set up an Edit view on which I have a text box and DropDownListFor. I have figured out a way to populate the DDLF, and the rendered and posted values are correct, but i cant seem to get the model to update properly.
The object i am trying to update is generated from LINQtoSQL, and in database it has foreign key column. In LINQtoSQL class that resulted in "Contains" relationship. I can get to ID property that represents the column in DB, and also the object that it represents.
zupanija = new Zupanija(); //object that needs to be updated
zupanija.Drzava; //object that i want to change to make the update
zupanija.DrzavaID; //Property linked to object that should change
Only way i have figured out to do the update is to get the value from DDLF and use it to get the object that i want to change like this:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var zupanija = repo.ZupanijaById(id);
var drzava = new repoDrzava().DrzavaById(Convert.ToInt32(collection["Zupanija.DrzavaID"]));
zupanija.Drzava = drzava;
}
Also when i try to update the ID field like this, then i get the folowing error:
zupanija.DrzavaID = Convert.ToInt32(collection["Zupanija.DrzavaID"]);
Error: throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
This seems to me that it is very lousy way to do this, and i am trying to get UpdateModel to work.
I have found the solution while looking for something else, in blog by Joe Stevens:
Using Controller UpdateModel when using ViewModel
The catch is in following: When view model is used then to correctly bind the properties it is necessary to "instruct" the UpdateModel helper how to find the actual class we wish to update.
My solution required to modify
UpdateModel(zupanija); to UpdateModel(zupanija,"Zupanija");
Because i was using a ViewModel class that contained couple properties along with the main data class i wanted to update.
Here is the code, i hope it helps to understand:
public class ZupanijaFVM
{
public IEnumerable<SelectListItem> Drzave { get; private set; }
public Zupanija Zupanija { get; private set; }
...
}
// From Controller
//
// GET: /Admin/Zupanije/Edit/5
public ActionResult Edit(int id)
{
var zupanija = repo.ZupanijaById(id);
return zupanija == null ? View("Error") : View(new ZupanijaFVM(repo.ZupanijaById(id)));
}
//
// POST: /Admin/Zupanije/Edit/5
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var zupanija = repo.ZupanijaById(id);
if (TryUpdateModel(zupanija, "Zupanija"))
{
repo.Save();
return RedirectToAction("Details", new { id = zupanija.ZupanijaID });
}
return View(new ZupanijaFVM(zupanija));
}
//From View:
#model VozniRed.Areas.Admin.Models.ZupanijeFVM
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Zupanija</legend>
#Html.HiddenFor(model => model.Zupanija.ZupanijaID)
<div class="editor-label">
#Html.LabelFor(model => model.Zupanija.Naziv)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Zupanija.Naziv)
#Html.ValidationMessageFor(model => model.Zupanija.Naziv)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Zupanija.Drzava)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Zupanija.DrzavaID, Model.Drzave)
#Html.ValidationMessageFor(model => model.Zupanija.DrzavaID)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
A dropdown list is represented by a <select> tag in an HTML form. A <select> contains a list of <option> tags each containing an ID and a text. When the user selects an option and submits the form the corresponding ID of this options is POSTed to the server. And only the ID. So all you can expect to get in your Edit POST action is the ID of the selected option. And all that UpdateModel does is use the request parameters that are sent and convert them to a strongly typed object. But because all that is a POSTed is a simple ID that's all you can get. From there on you have to query the datastore using this ID if you want to obtain the corresponding model. So you cannot get something that is not existing.