I'm using ASP.NET MVC 3, and just ran into a 'gotcha' using the DropDownListFor HTML Helper.
I do this in my Controller:
ViewBag.ShippingTypes = this.SelectListDataRepository.GetShippingTypes();
And the GetShippingTypes method:
public SelectList GetShippingTypes()
{
List<ShippingTypeDto> shippingTypes = this._orderService.GetShippingTypes();
return new SelectList(shippingTypes, "Id", "Name");
}
The reason I put it in the ViewBag and not in the model (I have strongly typed models for each view), is that I have a collection of items that renders using an EditorTemplate, which also needs to access the ShippingTypes select list.
Otherwise I need to loop through the entire collection, and assign a ShippingTypes property then.
So far so good.
In my view, I do this:
#Html.DropDownListFor(m => m.RequiredShippingTypeId, ViewBag.ShippingTypes as SelectList)
(RequiredShippingTypeId is of type Int32)
What happens is, that the value of RequiredShippingTypeId is not selected in the drop down.
I came across this: http://web.archive.org/web/20090628135923/http://blog.benhartonline.com/post/2008/11/24/ASPNET-MVC-SelectList-selectedValue-Gotcha.aspx
He suggests that MVC will lookup the selected value from ViewData, when the select list is from ViewData. I'm not sure this is the case anymore, since the blog post is old and he's talking about MVC 1 beta.
A workaround that solves this issue is this:
#Html.DropDownListFor(m => m.RequiredShippingTypeId, new SelectList(ViewBag.ShippingTypes as IEnumerable<SelectListItem>, "Value", "Text", Model.RequiredShippingTypeId.ToString()))
I tried not to ToString on RequiredShippingTypeId at the end, which gives me the same behavior as before: No item selected.
I'm thinking this is a datatype issue. Ultimately, the HTML helper is comparing strings (in the Select List) with the Int32 (from the RequiredShippingTypeId).
But why does it not work when putting the SelectList in the ViewBag -- when it works perfectly when adding it to a model, and doing this inside the view:
#Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, Model.ShippingTypes)
The reason why this doesn't work is because of a limitation of the DropDownListFor helper: it is able to infer the selected value using the lambda expression passed as first argument only if this lambda expression is a simple property access expression. For example this doesn't work with array indexer access expressions which is your case because of the editor template.
You basically have (excluding the editor template):
#Html.DropDownListFor(
m => m.ShippingTypes[i].RequiredShippingTypeId,
ViewBag.ShippingTypes as IEnumerable<SelectListItem>
)
The following is not supported: m => m.ShippingTypes[i].RequiredShippingTypeId. It works only with simple property access expressions but not with indexed collection access.
The workaround you have found is the correct way to solve this problem, by explicitly passing the selected value when building the SelectList.
This might be silly, but does adding it to a variable in your view do anything?
var shippingTypes = ViewBag.ShippingTypes;
#Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, shippingTypes)
you can create dynamic viewdata instead of viewbag for each dropdownlist field for complex type.
hope this will give you hint how to do that
#if (Model.Exchange != null)
{
for (int i = 0; i < Model.Exchange.Count; i++)
{
<tr>
#Html.HiddenFor(model => model.Exchange[i].companyExchangeDtlsId)
<td>
#Html.DropDownListFor(model => model.Exchange[i].categoryDetailsId, ViewData["Exchange" + i] as SelectList, " Select category", new { #id = "ddlexchange", #class = "form-control custom-form-control required" })
#Html.ValidationMessageFor(model => model.Exchange[i].categoryDetailsId, "", new { #class = "text-danger" })
</td>
<td>
#Html.TextAreaFor(model => model.Exchange[i].Address, new { #class = "form-control custom-form-control", #style = "margin:5px;display:inline" })
#Html.ValidationMessageFor(model => model.Exchange[i].Address, "", new { #class = "text-danger" })
</td>
</tr>
}
}
ViewModel CompanyDetail = companyDetailService.GetCompanyDetails(id);
if (CompanyDetail.Exchange != null)
for (int i = 0; i < CompanyDetail.Exchange.Count; i++)
{
ViewData["Exchange" + i]= new SelectList(companyDetailService.GetComapnyExchange(), "categoryDetailsId", "LOV", CompanyDetail.Exchange[i].categoryDetailsId);
}
I was just hit by this limitation and figured out a simple workaround. Just defined extension method that internally generates SelectList with correct selected item.
public static class HtmlHelperExtensions
{
public static MvcHtmlString DropDownListForEx<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes = null)
{
var selectedValue = expression.Compile().Invoke(htmlHelper.ViewData.Model);
var selectListCopy = new SelectList(selectList.ToList(), nameof(SelectListItem.Value), nameof(SelectListItem.Text), selectedValue);
return htmlHelper.DropDownListFor(expression, selectListCopy, htmlAttributes);
}
}
The best thing is that this extension can be used the same way as original DropDownListFor:
#for(var i = 0; i < Model.Items.Count(); i++)
{
#Html.DropDownListForEx(x => x.Items[i].CountryId, Model.AllCountries)
}
There is an overloaded method for #html.DropdownList for to handle this.
There is an alternative to set the selected value on the HTML Dropdown List.
#Html.DropDownListFor(m => m.Section[b].State,
new SelectList(Model.StatesDropdown, "value", "text", Model.Section[b].State))
I was able to get the selected value from the model.
"value", "text", Model.Section[b].State this section the above syntax adds the selected attribute to the value loaded from the Controller
Related
I have a #foreach in my View that makes a table. Each row has two items within it's td. When I click my Edit button, the visible item's in a row disappear (DislayFor's) and the hidden items in the row appear (DropDownList)
View Code
<td class="col-md-3">
<span class="item-display">
<span style="font-size: 17px">
#Html.DisplayFor(modelItem => item.Movie.Name)
</span>
</span>
<span class="item-field">
#Html.DropDownList("movieID", item.Movie.Name)
</span>
</td>
By doing this I can select a new value in the DropDownList and then Save that change to my Database (then hiding the DropDownList and unhiding the DisplayFor.
Everything works fine, however I have an issue with the initally selected value, it appears twice with the initial value having an actual value of 0 (which relates to nothing in the DB).
Picture Example
QUESTION:
Right now my dropdown add's a value upon clicking Edit, the item initially selected has the correct name but it is given the index of 0 (which is invalid for my database).
I want to have the initially selected item to NOT be added, but rather to set the selector of the dropdown to the CORRECT INDEX of the appropriate item. I am not sure why it duplicates my selected item twice.
Controller Code
public ActionResult Index(string Filter, string searchString)
{
if (String.IsNullOrEmpty(searchString) || String.IsNullOrEmpty(Filter) || (Int32.Parse(Filter) == 0))
{
ViewBag.employeeID = new SelectList(db.Employees, "ID", "Name", );
ViewBag.movieID = new SelectList(db.Movies, "ID", "Name", initiallySelectedValue);
ViewBag.roleID = new SelectList(db.Roles, "ID", "RoleType");
var movieemployees = db.MovieEmployees.Include(m => m.Employee).Include(m => m.Movie).Include(m => m.Role);
return View(movieemployees.ToList().OrderBy(x => x.Employee.Name));
}
else
{
ViewBag.employeeID = new SelectList(db.Employees, "ID", "Name");
ViewBag.movieID = new SelectList(db.Movies, "ID", "Name");
ViewBag.roleID = new SelectList(db.Roles, "ID", "RoleType");
var parameter = Int32.Parse(Filter);
return View(db.MovieEmployees.Include(m => m.Employee).Include(m => m.Movie).Include(m => m.Role).Where(x => (parameter == 1 && x.Movie.Name.Contains(searchString)) || (parameter == 2 && x.Employee.Name.Contains(searchString)) || (parameter == 3 && x.Role.RoleType.Contains(searchString))).Distinct().ToList().OrderBy(x => x.Employee.Name));
}
}
Your understanding of the parameters for DropDownList isn't quite correct, but you're close! The second parameter for DropDownList (in your case item.Movie.Name) is adding an option label. If you replaced that with a hard-coded string that would serve as a good example of what it's doing (you would see that string as the first option of every select input).
It sounds to me like you want to delete that last parameter since it will only end up serving as a duplicate. Your code would simply look like this:
#Html.DropDownList("movieID")
The important part of your code is where you're building the object that you're storing in ViewData with the key movieID. You didn't post your controller code, but I imagine it looks something like:
var movies = movieRepository.GetAllMovies();
ViewData["movieID"] = new SelectList(movies, "Name", "Id", initiallySelectedValue);
Where Name and Id are the names of properties on the movie object and initiallySelectedValue is rather self explanatory.
Edit
Here is an example of how I would go about solving your problem:
Controller
public ActionResult Index() {
//Get all the possible movies that can be selected
var movies = movieRepository.GetAllMovies();
//Get a list of employees with their related favorite movie record
var employeesWithFavoriteMovie = movieRepository.GetEmployeesWithMovie();
var employeeModels = new List<EmployeeModel>();
//Iterate through the list of employees and their favorite movie, and build the model
foreach (var employeeWithFavoriteMovie in employeesWithFavoriteMovie) {
employeeModels.Add(new EmployeeModel() {
FirstName = employeeWithFavoriteMovie.FirstName,
FavoriteMovieId = employeeWithFavoriteMovie.Movie.Id,
MovieSelectList = new SelectList(movies, "Name", "Id", employeeWithFavoriteMovie.Movie.Id)
});
}
return View(employeeModels);
}
View
#model IEnumerable<WebApplication1.Controllers.EmployeeModel>
#foreach (var employeeModel in Model) {
#Html.DropDownList("Test", employeeModel.MovieSelectList)
}
Notice how a SelectList was built for each employee and that each list is then populated with that employees current favorite movie id. This will now put you in a position to have a properly built SelectList for each employee.
#Html.DropDownListFor(model => model.classmasterd_HCF[i].TD_TEACHER, new SelectList(Model.Teacher, "ParamKey", "ParamValue", Model.classmasterd_HCF[i].TD_TEACHER) as SelectList, new { #class = "form-control input-sm DtlField EditableCtrl", #style = "min-width:100%;", #disabled = "disabled" })
where Teacher in Model.Teacher is a model with code in paramKey and description in paramvalue. Selected value saved in TD_TEACHER field
I want to following html code using asp.net mvc 3 razor html helper:
<input type="text" .... . placeholder="Login" data-icon="user" />
I have try this one:
#Html.TextBoxFor(m => m.UserName, new { placeholder = "Login", data-icon = "user" })
or
#Html.TextBoxFor(m => m.UserName, new { placeholder = "Login", #data-icon = "user" })
Displayed Error:
Invalid anonymous type members declaration.
This might due to dash in data-icon not taken as attributes. How could I add data-icon attributes in text box field.
try this
#Html.TextBoxFor(m => m.UserName, new { placeholder = "Login", data_icon = "user" })
Yes, you can't write like that but you can write your own Extension to solve this problem. Here is the sample code:
public static MvcHtmlString MyInput(this HtmlHelper htmlHelper, string name, string value, string icon)
{
var attrs = new Dictionary<string,object>();
attrs.Add("data-icon", icon);
return htmlHelper.TextBox(name, name, value, attrs);
}
Or you can also use in razor like this:
#{
var attrs = new Dictionary<string, object>();
attrs.Add("placeholder","Login");
attrs.Add("data-icon","user");
}
#Html.TextBoxFor(m => m.UserName, attrs)
Plz don't forget to mark it's right answer if it helps you :-)
This is really the same as vNext's second alternative, but if you prefer to write it in-line:
#Html.TextBoxFor(m => m.UserName, new Dictionary<string, object> { { "placeholder", "Login" }, { "data-icon", "user" } })
I'm building my first MVC(3) application which manages snippets.
Obviously I have an edit page for a snippet, which has a programming language (0.* to 0.1).
Now my question is, how can I build a listbox with all the present programming languages to show on my edit page for a snippet?
I think I can create a ViewModel and populate that with all the values by querying them manually and passing them as a list, but I have a feeling that the people who built MVC have a tidier solution to this? I have "Pro MVC 3" and "Pro Entity Framework" at hand by Apress but can't seem to find an answer to this.
When I try:
#Html.EditorFor(model => model.Language or model.Language.Name)
I get errors.
Thanks in advance, any help is appreciated!
I think I can create a ViewModel
Straight to the point! That's exactly what I would recommend you doing.
But if you want to stick with your domain models you could do some adaptations in the view in order to be able to use the ListBox helper (totally unrecommended solution, but since you asked for something more tidier according to your criteria):
#model Snippet
#Html.ListBox(
"SelectedIds",
new MultiSelectList(
Model.Languages.Select(l => new { Value = l.ID.Tostring(), Text = l.Name }),
"Value",
"Text"
)
)
Here is what I do in similar cases:
1) Create a View Model for editing language
public class LanguagesModel
{
public IEnumerable<int> SelectedLanguageIds { get; set; }
public MultiSelectList languages { get; set; }
}
2) Populate the model in the controller:
public ActionResult Index()
{
List<Language> languages = _languageService.GetLanguages();//however you get the languages from your datasource
var temp = new MultiSelectList(
languages.Select(l => new { Value = l.LanguageId.ToString(), Text = l.Name }),
"Value",
"Text");
LanguagesModel model = new LanguagesModel();
model.languages = temp;
return View(model);
}
3) Create a view that uses the DropDownListFor helper:
#model LanguagesModel
#using (Html.BeginForm(Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })))
{
#Html.ValidationSummary(true)
<fieldset>
#Html.ListBoxFor(m => m.SelectedLanguageIds, Model.languages)
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
be carefule to replace "Index" and "Home" by the Action and Controllers that you are actually using.
Edit Changed code to use ListBox instead of DropDownList. Inspired by Darin Dimitrov's answer
I know on the Razor View file, we can do something like this
#Html.TextBox("username", null, new { maxlength = 20, autocomplete = "off" })
However, I am hoping to create a model for the MVC that can be used to create a form with explicitly defined the size and max length of the textboxes. I try [StringLength(n)] on top of the properties of the model, but that seems to only do the validation ratherh set the size of the textbox.
Is there anyway that we can define the length of the text field as a data annotation on top of a property of a model?
So ultimately, we could just create the whole form by using razor to map to a model rather than explicitly pick up the model properties one by one in order to set the textbox size.
Here is a outline of a custom helper that uses StringLengthAttribute.
public class MyModel
{
[StringLength(50)]
public string Name{get; set;}
}
public MvcHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
var attributes = new Dictionary<string, Object>();
var memberAccessExpression = (MemberExpression)expression.Body;
var stringLengthAttribs = memberAccessExpression.Member.GetCustomAttributes(
typeof(System.ComponentModel.DataAnnotations.StringLengthAttribute), true);
if (stringLengthAttribs.Length > 0)
{
var length = ((StringLengthAttribute)stringLengthAttribs[0]).MaximumLength;
if (length > 0)
{
attributes.Add("size", length);
attributes.Add("maxlength", length);
}
}
return helper.TextBoxFor(expression, attributes);
}
Does this not work?
public class ViewModel
{
[StringLength(20)]
public string UserName {get;set;}
}
In the View:
#Html.TextBoxFor(x => x.UserName, new {autocomplete = "off"})
or:
#Html.EditorFor(x => x.UserName)
I find that I prefer my views to just Call Html.EditorFor(...). This means that the Editor and Display templates decide the fate of controls in my view, such that my view code gets cleaned up a lot - it just has html and generic requests for editors.
The following link gives a working sample of getting this working in an Editor Template
https://jefferytay.wordpress.com/2011/12/20/asp-net-mvc-string-editor-template-which-handles-the-stringlength-attribute/
I'm using similar in my String.cshtml Editor Template (goes in Shared/EditorTemplates ).
#model object
#using System.ComponentModel.DataAnnotations
#{
ModelMetadata meta = ViewData.ModelMetadata;
Type tModel = meta.ContainerType.GetProperty(meta.PropertyName).PropertyType;
}
#if(typeof(string).IsAssignableFrom(tModel)) {
var htmlOptions = new System.Collections.Generic.Dictionary<string, object>();
var stringLengthAttribute = (StringLengthAttributeAdapter)ViewData.ModelMetadata.GetValidators(this.ViewContext.Controller.ControllerContext).Where(v => v is StringLengthAttributeAdapter).FirstOrDefault();
if (stringLengthAttribute != null && stringLengthAttribute.GetClientValidationRules().First().ValidationParameters["max"] != null)
{
int maxLength = (int)stringLengthAttribute.GetClientValidationRules().First().ValidationParameters["max"];
htmlOptions.Add("maxlength", maxLength);
if (maxLength < 20)
{
htmlOptions.Add("size", maxLength);
}
}
htmlOptions.Add("class", "regular-field");
<text>
#Html.TextBoxFor(m => m, htmlOptions)
</text>
}
else if(typeof(Enum).IsAssignableFrom(tModel)) {
//Show a Drop down for an enum using:
//Enum.GetValues(tModel)
//This is beyond this article
}
//Do other things for other types...
Then my model is annotated such as:
[Display(Name = "Some Field", Description = "Description of Some Field")]
[StringLength(maximumLength: 40, ErrorMessage = "{0} max length {1}.")]
public string someField{ get; set; }
And my View simply calls:
<div class="editor-label">
#Html.LabelWithTooltipFor(model => model.something.someField)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.something.someField)
#Html.ValidationMessageFor(model => model.something.someField)
</div>
You might also notice that my String.cshtml Editor Template also auto-magically handles Enum's, but that is starting to digress from the current topic, so I nixed that code, I'll just say here that the String Editor Template can pull extra weight, and likely google has someting on that https://www.google.com/search?q=string+editor+template+enum
Label With Tooltip For is a custom HTML helper that just drops the description into the label title, for more information on mouse over for every label.
I'd recommend this approach if you want to do this in an Editor Template.
Here is my model:
public class NewsCategoriesModel {
public int NewsCategoriesID { get; set; }
public string NewsCategoriesName { get; set; }
}
My controller:
public ActionResult NewsEdit(int ID, dms_New dsn) {
dsn = (from a in dc.dms_News where a.NewsID == ID select a).FirstOrDefault();
var categories = (from b in dc.dms_NewsCategories select b).ToList();
var selectedValue = dsn.NewsCategoriesID;
SelectList ListCategories = new SelectList(categories, "NewsCategoriesID", "NewsCategoriesName",selectedValue);
// ViewBag.NewsCategoriesID = new SelectList(categories as IEnumerable<dms_NewsCategory>, "NewsCategoriesID", "NewsCategoriesName", dsn.NewsCategoriesID);
ViewBag.NewsCategoriesID = ListCategories;
return View(dsn);
}
And then my view:
#Html.DropDownList("NewsCategoriesID", (SelectList)ViewBag.NewsCategoriesID)
When i run, the DropDownList does not select the value I set.. It is always selecting the first option.
You should use view models and forget about ViewBag Think of it as if it didn't exist. You will see how easier things will become. So define a view model:
public class MyViewModel
{
public int SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
}
and then populate this view model from the controller:
public ActionResult NewsEdit(int ID, dms_New dsn)
{
var dsn = (from a in dc.dms_News where a.NewsID == ID select a).FirstOrDefault();
var categories = (from b in dc.dms_NewsCategories select b).ToList();
var model = new MyViewModel
{
SelectedCategoryId = dsn.NewsCategoriesID,
Categories = categories.Select(x => new SelectListItem
{
Value = x.NewsCategoriesID.ToString(),
Text = x.NewsCategoriesName
})
};
return View(model);
}
and finally in your view use the strongly typed DropDownListFor helper:
#model MyViewModel
#Html.DropDownListFor(
x => x.SelectedCategoryId,
Model.Categories
)
just in case someone comes with this question, this is how I do it, please forget about the repository object, I'm using the Repository Pattern, you can use your object context to retrieve the entities. And also don't pay attention to my entity names, my entity type Action has nothing to do with an MVC Action.
Controller:
ViewBag.ActionStatusId = new SelectList(repository.GetAll<ActionStatus>(), "ActionStatusId", "Name", myAction.ActionStatusId);
Pay attention that the last variable of the SelectList constructor is the selected value (object selectedValue)
Then this is my view to render it:
<div class="editor-label">
#Html.LabelFor(model => model.ActionStatusId, "ActionStatus")
</div>
<div class="editor-field">
#Html.DropDownList("ActionStatusId")
#Html.ValidationMessageFor(model => model.ActionStatusId)
</div>
I think it is pretty simple, I hope this helps! :)
I drilled down the formation of the drop down list instead of using #Html.DropDownList(). This is useful if you have to set the value of the dropdown list at runtime in razor instead of controller:
<select id="NewsCategoriesID" name="NewsCategoriesID">
#foreach (SelectListItem option in ViewBag.NewsCategoriesID)
{
<option value="#option.Value" #(option.Value == ViewBag.ValueToSet ? "selected='selected'" : "")>#option.Text</option>
}
</select>
Well its very simple in controller you have somthing like this:
-- Controller
ViewBag.Profile_Id = new SelectList(db.Profiles, "Id", "Name", model.Profile_Id);
--View (Option A)
#Html.DropDownList("Profile_Id")
--View (Option B) --> Send a null value to the list
#Html.DropDownList("Profile_Id", null, "-- Choose --", new { #class = "input-large" })
Replace below line with new updated working code:
#Html.DropDownList("NewsCategoriesID", (SelectList)ViewBag.NewsCategoriesID)
Now Implement new updated working code:
#Html.DropDownListFor(model => model.NewsCategoriesID, ViewBag.NewsCategoriesID as List<SelectListItem>, new {name = "NewsCategoriesID", id = "NewsCategoriesID" })
I want to put the correct answer in here, just in case others are having this problem like I was. If you hate the ViewBag, fine don't use it, but the real problem with the code in the question is that the same name is being used for both the model property and the selectlist as was pointed out by #RickAndMSFT
Simply changing the name of the DropDownList control should resolve the issue, like so:
#Html.DropDownList("NewsCategoriesSelection", (SelectList)ViewBag.NewsCategoriesID)
It doesn't really have anything to do with using the ViewBag or not using the ViewBag as you can have a name collision with the control regardless.
I prefer the lambda form of the DropDownList helper - see MVC 3 Layout Page, Razor Template, and DropdownList
If you want to use the SelectList, then I think this bug report might assist - http://aspnet.codeplex.com/workitem/4932
code bellow, get from, goes
Controller:
int DefaultId = 1;
ViewBag.Person = db.XXXX
.ToList()
.Select(x => new SelectListItem {
Value = x.Id.ToString(),
Text = x.Name,
Selected = (x.Id == DefaultId)
});
View:
#Html.DropDownList("Person")
Note:
ViewBag.Person and #Html.DropDownList("Person") name should be as in view model
To have the IT department selected, when the departments are loaded from tblDepartment table, use the following overloaded constructor of SelectList class. Notice that we are passing a value of 1 for selectedValue parameter.
ViewBag.Departments = new SelectList(db.Departments, "Id", "Name", "1");
For anyone that dont want to or dont make sense to use dropdownlistfor, here is how I did it in jQuery with .NET MVC set up.
Front end Javascript -> getting data from model:
var settings = #Html.Raw(Json.Encode(Model.GlobalSetting.NotificationFrequencySettings));
SelectNotificationSettings(settings);
function SelectNotificationSettings(settings) {
$.each(settings, function (i, value) {
$("#" + value.NotificationItemTypeId + " option[value=" + value.NotificationFrequencyTypeId + "]").prop("selected", true);
});
}
In razor html, you going to have few dropdownlist
#Html.DropDownList(NotificationItemTypeEnum.GenerateSubscriptionNotification.ToString,
notificationFrequencyOptions, optionLabel:=DbRes.T("Default", "CommonLabels"),
htmlAttributes:=New With {.class = "form-control notification-item-type", .id = Convert.ToInt32(NotificationItemTypeEnum.GenerateSubscriptionNotification)})
And when page load, you js function is going to set the selected option based on value that's stored in #model.
Cheers.