Select Box Not Populated On View After Model State Is Invalid - asp.net-mvc

I'm creating a new .net core MVC project for people to use to submit proposals for presentation at a conference.
For the proposal submission form I'm using a binding model (AKA view model) to bind the user's input on the form.
Here is part of the code for the binding model class:
public class BaseSubmissionBindingModel
{
public BaseSubmissionBindingModel(){
}
[Required]
public int ConferenceId { get; set; }
[Required]
[StringLength(250)]
[Display(Name = "Title")]
public string SubmissionTitle { get; set; }
[Required]
[StringLength(1000)]
[Display(Name = "Abstract")]
public string SubmissionAbstract { get; set; }
public IEnumerable<SelectListItem> SubmissionCategoryItems { get; set; }
[Required]
[Display(Name = "Select the Submission Category ")]
public string SelectedSubmissionCategory { get; set; }
}
Note the field SubmissionCategoryItems - this field is used to populate a drop down select form control in the view. The data for this control comes from a database query and which submission categories to show depends on the conference id value.
In the controller class I get the submission categories for the conference id and create a List<SelectListItem> submissionCategoryItems with an SelectListItem object for each submission category.
I then create the BaseSubmissionBindingModel object and assign the submissionCategoryItems to the BaseSubmissionBindingModel IEnumerable<SelectListItem> SubmissionCategoryItems.
The controller then returns View(baseSubmissionBindingModel).
The view page for the user's input renders correctly and the Select Submission Category select box has the correct options for each submission category.
Below is the HTML on the create submission form
<div class="form-group">
<label asp-for="SelectedSubmissionCategory"></label>
<select asp-for="SelectedSubmissionCategory" asp-items="#Model.SubmissionCategoryItems">
<option value="" selected>--select--</option>
</select>
<span asp-validation-for="SelectedSubmissionCategory"></span>
</div>
My problem occurs if the user does not enter a required value on the form but then clicks on the submit button for the form.
My controller class has the CreateSubmission method below which gets called when the user clicks submit on the form
public IActionResult CreateSubmission(BaseSubmissionBindingModel baseSubmissionBindingModel) {
if (! ModelState.IsValid) {
return View("Index", baseSubmissionBindingModel);
}
Log.Information("Submission data provided is " + baseSubmissionBindingModel.ToString());
return View("Success", baseSubmissionBindingModel);
}
If the user submits the form without providing all the required data then the model's state is invalid. The initial view is returned to the user along with the validation error messages.
However the select box for the submission category no longer has any options for the submission categories retrieved from the database. So the user can no longer select a submission category.
How can I ensure the submission category select box still has its option elements if the user fails to initially provide all the required data but still submits the form?
Thank you for the help.
Bruce

When model state validation fails, you are returning to the same view, which has the SELECT tag helper. Your select tag helper is using the SubmissionCategoryItems property of the passed view model as the source collection to build the options for the SELECT element. Http is stateless.For your second call (The form submission call), it does not have any idea what you did in your previous call (you did setup these collection items in your GET action call). So you need to set the collection property value every time you return to a view which uses that collection to build the select element.
public IActionResult CreateSubmission(BaseSubmissionBindingModel model)
{
if (! ModelState.IsValid)
{
// Set the collection property value again
model.SubmissionCategoryItems = GetCategoryItems();
return View("Index", model);
}
//to do : do some thing useful
Log.Information("Submission data " + baseSubmissionBindingModel.ToString());
return View("Success", model);
}
private List<SelectListItem> GetCategoryItems()
{
var items = new List<SelectListItem>();
// TO DO : I hard coded 2 items. You can replace it with items from your database
items.Add(new SelectListItem() { Value = "1", Text = "Seattle" });
items.Add(new SelectListItem() { Value = "2", Text = "Detroit" });
return items;
}

Related

Show dropdownlist in the View

I am working on a project in which, i am getting the client names from database table using the HomeController>Index Action method.
I want to send this list to Index view and display this list in the dropdownlist.
Request you to please help me with the View accordingly as i am new to MVC.
Home Controller
public ActionResult Index()
{
var model = from c in
_mdlCntxtcls.clients
where (DateTime.Now<=c.End_Date)
select c;
return View(model);
}
Model
public class Client
{
public int ClientID { get; set; }
public string Client_Names { get; set; }
public DateTime Start_Date { get; set; }
public DateTime End_Date { get; set; }
}
Please help as early as possible
Thank you
You are passing a collection of Client objects to the view. So your view should be strongly typed to a collection of Client object to accept that as the (view) model data.
You can use the DropDownList html helper method to render a SELECT element from this view model data. You can create a SelectList object from this collection (your page model)
#model IEnumerable<YourNamespaceHere.Client>
#Html.DropDownList("StudentSelect",new SelectList(Model,"ClientID","Client_Names"))
This will render a SELECT element with name attribute value set to StudentSelect. Each options in the SELECT elemtn will have the ClientID as the value attribute value and Client_Names property value as the option text.
You can also use viewbag or viewdata for send list of Client from controller to view and then you can put it in dropdown list.
In Controller you can use like :
List<SelectListItem> ClientList = new List<SelectListItem>();
using (dbContext db = new dbContext())
{
var Clients = db.Client.ToList();
foreach (var i in Clients)
{
ClientList.Add(new SelectListItem { Text = i.Client_Name, Value = i.ClientID.ToString() });
}
}
ViewBag.ClientList = ClientList;
and in view side you can use that viewbag like :
#Html.DropDownListFor(x => x.Client, (IEnumerable<SelectListItem>)ViewBag.ClistList)

ASP.NET MVC 5 site - edit view not selecting item in DropDownListFor

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

dropdown selection issue in mvc

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.

Populate a dropdown list from db

I have an mvc 3 application with 2 tables in my entity framework.
PurchaseTable which was PurchaseID,PurchaseDate & ProductID I have another table called Product which contains ProductID and ProductName. creating a new view to insert a new purchase how do I change the textbox in the view for ProductID to be a dropdown bound by the ProductName in the Product table?
Create a ViewModel:
public class CreatePurchaseViewModel
{
public Purchase Purchase { get; set; }
public IEnumerable<SelectListItem> Products { get; set; }
public int SelectedProductId { get; set; }
}
Setup the View with the ViewModel:
[HttpGet]
public ActionResult CreateProduct()
{
var model = new CreatePurchaseViewModel
{
Products = repository.FindAllProducts().Select(x =>
new SelectListItem
{
Text = x.ProductName,
Value = x.ProductId
}
};
return View(model);
}
Then simply bind to the ViewModel and use the DropDownListFor HTML Helper:
<! -- other code to bind to Purchase, and then: -->
<%= Html.DropDownListFor(x => x.SelectedProductId, Model.Products) %>
Then when you submit the form, the SelectedProductId value in the model will be populated with the selected value from the dropdown list.
The answer is easier than it looks. Simply create a list:
myDropDown = new SelectList(db.Products, "Product_ID", "ProductName", idSelected);
The first parameter is your table ("Products" from the Entity Model), the second is the "id" that will be returned when selecting from the list (i.e SelectedValue), the third is the "text" that will be displayed in the list and the last parameter is the currently selected item.
Assuming your model is called MyTablesDB, then:
MyTablesDB db = new MyTablesDB();
For example:
public SelectList GetPullDown(object idSelected) {
myTablesDB myDB = new MyTablesDB();
return new SelectList(myDB.Products, "Product_ID", "Product_Name", idSelected);
}
Cheers,

ASP.NET MVC Html.DropDownList SelectedValue

I have tried this is RC1 and then upgraded to RC2 which did not resolve the issue.
// in my controller
ViewData["UserId"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
result: the SelectedValue property is set on the object
// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["UserId"])%>
result: all expected options are rendered to the client, but the selected attribute is not set. The item in SelectedValue exists within the list, but the first item in the list is always defaulted to selected.
How should I be doing this?
Update
Thanks to John Feminella's reply I found out what the issue is. "UserId" is a property in the Model my View is strongly typed to. When Html.DropDownList("UserId" is changed to any other name but "UserId", the selected value is rendered correctly.
This results in the value not being bound to the model though.
This is how I fixed this problem:
I had the following:
Controller:
ViewData["DealerTypes"] = Helper.SetSelectedValue(listOfValues, selectedValue) ;
View
<%=Html.DropDownList("DealerTypes", ViewData["DealerTypes"] as SelectList)%>
Changed by the following:
View
<%=Html.DropDownList("DealerTypesDD", ViewData["DealerTypes"] as SelectList)%>
It appears that the DropDown must not have the same name has the ViewData name :S weird but it worked.
Try this:
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
And then:
var list = new[] {
new Person { Id = 1, Name = "Name1" },
new Person { Id = 2, Name = "Name2" },
new Person { Id = 3, Name = "Name3" }
};
var selectList = new SelectList(list, "Id", "Name", 2);
ViewData["People"] = selectList;
Html.DropDownList("PeopleClass", (SelectList)ViewData["People"])
With MVC RC2, I get:
<select id="PeopleClass" name="PeopleClass">
<option value="1">Name1</option>
<option selected="selected" value="2">Name2</option>
<option value="3">Name3</option>
</select>
You can still name the DropDown as "UserId" and still have model binding working correctly for you.
The only requirement for this to work is that the ViewData key that contains the SelectList does not have the same name as the Model property that you want to bind. In your specific case this would be:
// in my controller
ViewData["Users"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["Users"])%>
This will produce a select element that is named UserId, which has the same name as the UserId property in your model and therefore the model binder will set it with the value selected in the html's select element generated by the Html.DropDownList helper.
I'm not sure why that particular Html.DropDownList constructor won't select the value specified in the SelectList when you put the select list in the ViewData with a key equal to the property name. I suspect it has something to do with how the DropDownList helper is used in other scenarios, where the convention is that you do have a SelectList in the ViewData with the same name as the property in your model. This will work correctly:
// in my controller
ViewData["UserId"] = new SelectList(
users,
"UserId",
"DisplayName",
selectedUserId.Value); // this has a value
// in my view
<%=Html.DropDownList("UserId")%>
The code in the previous MVC 3 post does not work but it is a good start. I will fix it. I have tested this code and it works in MVC 3 Razor C# This code uses the ViewModel pattern to populate a property that returns a List<SelectListItem>.
The Model class
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
The ViewModel class
using System.Web.Mvc;
public class ProductListviewModel
{
public List<SelectListItem> Products { get; set; }
}
The Controller Method
public ViewResult List()
{
var productList = new List<SelectListItem>();
foreach (Product p in Products)
{
productList.Add(new SelectListItem
{
Value = p.ProductId.ToString(),
Text = "Product: " + p.Name + " " + p.Price.ToString(),
// To set the selected item use the following code
// Note: you should not set every item to selected
Selected = true
});
}
ProductListViewModel productListVM = new ProductListViewModeld();
productListVM.Products = productList;
return View(productListVM);
}
The view
#model MvcApp.ViewModels.ProductListViewModel
#using (Html.BeginForm())
{
#Html.DropDownList("Products", Model.Products)
}
The HTML output will be something like
<select id="Products" name="Products">
<option value="3">Product: Widget 10.00</option>
<option value="4">Product: Gadget 5.95</option>
</select>
depending on how you format the output. I hope this helps. The code does work.
If we don't think this is a bug the team should fix, at lease MSDN should improve the document. The confusing really comes from the poor document of this. In MSDN, it explains the parameters name as,
Type: System.String
The name of the form field to return.
This just means the final html it generates will use that parameter as the name of the select input. But, it actually means more than that.
I guess the designer assumes that user will use a view model to display the dropdownlist, also will use post back to the same view model. But in a lot cases, we don't really follow that assumption.
Use the example above,
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
If we follow the assumption,we should define a view model for this dropdownlist related view
public class PersonsSelectViewModel{
public string SelectedPersonId,
public List<SelectListItem> Persons;
}
Because when post back, only the selected value will post back, so it assume it should post back to the model's property SelectedPersonId, which means Html.DropDownList's first parameter name should be 'SelectedPersonId'. So, the designer thinks that when display the model view in the view, the model's property SelectedPersonId should hold the default value of that dropdown list. Even thought your List<SelectListItem> Persons already set the Selected flag to indicate which one is selected/default, the tml.DropDownList will actually ignore that and rebuild it's own IEnumerable<SelectListItem> and set the default/selected item based on the name.
Here is the code from asp.net mvc
private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
IDictionary<string, object> htmlAttributes)
{
...
bool usedViewData = false;
// If we got a null selectList, try to use ViewData to get the list of items.
if (selectList == null)
{
selectList = htmlHelper.GetSelectData(name);
usedViewData = true;
}
object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string));
// If we haven't already used ViewData to get the entire list of items then we need to
// use the ViewData-supplied value before using the parameter-supplied value.
if (defaultValue == null && !String.IsNullOrEmpty(name))
{
if (!usedViewData)
{
defaultValue = htmlHelper.ViewData.Eval(name);
}
else if (metadata != null)
{
defaultValue = metadata.Model;
}
}
if (defaultValue != null)
{
selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}
...
return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}
So, the code actually went further, it not only try to look up the name in the model, but also in the viewdata, as soon as it finds one, it will rebuild the selectList and ignore your original Selected.
The problem is, in a lot of cases, we don't really use it that way. we just want to throw in a selectList with one/multiple item(s) Selected set true.
Of course the solution is simple, use a name that not in the model nor in the viewdata. When it can not find a match, it will use the original selectList and the original Selected will take affect.
But i still think mvc should improve it by add one more condition
if ((defaultValue != null) && (!selectList.Any(i=>i.Selected)))
{
selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}
Because, if the original selectList has already had one Selected, why would you ignore that?
Just my thoughts.
This appears to be a bug in the SelectExtensions class as it will only check the ViewData rather than the model for the selected item. So the trick is to copy the selected item from the model into the ViewData collection under the name of the property.
This is taken from the answer I gave on the MVC forums, I also have a more complete answer in a blog post that uses Kazi's DropDownList attribute...
Given a model
public class ArticleType
{
public Guid Id { get; set; }
public string Description { get; set; }
}
public class Article
{
public Guid Id { get; set; }
public string Name { get; set; }
public ArticleType { get; set; }
}
and a basic view model of
public class ArticleModel
{
public Guid Id { get; set; }
public string Name { get; set; }
[UIHint("DropDownList")]
public Guid ArticleType { get; set; }
}
Then we write a DropDownList editor template as follows..
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">
IEnumerable<SelectListItem> GetSelectList()
{
var metaData = ViewData.ModelMetadata;
if (metaData == null)
{
return null;
}
var selected = Model is SelectListItem ? ((SelectListItem) Model).Value : Model.ToString();
ViewData[metaData.PropertyName] = selected;
var key = metaData.PropertyName + "List";
return (IEnumerable<SelectListItem>)ViewData[key];
}
</script>
<%= Html.DropDownList(null, GetSelectList()) %>
This will also work if you change ArticleType in the view model to a SelectListItem, though you do have to implement a type converter as per Kazi's blog and register it to force the binder to treat this as a simple type.
In your controller we then have...
public ArticleController
{
...
public ActionResult Edit(int id)
{
var entity = repository.FindOne<Article>(id);
var model = builder.Convert<ArticleModel>(entity);
var types = repository.FindAll<ArticleTypes>();
ViewData["ArticleTypeList"] = builder.Convert<SelectListItem>(types);
return VIew(model);
}
...
}
The problems is that dropboxes don't work the same as listboxes, at least the way ASP.NET MVC2 design expects: A dropbox allows only zero or one values, as listboxes can have a multiple value selection. So, being strict with HTML, that value shouldn't be in the option list as "selected" flag, but in the input itself.
See the following example:
<select id="combo" name="combo" value="id2">
<option value="id1">This is option 1</option>
<option value="id2" selected="selected">This is option 2</option>
<option value="id3">This is option 3</option>
</select>
<select id="listbox" name="listbox" multiple>
<option value="id1">This is option 1</option>
<option value="id2" selected="selected">This is option 2</option>
<option value="id3">This is option 3</option>
</select>
The combo has the option selected, but also has its value attribute set. So, if you want ASP.NET MVC2 to render a dropbox and also have a specific value selected (i.e., default values, etc.), you should give it a value in the rendering, like this:
// in my view
<%=Html.DropDownList("UserId", selectListItems /* (SelectList)ViewData["UserId"]*/, new { #Value = selectedUser.Id } /* Your selected value as an additional HTML attribute */)%>
In ASP.NET MVC 3 you can simply add your list to ViewData...
var options = new List<SelectListItem>();
options.Add(new SelectListItem { Value = "1", Text = "1" });
options.Add(new SelectListItem { Value = "2", Text = "2" });
options.Add(new SelectListItem { Value = "3", Text = "3", Selected = true });
ViewData["options"] = options;
...and then reference it by name in your razor view...
#Html.DropDownList("options")
You don't have to manually "use" the list in the DropDownList call. Doing it this way correctly set the selected value for me too.
Disclaimer:
Haven't tried this with the web forms view engine, but it should work too.
I haven't tested this in the v1 and v2, but it might work.
I managed to get the desired result, but with a slightly different approach. In the Dropdownlist i used the Model and then referenced it. Not sure if this was what you were looking for.
#Html.DropDownList("Example", new SelectList(Model.FeeStructures, "Id", "NameOfFeeStructure", Model.Matters.FeeStructures))
Model.Matters.FeeStructures in above is my id, which could be your value of the item that should be selected.

Resources