Getting some very annoying behaviour from my MVC3 app. So I've got a model with 3 values:
[DisplayName("Airlines ")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode1 { get; set; }
[DisplayName("Airlines")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode2 { get; set; }
[DisplayName("Airlines")]
[StringLength(2, ErrorMessage = "Airline codes can only be 2 characters in length")]
public string AirlineCode3 { get; set; }
Now these are populated from a DropDowList so I'm popping the DropDownListItems in the ViewBag and rendering them in the view such:
#Html.LabelFor(l => l.AirlineCode1) <span>(select/enter code in box on right)</span>
<div id="airportCode1">
#Html.DropDownListFor(d => d.AirlineCode, ViewBag.AirLines as List<SelectListItem>) <input type="text" maxlength="2" value="#Model.AirlineCode1" />
</div>
<div id="airportCode2" style="#Model.AirlineCode2Style">
#Html.DropDownListFor(d => d.AirlineCode, ViewBag.AirLines2 as List<SelectListItem>) <input type="text" maxlength="2" value="#Model.AirlineCode2" />
</div>
<div id="airportCode3">
#Html.DropDownListFor(d => d.AirlineCode3, ViewBag.AirLines3 as List<SelectListItem>) <input type="text" maxlength="2" value="#Model.AirlineCode3" />
</div>
So my controller looks like:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines;
ViewBag.AirLines3 = airLines;
Now in some circumstances I want to prepoulate AirLineCode in the model. so I set the model value in the controller. This resulted in some odd behaviour. Suddenly all my DropDownLists contained the prepopulated value!
Checked the model, value only set in AirLineCode1. Checked the ViewBag, no selected SelectListItems. So I figured the ViewBag must be maintaining a reference. So I changed my code to:
ViewBag.AirLines = PopulateAirlines(user);
ViewBag.AirLines2 = PopulateAirlines(user);
ViewBag.AirLines3 = PopulateAirlines(user);
Boom, fixed. Problem is PopulateAirlines is an expensive process!
Problem is the ViewBag appears to be maintaing a reference between the 3 SelectListItem Lists. How do I stop it doing this and still only make one call to PopulateAirlines(user);?
I tried the below code and this completely blew up:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines.Select(s => new SelectListItem() { Text = s.Text, Value = s.Value, Selected = s.Selected });
ViewBag.AirLines3 = airLines.Select(s => new SelectListItem() { Text = s.Text, Value = s.Value, Selected = s.Selected });
with the error:
There is no ViewData item of type 'IEnumerable' that
has the key 'AirlineCode2'.
What??!
It may be because of in this code:
IEnumerable<SelectListItem> airLines = PopulateAirlines(user);
ViewBag.AirLines = airLines;
ViewBag.AirLines2 = airLines;
ViewBag.AirLines3 = airLines;
all the ViewBag properties are referring to the same list. A change to the list will therefore reflect when referencing any of the properties. When you call the PopulateAirlines() method you're creating 3 different lists.
You'll need make three separate lists but only create the first using the PopulateAirlines method and then clone it twice. That will not be as expensive.
Related
I have a dilemma that I hope someone can help me with. During the Edit Get stage of loading and updating a page before it is loaded, I store the checkboxes that are checked like this:
foreach (var course in courses.Select((x, i) => new { Data = x, Index = i }))
{
#: <td>
<br /><br /><br />
<input type="checkbox" id="checkbox" name="selectedCourses" value="#course.Data.CourseID"
#(Html.Raw(course.Data.Assigned ? "checked=\"checked\"" : "")) />
</td>
bool theNewString = course.Data.Assigned;
String a = theNewString.ToString();
assignedCourses.Add(a);
}
At the top of the view I defined the list to store the data like this so that I can be sent in the BeginForm:
#{ List<String> assignedCourses = new List<String>(); }
Then I try to send the list to the Edit POST like this:
#using (Html.BeginForm(new { assigned = assignedCourses }))
In my controller signature looks like this:
[HttpPost]
public ActionResult Edit(int id, List<String> assigned)
The list is loaded at the end of the get stage but the data in the list does not go through to Edit POST. My question is how do I get the list that I created at the end of the edit get stage to persist so that I can use it in my post?
Thanks for any ideas.
I'll try make this as simple as possible. So your Course model is something like this...
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public bool Assigned { get; set; }
}
...and you want to display some saved values on screen to edit. So starting at the controller, I've dummied some data and set one of the Assigned booleans to true so it is checked when the page loads.
public Controller Courses
{
public ActionResult Edit(int id)
{
var courses = new Course[] {
new Course() { Id = 1, Name = "maths", Assigned = true },
new Course() { Id = 2, Name = "english", Assigned = false },
new Course() { Id = 3, Name = "science", Assigned = false }
};
return View(courses);
}
Now, your page should expect a collection of these courses, so at the top of your page define the type that the View is expecting...
cshtml
#model IEnumerable<ExampleProject.Domain.Course>
...which you can then enumerate through in your View and create your checkboxes.
#using (Html.BeginForm("Edit", "Courses", FormMethod.Post))
{
#foreach(var item in Model)
{
<label for="#item.Id">#item.Name</label>
<input type="checkbox" name="courses" id="#item.Id" value="#item.Id" checked="#item.Assigned" />
}
<button type="submit">Save changes</button>
}
This will render the following html:
<label for="1">maths</label>
<input type="checkbox" name="courses" id="1" value="1" checked="checked">
<label for="2">english</label>
<input type="checkbox" name="courses" id="2" value="2">
<label for="3">science</label>
<input type="checkbox" name="courses" id="3" value="3">
Now you can check what you like and submit your form. In true http fashion your controller action will receive the values were checked on the View:
[HttpPost]
public ActionResult Courses(int id, int[] courses)
{
// courses will contain the values of the checkboxes that were checked
return RedirectToAction("Index"); // etc
}
Hopefully this helps. You can be a bit smarter and use the Html helpers for some more complicated binding (eg. Html.CheckBox, Html.CheckBoxFor) but this should get you going and it's clear to see what's going on.
Firstly, I'm very new to this so if this is a simple question answered elsewhere please just link me there; I don't want to waste anyone's time (I tried searching for a long time before posting).
So, moving on, I'm having some trouble updating information in my database (Using EntityFramework) in an MVC3 project. I have a profile controller (using static info and the nullable int right now for testing) that looks like this:
public ViewResult Edit(int? memberID)
{
Member member = repository.Members.GetMember(12985);
return View(new ProfileEditViewModel
{
MemberID = member.id,
FirstName = member.fname,
LastName = member.lname,
HomePhone = member.home_phone,
CellPhone = member.cell_phone,
Address = member.street,
City = member.city,
State = member.state,
Zip = member.zip,
Email = member.email,
Sex = member.sex,
MemberType = member.membertype,
TypeOfCar = member.typeofcar
});
}
I've then got a strongly typed view that starts like this:
#using (Html.BeginForm()) {
<div class="editor-label">
<label for="fname">First Name:</label>
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
and strings the form down for each attribute of my ViewModel except MemberID. It closes with
<p>
<input type="submit" value="Save" />
#Html.ActionLink("Cancel", "Summary")
</p>
Back in my controller I then have the complementary method:
[HttpPost]
public ActionResult Edit(ProfileEditViewModel memberViewModel)
{
Member member = repository.Members.GetMember(memberViewModel.MemberID);
if (ModelState.IsValid)
{
member.fname = memberViewModel.FirstName;
member.lname = memberViewModel.LastName;
member.home_phone = memberViewModel.HomePhone;
member.cell_phone = memberViewModel.CellPhone;
member.street = memberViewModel.Address;
member.city = memberViewModel.City;
member.state = memberViewModel.State;
member.zip = memberViewModel.Zip;
member.email = memberViewModel.Email;
member.sex = memberViewModel.Sex;
member.membertype = memberViewModel.MemberType;
member.typeofcar = memberViewModel.TypeOfCar;
repository.Members.UpdateMember(member);
return RedirectToAction("Summary");
}
else
{
//there is something wrong with the data
return View(member);
}
The problem is that when I try to update a member's information I get a "Sequence contains no elements" error, even though i know that the MemberID i specified is valid since i was able to get to the update page, and I know the UpdateMember method works correctly since it was tested fully before this part of the project began. Where am I going wrong with the update?
You're missing a
#Html.HiddenFor(m => m.MemberID)
in your view.
I'm sure this is easy, but maybe I haven't searched well ...
I want to know how to get results from a partial view back to the model and/or controller.
If the user enters a FirstName, Gender (from drop down) and Grade (from drop down), I only find then FirstName and Gender in the model. I want to know how to get the Grade from the drop down in the partial view all the way back into the model, so I can see it in the controller.
Please look for this question in the controller code:
What do I need to do to get the GradeLevel from the partial class to be here: <<<<<
Note: this is not the exact code. There may be small, insignificant typo's.
EDIT: Apparently you can't add a long comment, so I will add here:
Thank you, Tom and Mystere Man. Tom got me thinking as to why it doesn't work. I didn't think through the model binding. With the design I proposed, the HTML gets rendered and the Grade drop down has this id: "Grade". The property on the model I want to bind to is: "GradeLevelID". If I change the helper in the partial view to be #Html.DropDownList("GradeLevelID" ... it works perfectly.
But that is not a good solution. My idea was to abstract the partial view from the main view. Hard coding the name blows that! I did work up a slightly improved solution. In the main view, I change the #Html.Partial statement to pass the model property name to the partial. Like such:
#Html.Partial("GradeDropDown", (SelectList)Model.GradeSelectList, new ViewDataDictionary { { "modelPropertyName", "GradeLevelID" } })
Then I could change the partial view to say
#model System.Web.Mvc.SelectList
#Html.DropDownList((string)ViewData["modelPropertyName"], Model)
But that also seems like a goofy way to approach things. Thanks for the help. I'll look at EditorTemplates.
Here is my model:
public class RegisterModel{
public MemberRegistration MemberRegistration{
get{
if (HttpContext.Current.Session["MemberRegistration"] == null){
return null;
}
return (MemberRegistration)HttpContext.Current.Session["MemberRegistration"];
}
set{
HttpContext.Current.Session["MemberRegistration"] = value;
}
}
public string FirstName{
get{
return MemberRegistration.FirstName;
}
set{
MemberRegistration.FirstName = value;
}
}
public SelectList GenderSelectList{
get{
List<object> tempList = new List<object>();
tempList.Add(new { Value = "", Text = "" });
tempList.Add(new { Value = "M", Text = "Male" });
tempList.Add(new { Value = "F", Text = "Female" });
return new SelectList(tempList, "value", "text", MemberRegistration.Gender);
}
}
[Required(ErrorMessage = "Gender is required")]
public string Gender{
get{
return MemberRegistration.MemberPerson.Gender;
}
set{
MemberRegistration.MemberPerson.Gender = value;
}
}
public SelectList GradeLevelSelectList{
get{
List<object> tempList = new List<object>();
tempList.Add(new { Value = "", Text = "" });
tempList.Add(new { Value = "1", Text = "1st" });
tempList.Add(new { Value = "2", Text = "2nd" });
tempList.Add(new { Value = "3", Text = "3rd" });
tempList.Add(new { Value = "4", Text = "4th" });
return new SelectList(tempList, "value", "text", MemberRegistration.GradeLevel);
}
}
[Required(ErrorMessage = "Grade is required")]
public Int32 GradeLevel{
get{
return MemberRegistration.GradeLevel;
}
set{
MemberRegistration.GradeLevel = value;
}
}
}
Here is my main view:
#model RegisterModel
#using (Html.BeginForm())
{
<p class="DataPrompt">
<span class="BasicLabel">First Name:</span>
<br />
#Html.EditorFor(x => x.FirstName)
</p>
<p class="DataPrompt">
<span class="BasicLabel">Gender:</span>
<br />
#Html.DropDownListFor(x => x.Gender, Model.GenderSelectList)
</p>
<p class="DataPrompt">
<span class="BasicLabel">Grade:</span><span class="Required">*</span>
<br />
#Html.Partial("GradeDropDown", (SelectList)Model.GradeLevelSelectList)
</p>
<p class="DataPrompt">
<input type="submit" name="button" value="Next" />
</p>
}
Here is my partial view (named "GradeDropDown"):
#model System.Web.Mvc.SelectList
#Html.DropDownList("Grade", Model)
Here is my controller:
[HttpPost]
public ActionResult PlayerInfo(RegisterModel model)
{
string FirstName = model.Registration.FirstName;
string Gender = model.Registration.Gender;
>>>>> What do I need to do to get the GradeLevel from the partial class to be here: <<<<<
Int32 GradeLevel = model.Registration.GradeLevel;
return RedirectToAction("Waivers");
}
I don't even know why you are using a partial view. All you're doing is using one helper method, you could replace the partial view with the helper method in the view and it would be less code.
Second, you should be using Html.DropDownListFor() instead of Html.DropDownList(), then it will correctly name the html controls for you.
Just do this:
<p class="DataPrompt">
<span class="BasicLabel">Grade:</span><span class="Required">*</span>
<br />
#Html.DropDownListFor(m => m.GradeLevel, (SelectList)Model.GradeLevelSelectList)
</p>
try this to get the correct naming for the elements when they get posted.
On your main view
#Html.Partial("GradeDropDown", Model) //Pass the Model to the partial view
Here is your partial view (named "GradeDropDown"):
#model RegisterModel
#Html.DropDownList("Grade", (SelectList)Model.GradeLevelSelectList)
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. */ }))