Setting a preselected item for html.dropdownlistfor - asp.net-mvc

I have a a html.dropdownlistfor which list around 900 items
Html.DropDownListFor(m => m.ParentWebContentID, null, "Choose...", htmlAttributes: new { #class = "form-control" })
I want it to have a preselected item and thought the 3rd parameter ("Choose...") was for this.
So I arranged for a ViewBag variable to hold a value (ChosenParentWebContentId)
like this
public ActionResult Create(int? id)
{
if (!AccountController.IsInRole(System.Web.HttpContext.Current.User.Identity.Name, "admin"))
{
return RedirectToAction("Index");
}
ViewBag.ParentWebContentID = GetWebContentListwithGroup(null);
if(id != null)
{
ViewBag.ChosenParentWebContentID = db.WebContent.FirstOrDefault(x => x.WebContentID == id).ParentWebContentID;
}
ViewBag.WebContentTypeID = db.WebContentType.ToList();
ViewBag.ContentTypeID = id;
ViewBag.LanguageCode = new SelectList(db.WebLanguage, "LanguageCode", "DisplayName");
ViewBag.CreatedByUserID = new SelectList(db.AspNetUsers, "Id", "Email");
ViewBag.LastEditedByUserID = new SelectList(db.AspNetUsers, "Id", "Email");
ViewBag.DetailList = db.WebContentDetail.Where(x => x.WebContentID == id).ToList();
return View();
}
I tried changing "choose..." with the ViewBag variable but it didn't work. Is there any other way to do this?
I couldn't find any help on other examples around internet.

When you use Html.DropDownListFor (or any of the *For family of helpers), the value comes from the value of the bound property. Actually, it's a little more complex than that. The value comes from the value of the bound property in ModelState.
So, if you want an item to be automatically selected, then you need to set ParentWebContentID to the value you want selected. Plain and simple. However, once a post is made, the select value will always be what was posted, even if you set ParentWebContentID to a different value. Also worth bearing in mind: Request, ViewBag and ViewData all participate in defining the values in ModelState. As a result, if for example you had an action that had a param named parentWebContentID (case-insensitive), that value from the request would always take precedence over any value you set on the model explicitly.

Related

Using Html.DropDownListFor as part of a paging solution in a razor view

I've spent hours on this, and I'm obviously missing something. Code extracts below are from a cut-down version of my actual app.
My viewmodel contains this:
public IEnumerable<SelectListItem> DisplayPageOptions { get; set; }
public string SelectedPageOption { get; set; }
private static SelectListItem item1 = new SelectListItem() { Text = "25", Value = "25" };
private static SelectListItem item2 = new SelectListItem() { Text = "50", Value = "50" };
private static SelectListItem item3 = new SelectListItem() { Text = "100", Value = "100" };
public SelectList selectList = new SelectList(new[] { item1, item2, item3 }, "Value", "Text", item1);
My razor view contains this:
#Html.DropDownListFor(m => m.SelectedPageOption, Model.DisplayPageOptions, "Pages to display", new { onchange = #"submit()" })
My controller contains this:
SymbolViewModel vm = SymbolViewModel.Instance;
if (vm.SelectedPageOption == null)
{
vm.SelectedPageOption = "25";
}
vm.DisplayPageOptions = vm.selectList;
SymbolDataService service = new();
vm.Load(service);
return View(vm);
My intention is that the user selects 25, 50, or 100 from the dropdown and a corresponding number of rows is displayed. My understanding is that the Html.DropDownListFor should bind to the SelectedPageOption property, which I would then use in the ViewModel to load the correct number of rows.
However, the change in dropdown value has no effect on the SelectedPageOption property, so the page is reloaded always with the original default '25' rows - taken from the value that is set in the controller. I have tried making SelectedPageOption an Int, having seen examples on the web. I hadn't wanted to do this originally because the SelectedListItem Values have to be strings. It makes no difference. I also tried not using a singleton pattern for the viewmodel - also no effect.
The binding is not completely ineffective. Whatever default value I set in the controller, this is the value that appears in the dropdown. But the binding does not work the other way. If anyone can cast some light on what's going on, I'd appreciate it.
EDIT: What I didn't originally point out, and what turned out to be relevant, is that my controller method containing the above code is asynchronous.
I finally worked out what was going on. Because my controller Index method is asynchronous, I need to await the model update before reloading my viewmodel. It seems obvious now!
SymbolViewModel vm = SymbolViewModel.Instance;
await TryUpdateModelAsync(vm);

How to return distinct data to DropDownListFor?

I apply .Net MVC structure with C#. In the controller, I want to distinct specific column (IndustryName), and return the result to Html.DropDownListFor in view. But I get a running time error at view:
System.Web.HttpException: DataBinding: 'System.String' not include
'IndustryName' property.
Is there any one meet such problem, and how to solve it?
Thank you very much for your helping.
Controller:
public ActionResult Create()
{
var industrys = this._pmCustomerService.GetAll().Select (x => x.IndustryName).Distinct();
ViewBag.Industrys = new SelectList(industrys, "IndustryName", "IndustryName", null);
return View();
}
View:
#Html.DropDownListFor(model => model.IndustryName, (SelectList)ViewBag.Industrys)
Your query is returning IEnumerable<string> (you select only the IndustryName property in the .Select() clause. string does not contain an property named IndustryName so you get this error. Just change the SelectList to
ViewBag.Industrys = new SelectList(industrys);
This will bind the options value and display text to the value of IndustryName
The following sample implementation may help you fix the problem:
var industries= this._pmCustomerService.GetAll()
.GroupBy(ind => new { ind.IndustryName})
.Select(group => new SelectListItem
{
Text = group.First().Name,
Value = group .First().Name
}
);
ViewBag.Industries= industries;
You can find more about the 'GroupBy & Select' approach instead of using linq's Distinct(), here
View
#Html.DropDownList("ddlIndustries",(#ViewBag.Industries) as IEnumerable<SelectListItem>)
If you like to use DropDownListFor helper instead then modify view code as follows:
#{
var industries = ViewBag.Industriesas IEnumerable<SelectListItem>;
}
#Html.DropDownListFor(m=> industries , industries )
You get this error because you create SelectList with wrong collection. This should work i think.
var industrys = this._pmCustomerService.GetAll().Select(x => new SelectListItem
{
Value = x.IndustryName,
Text = x.IndustryName
}).Distinct();
ViewBag.Industrys = new SelectList(industrys);
return View();
You are only Selecting IndustryName which is obviously of type String, use DistinctBy() from MoreLinq by Jon Skeet, here is the reference SO post:
public ActionResult Create()
{
var industrys = this._pmCustomerService.GetAll().DistinctBy(x => x.IndustryName);
ViewBag.Industrys = new SelectList(industrys, "IndustryName", "IndustryName", null);
return View();
}

DropDownListFor selecting wrong value when using a lambda with no argument (closure)

My index page looks like this:
case1:
#model IEnumerable<MyStats.Models.Port>
...
<!-- does not work -->
#Html.DropDownListFor(modelItem => item.Active,new List<SelectListItem>(){ new SelectListItem(){ Text = "True", Value=bool.TrueString}, new SelectListItem(){ Text = "False", Value=bool.FalseString}});
<!-- does work -->
#Html.EditorFor(modelItem => item.Active)
item is a Port-Model from the enumerable modele defined on top of the file.
item.Active is a bool value.
Unfortunately, DropDownListFor does not work, the bool values are not set correct.
But EditorFor does work.
And in the edit window, DropdownlistFor does work:
case2:
#model MyStats.Models.Port
...
#Html.DropDownListFor(model => model.Active,new List<SelectListItem>(){ new SelectListItem(){ Text = "True", Value=bool.TrueString}, new SelectListItem(){ Text = "False", Value=bool.FalseString}})
As far as I understand, the difference is, that in case1 the lambda expression is a closure, where item.Active is stored inside, and in case2 the model is passed to the lambda expression at runtime (somewhere in the htmlhelper).
But why is there a difference? It should not matter since in case1 the right value should be extracted from the expression closure.
And since it works with EditorFor, why does it not work with DropDownListFor?
I was having the same issue! Here is what I found after some digging in the MVC source code:
Short version:
If you use modelItem => item.Active, it will not be executed it as a standard .NET expression! Instead, the logic will notice item.Active as code, which accesses the Active property and will attempt to fetch this property from the model of the page and not from the item you are using!!!
Longer version:
Here is the source code for the DropDownFor method: Link Notice that the method uses an object called ModelMetadata and creates it using ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).
Now if you look at ModelMetadata.FromLambdaExpression here, notice that the method does not evaluate the expression - it tries to figure out its type. In your case it gets in
case ExpressionType.MemberAccess:
// Property/field access is always legal
MemberExpression memberExpression = (MemberExpression)expression.Body;
propertyName = memberExpression.Member is PropertyInfo ? memberExpression.Member.Name : null;
containerType = memberExpression.Expression.Type;
legalExpression = true;
break;
After this the code will try to access the property on the model of the page and not your item from the closure.
TParameter container = viewData.Model;
Func<object> modelAccessor = () =>
{
try
{
return CachedExpressionCompiler.Process(expression)(container);
}
catch (NullReferenceException)
{
return null;
}
};
I hope this helps! I am still trying to figure out how to workaround this :)
I would use:
#Html.DropDownList("Active",
new SelectList( new List<string>() { "True", "False" }, Model.Active.ToString()))
I think it is shorter, clearer. When you make a SelectList you can specify the default value manually. Try the DropDownListFor too, it might work with a SelectList instead of an IEnumerable
#Html.DropDownList(model => model.Active,
new SelectList( new List<string>() { "True", "False" }))
P.S. In the first example you have typed "modelItem => item.Active" instead of "modelItem => modelItem.Active" I guess.

How to remove model state error for dropdown with selected option label in mvc?

I am working on a MVC project.I have a view having a dropdownlist with an option label "Select Task".Now the integer property bound with this dropdown is not a required field.
But then too after I try to save, I get the dropdown having a red border showing that it is required. When I analysed, I found that the option label has value null.
Using firebug when I entered 0 for the value of option label, the view was saved with no model state error. Am I doing something wrong ? How to avoid it ?
View
#Html.DropDownListFor(model => model.projecttaskid, new SelectList((IList<SelectListItem>)ViewData["MyTasks"], "Value", "Text"),"Select Task", new { #class = "span2" })
Model public int projecttaskid { get; set; } Controller It doesn't reach the controller action.
projecttaskid is not nullable. Unless you provide a default value for the drop down list, the model will not bind properly, which is why you are getting validation error even though the model field is not marked as [required].
Edit: by default value I mean an item in the list with a value of 0, since the default value for projecttaskid is 0.
For instant, you use an overload of DropDownListFor with the optionLabel argument (your "Select Task" argument.
If no item is selected, this will be taken as the "selected option", and return a null value for model.projecttaskid.
The easiest way would be to add an element with a 0 value when you create your list (ViewData["MyTasks"])
With, for example :
Value = 0;
Text = "Select Task;
And use an overload of DropDownListFor without the optionLabel.
[By the way, usage of ViewModels instead of ViewData would be a good thing, but that's another problem]
*EDIT *
We do use some extension methods to manage these cases :
public static IEnumerable<SelectListItem> ToSelectListItem<T, TValue, TText>(
this IEnumerable<T> enumerable,
Func<T, TText> text,
Func<T, TValue> value)
{
return enumerable.Select(item => new SelectListItem
{
Text = text(item).ToString(),
Value = value(item).ToString()
}).AsEnumerable();
}
public static IEnumerable<SelectListItem> WithDefaultZeroValue(this IEnumerable<SelectListItem> selectListItems, string chooseText/* = ChooseText*/)
{
IList<SelectListItem> items = selectListItems.ToList();
items.Insert(0, new SelectListItem { Value = "0", Text = chooseText });
return items.AsEnumerable();
}
usage
var myList = mySourceForDropDown.ToSelectListItem(m => m.TextField, m => m.ValueField)
.WithDefaultZeroValue("SelectTask")
use this:
#Html.DropDownListFor(model => model.Type, new SelectList(Enum.GetNames(typeof(Enums.TenderType))), new Dictionary<string, object>() { { "data-val", "false" } })

Checkbox filter problem when null

I have an array of checkboxes, and trying to filter them, but when no checkbox is checked, it throws an System.ArgumentNullException
public ActionResult UserEdit(string[] UsergroupIDs)
{
IEnumerable<Usergroup> userUsergroups =
UsergroupIDs.Where(x => x != "false")
.Select(x => (Usergroup)_ug.GetUsergroups(int.Parse(x)).FirstOrDefault());
How should I modify this one?
/M
Set the value to an empty list initially, then change it to the results of your query if the paremeters isn't null. Or, you could modify your view to include hidden fields for each checkbox that has the default (false) property so that parameter is never null. The latter is what the Checkbox helper method does so using it would also solve your problem. Even better, do both.
public ActionResult UserEdit(string[] UsergroupIDs)
{
IEnumerable<Usergroup> userUsergroups = new List<UserGroup>();
if (UsergroupIDs != null)
{
userUsergroups = UsergroupIDs.Where(x => x != "false")
.Select(x => (Usergroup)_ug.GetUsergroups(int.Parse(x)).FirstOrDefault());
}
...
}

Resources