I'm working on an application that has the option of using a VIN decoder to get car information. You can type in the VIN, or you can select through dropdowns, manufacturer, year, make, model, etc.
The manufacturer dropdown is the only thing initialized on the page, with no selected value. Selecting a manufacturer will find all available years for that manufacturer and return the list of years, as well as returning the list of manufacturers and the one that was selected. Selecting a year will then return list of available manufacturers, years and makes, with the selected manufacturer and selected year both identified, and so on down through the application.
This workflow works fine and all of my dropdowns display correctly. When entering a VIN though, I have selected values for each, and still find the lists of available options, and render the page exactly as I would if someone had selected options by hand up to that point. All of the dropdowns render correctly with the proper selected attributes when I do this except the manufacturer.
I have tried to isolate it as much as possible, and have stripped out everything else, and I have this now:
View:
#model My_Project.Models.Data
#using System.Web.Helpers
#using (Html.BeginForm("Temp", "Home", FormMethod.Post, new { id = "formIndex" }))
{
<div>
VIN:
#Html.TextBox("vin", Model.VIN) <button type="submit">Go!</button>
</div>
<div>
Manufacturer: (#Model.ManufacturerId)
#Html.DropDownListFor(m => m.ManufacturerId, Model.Manufacturers, new { style = "width: 175px;" })
</div>
}
Model:
namespace My_Project.Models
{
[Serializable]
public class Data
{
public string VIN { get; set; }
public int ManufacturerId { get; set; }
public SelectList Manufacturers { get; set; }
}
}
Controller:
public ActionResult Temp()
{
Data model = new Data
{
Manufacturers = DBAccess.getManufacturers()
};
Session["ModelData"] = model;
return View(model);
}
[HttpPost]
public ActionResult Temp(Data newData)
{
Data oldData = Session["ModelData"] as Data;
oldData.ManufacturerId = 20;
Session["ModelData"] = oldData;
return View(oldData);
}
If I set the ManufacturerId in Temp(), then my dropdown list renders correctly with whatever manufacturer selected. If it is set in the post response though, the dropdown list renders with all the correct options, but without the correct manufacturer selected. And if you look in the view, I actually have it displaying the manufacturerId to me to make sure it is getting the data correctly, and manufacturerId is set to a value that is in the list, but it is not selected.
I can't figure out what the difference is between these two instances given that the model used in rendering the view looks identical. On top of that, if the post method is called by selecting the manufacturer (I have that functionality stripped out at this point), it would return the same model but also render correctly.
What would be causing this to not render correctly on the return from the post?
If you need to set a value from controller post method, I think you need to update the ModelState with the new value. I think it is because even if you pass updated model to the view, ModelState is still holding the old value.
Try this:
[HttpPost]
public ActionResult Temp(Data newData)
{
Data oldData = Session["ModelData"] as Data;
oldData.ManufacturerId = 20;
Session["ModelData"] = oldData;
//Update model state with new ManufacturerId here
CultureInfo myCulture = new System.Globalization.CultureInfo("en-GB");
ModelState.SetModelValue("ManufacturerId",
new ValueProviderResult((object)oldData.ManufacturerId,
oldData.ManufacturerId.ToString(), myCulture));
return View(oldData);
}
Related
I have an MVC 5 site...
A story can be optionally associated with a PlaceId. A blank placeId is also valid. A place can be associated with more than one story.
Models
public class Place
{
public Guid Id { get; set; }
public string PlaceName { get; set; }
}
public class Story
{
public Guid Id { get; set; }
public Guid? PlaceId { get; set; }
public string StoryName { get; set; }
}
I have several requirements.
When editing the story I would like a drop down list of all places to
be displayed - with the associated place (if any) - selected.
I want to use the strongly typed DropDownListFOR (as opposed to
DropDownList).
I want to add a "No Associated Place" which will be
selected if PlaceId is null (and should pass null back to the model).
I want to add a css class = "form-control" to the DropDownListFor.
Controller (Get)
public ActionResult Edit(Guid Id)
{
// Get Story
var story = StoryRepo.SelectArticle(Id);
// Put select list of all places in ViewBag
ViewBag.PlaceId = new SelectList(PlaceRepo.SelectAll(), "Id", "PlaceName", new { Id = story.PlaceId });
// Return view
return View(story);
}
In View
#Html.DropDownListFor(x => x.PlaceId, (IEnumerable<SelectListItem>)ViewBag.PlaceId, "No Associated Place",
new { #class = "form-control" })
This populates the dropdown list fine, and when you select an item the PlaceId is in the model bound to the controller. However - it does not select the existing PlaceId when the view loads.
Shouldn't the final parameter in the ViewBag.PlaceId line new { Id = story.PlaceId } - cause this selection?
I can't find anything on this specific issue online - and can find little about how to bind a dropdown to a strongly typed edit view in the way I require.
How can I make it select the correct item? (also any improvements on how I am doing this also appreciated).
Thanks.
I think you need to change the following code
// Put select list of all places in ViewBag
ViewBag.PlaceId = new SelectList(PlaceRepo.SelectAll(), "Id",
"PlaceName", new { Id = story.PlaceId });
to become
// Put select list of all places in ViewBag
ViewBag.PlaceId = new SelectList(PlaceRepo.SelectAll(), "Id",
"PlaceName", story.PlaceId);
Definition of the SelectedList from msdn
SelectList(IEnumerable, String, String, Object): Initializes a new
instance of the SelectList class by using the specified items for the
list, the data value field, the data text field, and a selected value.
here a working demo
updated demo
Hope this will help you
Hi i am working in one mvc project where i am stuck on very small issue of dropdown selection.
There are 2 dropdown.1st used for country name and 2nd one is used for tourism type.
And one button used for search according to dropdown selection.
Here is the link of the website:
www.ourplanettravel.com.au/
If we select "Tasmania" from 1st dropdown and "Tours & Cruises" from 2nd dropdown and click search button then,2nd dropdown looses its value(it shows --Choose Tourism Type-- ) only in this case, while in other options it works perfectly.
Here is the code that i am using:
<select id='TourismType' name="TourismType">
<option value=''>--Choose Tourism Type--</option>
{{if $item.data.oTourismType}}
{{each $item.data.oTourismType}}
<option value='${Text}'>${Text}</option>
{{/each}}
</select>
{{/if}}
Please suggest me where i am wrong.
It looks like the dropdown retains its value in the current instance of the view (hence "Tours & Cruises" is part of the query string after you search and is included in your search results), but doesn't retain its value in the dropdown itself. Basically the model that gets passed to the view that will be displayed when you go to the next page does not have the selected tourism type bound. You can rebind the property in the controller.
However, in general, I would recommend using Razor helpers to do your model binding rather than an explicit tag, which may avoid this issue in the first place.
Generic example with a single dropdown...
Model
public class YourModel {
public int SelectedTourismType { get; set; }
public IEnumerable<TourismType> TourismTypes { get; set; }
}
Tourism type class:
public class TourismType {
public int TourismTypeID { get; set; }
public string DisplayName { get; set; }
// other properties if applicable
}
View:
#model YourModel
// Your form or whatever here...
#Html.DropDownListFor(m => m.SelectedTourismType,
new SelectList(Model.TourismTypes, "TourismTypeID", "DisplayNameName"),
"Select an option") // Default text before the user has selected an option
Controller:
public ActionResult YourAction()
{
YourModel model = new YourModel();
model.TourismTypes= new List<TourismType> {
new TourismType { TourismTypeID = 1, Value = "Tours & Cruises" },
new TourismType { TourismTypeID = 2, Value = "Some other type name" }
}
return View("YourViewName", model);
}
This should work as long as you are passing through the same model when you refresh the view on the following page. Of course you would need to modify it to include both dropdowns, one dependent on the other.
I have read somewhat on the post-redirect-get design pattern and I'm not sure if it works for my purpose as what I have is an MVC site which is design to look like an application, I have multiple dropdowns on the page which all bind to an integer array as below in my controller:
[HttpPost]
public ViewResult ResponseForm(PartyInvites.Models.GuestResponse response, int[] SelectedCustomer)
{
return View(response); // works but resets all my selected dropdowns
// return View(); // gives an error that it can't rebind items in view
}
My View:
#foreach (Schedule sched in Model.Schedules)
{
#Html.DropDownList("MySelectedCustomer", new SelectList(sched.Customers, "Id", "FirstName"), "Select A Customer", new { #class = "SelectedCustomer" })
}
The GuestResponse:
public class GuestResponse
{
[Required(ErrorMessage = "You must enter your name")]
public string Name { get; set; }
public string SomeString = "someString";
public string Email { get; set; }
public string Phone { get; set; }
public bool? WillAttend { get; set; }
public int SelectedSchedule = 0;
public int SelectedCustomer = 0;
public List<Schedule> Schedules
{
get
{
return new List<Schedule>() { new Schedule() { ScheduleName = "party1", ScheduleId = 1 }, new Schedule() { ScheduleId = 2, ScheduleName = "party2" } };
}
set
{
Schedules = value;
}
}
}
The SelectCustomer property is a property on the GuestResponse class. All the dropdowns are bound and if I change a few they bind nicely to the int[] SelectedCustomer collection. However I want to return my View back (so it does nothing essentially) but this resets all the dropdowns to their original state as the response was never fully bound because there was multiple dropdowns and MVC couldn't model bind to it. What it the best way of doing this so it maintains state so to speak?
The correct way to handle this is to use a view model instead of passing your domain models to the view.
But if you don't want to follow good practices you could generate your dropdowns like this as a workaround:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownList(
"MySelectedCustomer[" + i + "]",
new SelectList(
Model.Schedules[i].Customers,
"Id",
"FirstName",
Request["MySelectedCustomer[" + i + "]"]
),
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
The correct way is to have a property of type int[] SelectedCustomers on your view model and use the strongly typed version of the DropDownListFor helper:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownListFor(
x => x.SelectedCustomers,
Model.Schedules[i].AvailableCustomers,
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
and your POST controller action will obviously take the view model you defined as parameter:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
// The model.SelectedCustomers collection will contain the ids of the selected
// customers in the dropdowns
return View(model);
}
And since you mentioned the Redirect-After-Post design pattern, this is indeed the correct pattern to be used. In case of success you should redirect to a GET action:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid => redisplay the view so that the user can fix
// the errors
return View(model);
}
// at this stage the model is valid => you could update your database with the selected
// values and redirect to some other controller action which in turn will fetch the values
// from the database and correctly rebind the model
GuestResponse domainModel = Mapper.Map<GuestResponseViewModel, GuestResponse>(model);
repository.Update(domainModel);
return RedirectToAction("Index");
}
Note: I'm first addressing why it's not binding anything, but that's not addressing the array issue, which I will get to afterwards. Where most people go wrong with MVC is that they do not take advantage of the built-in features of MVC to deal with these situations. They insist on doing foreach's and manually rendering things, but do not take into account the collection status.
The reason why the values are reset is because you are using Html.DropDownList() rather than Html.DropDownListFor(), and you are renaming the posted property name to a different name than your model property name.
You could simply change it to this:
#Html.DropDownList("SelectedCustomer", // note the removal of "My"
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
However, you would not have had this issue, and saved yourself a huge headache if you had just used the strongly typed version.
#Html.DropDownListFor(x => x.SelectedCustomer,
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
As for the Array, you should use an EditorTemplate for Schedules, and in that EditorTemplate you simply create your html as if it were a single item. That's the great thing about Editor/DisplayTemplates is that they automatically deal with collections.
Create a folder in your Views/Controller folder called EditorTemplates. In that folder, create an empty file called Schedule.cshtml (assuming Schedules is a List or array of Schedule). In that, you have code to render a single schedule.
EDIT:
Darin brings up a good point. I would make a small change to the model and add a Selected property to both Schedule and GuestResponse, then you can use Linq to return the selected schedule and it would simplify things.
EDIT2:
You some conflicts between the problem you've described and the code you've shown. I suggest you figure out exactly what you're trying to do, since your code does not really reflect a viable model for this.
I am a new to ASP.NET MVC, I am developing an application. I want to bind the data in the drop down list in create view.
How to bind the data in the drop down? I have go through many question and answers here...
I have seen usually everyone suggested to use List<SelectListItem> what is its purpose?
Do I need to use ViewModel while binding the data to drop down list?
Can I get simple example where data get bind in the dropdown using viewbag?
I have created a list in controller
List<string> items = new List<string>();
and I want to pass this list to view using viewbag and simply want to bind to drop down list.
How to do this ?
I'd suggest using a ViewModel as it makes interaction with user input so much easier. Here's an example of how you might bind data from your ViewModel to a drop down in your View. First, the ViewModel:
public class CrowdViewModel
{
public string SelectedPerson { get; set;}
public IEnumerable<SelectListItem> People { get; set; }
}
So yes, you're right - use a collection of SelectListItems. I'm guessing in your case, the SelectListItem's Value and Text property will be the same. You could turn your List into IEnumerable like this:
[HttpGet]
public ActionResult Home()
{
// get your list of strings somehow
// ...
var viewModel = new CrowdViewModel
{
People = items.Select(x => new SelectListItem { Text = x, Value = x })
}
return View(viewModel);
}
Now you need to bind that ViewModel's property to the DropDown on your view. If you're using the Razor ViewEngine, the code will look something like this:
#model MyApp.ViewModels.CrowdViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(model => model.SelectedPerson, Model.People)
}
Now when you post that form, MVC will bind the selected value to the ViewModel's SelectedPerson property!
[HttpPost]
public ActionResult Home(CrowdViewModel viewModel)
{
// viewModel.SelectedPerson == whatever the user selected
// ...
}
Easy as that!
Update:
If you really want to use the ViewBag (don't do it), you can pass your list through from your Controller action like so:
[HttpGet]
public ActionResult Home()
{
ViewBag.People = new List<string> { "Bob", "Harry", "John" };
return View();
}
And then create a SelectList on your View:
#Html.DropDownList("SelectedPerson", new SelectList(ViewBag.People, Model))
I have an application that shows a grid/table of questions and each question has a status dropdown. There are around 1-200 questions and each status drop down has about 50 choices that are the same for every row of the grid.
My controller passes the following model to a view:
IEnumerable<Question.Grid>
Then in my view I have the following code that prints out the detail lines of a grid table:
<tbody class="grid">
#if (Model != null) {
foreach (var item in Model) {
#Html.DisplayFor(model => item, "QuestionDetail")
}
}
</tbody>
Each of the grid lines has a status dropdown and I would like to pass the data for the dropdown (same for every row) to the QuestionDetail view. What's the best way for me to send this additional information so that in my view I can have something like the following:
#Html.DropDownList("Question.Status", Status, new { id = "StatusID"})
First of all, don't pass IENumerable of view models. Rather define one that has IEnumerable as property like this:
public class EnumViewModel
{
public IEnumerable<Question.Grid> Questions { get; set; }
public int MyAdditionalFieldIWantedToPassAlong { get; set; }
}
public class Question.Grid
{
public string MyExistingFields { get; set; }
// as many as you had
public string MyAdditionalFields { get; set; }
// as much as you want
}
and have your view receive one model instead of list of models as #model EnumViewModel.
At this point it must be very easy for you to add any additional information as your heart pleases inside the EnumViewModel should this information belong there. If its more specific to Questions,put it there and enjoy your items inside every model in the list containing that StatusID.
Think of it as just a container for you to hold data your view needs to
display and
post back to controller
Hope this helps.