Bind Value with DropDownListFor [duplicate] - asp.net-mvc

I am working on an ASP.NET MVC-4 web application. I'm defining the following inside my action method to build a SelectList:
ViewBag.CustomerID = new SelectList(db.CustomerSyncs, "CustomerID", "Name");
Then I am rendering my DropDownListFor as follow inside my View:
#Html.DropDownListFor(model => model.CustomerID, (SelectList)ViewBag.CustomerID, "please select")
As shown I am naming the ViewBag property to be equal to the Model property name which is CustomerID. From my own testing, defining the same name didn't cause any problem or conflict but should I avoid this ?

You should not use the same name for the model property and the ViewBag property (and ideally you should not be using ViewBag at all, but rather a view model with a IEnumerable<SelectListItem> property).
When using #Html.DropDownListFor(m => m.CustomerId, ....) the first "Please Select" option will always be selected even if the value of the model property has been set and matches one of the options. The reason is that the method first generates a new IEnumerable<SelectListItem> based on the one you have supplied in order to set the value of the Selected property. In order to set the Selected property, it reads the value of CustomerID from ViewData, and the first one it finds is "IEnumerable<SelectListItem>" (not the value of the model property) and cannot match that string with any of your options, so the first option is selected (because something has to be).
When using #Html.DropDownList("CustomerId", ....), no data-val-* attributes will be generated and you will not get any client side validation
Refer this DotNetFiddle showing a comparison of possible use cases. Only by using different names for the model property and the ViewBag property will it all work correctly.

There is not harm to use it. You will not get any error. but best practice is to bind model property.

Related

Using #Html.EditorFor to create new model

I want to render a form used to create a new instance of a Model. I tried,
#Html.EditorFor(model => new Person())
But I got the error,
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
I tried,
#Html.EditorForModel("MyNamespace.Person")
but nothing rendered. How do I use Html.EditorFor when I dont have a model instance to pass to it?
It seems that you're a bit confused about how to use #Html.EditorFor(). First,, your use of the lambda expression doesn't quite make sense, model => new Person(). If you want to create a new instance of a model then there's no need to use a lambda, just new Person() gives you that instance.
Second, you don't need an actual instance to pass into #Html.EditorFor(). The purpose of this method is to produce an html markup including an input field for a property or even multiple properties.
So let's say your Person model had an Age property, then you can create an edit field for that attribute by calling
#Html.EditorFor(model => model.Age)
Now where does model come from? You have to define it in your view, so add this line to the top of your view file so that ASP.net knows which model to grab the Age attribute from,
#model <namespace>.Models.Person
The namespace is usually the name of your project, and if you don't put your models in the Models folder, then replace that with where ever your model is contained in.

MVC.ViewBag is wrong?

I'm using
ViewBag.Something = session.Query<Something>().ToList();
To pass information from class Something to View and use it in selectList
#Html.DropDownListFor(model => model.model, new
SelectList(ViewBag.Something, "Id", "name"), "--Smthing--")
Is that bad? and how can i change it to be better?
The practice is not at all bad but it is recommonded to have a List property in our mode it self. in your case something like
public ActinResult YourActionMethod()
{
YourModelObject.Something = session.Query<Something>().ToList();
// And return your view after further code statements
return View(YourModelObject);
}
Infact instead of the original list object you cancreate a SelectList object where you can easily bind key and value and send it to view. By this way you can add all your business and build your model at once place and can populate it from the location you want to. Later View will just use that model rather than applying some more intelligence to it. It helps alot as all your values resides under same object rather than some in Model and some in ViewBag.
Secondly this practice is also fine if you don't have this list at more than one place and you are not reusing this model in any views. Also if you want to access this property outside your view e.g. in Layout or in some parent view which is consuming your view.
You can take a look at following post which explains how to bind a list to your model.
Setting default selected value of selectlist inside an editor template
I prefer to add all my properties to the model of the view, but the ViewBag can be useful to add things that don't quite fit in the model. It can also be useful when binding to elements in the layout since it doesn't have access to the view's model.

Is this by design in MVC Model Binding?

A simple scenario that I've never seen before, but a colleague has just hit - MVC3
Create an action method MyAction(int myProperty = 0)
Create a model that has a property MyProperty
Pass an instance of this model to a strongly typed view, but set the property to 10 in code (don't use the query string parameter!)
In the view, Html.TextBoxFor(x => x.MyProperty)
This should render 10 in the text box.
Now call the action method MyAction?myProperty=8
Shouldn't this still render 10 in the text box?
I see that I can override the property discovered by the expression and assume this is because they are the same name (Query String parameter and model property). Eveything is then in the ViewData but one overrides the other.
Is this by design?
This is by design - ModelState is the highest priority value-provider for model properties, higher than even model itself. Without query string parameter, ModelState does not contain value for MyProperty, so framework uses model value.
You can use ModelState.Remove("MyProperty") to ensure using model value
If you look at the source code for Html.TextBoxFor you will see that if a value exists in ModelState then it will always use that value before any other.
string attemptedValue = (string)htmlHelper.GetModelStateValue(fullName, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(fullName, format) : valueParameter), isExplicitValue);
If the value is in ModelState, then it doesn't matter what you set in code.

Using a edit template without using Html.EditorFor()

I have a date time picker combination in a edit template that can be used like Html.EditorFor(x => x.ETA) but now I want to use the same template somewhere where I don't have a model that contains a DateTime property. So I tried Html.Editor("DateWithTime", "Arrival") which uses the correct template, but doesn't assign a value to ViewData.ModelMetadata.PropertyName which is something that my template relies on. It sets the id of the textbox which is obviously important.
Is there a way to render the template and assign a id value to the ViewData.ModelMetadata.PropertyName so I can re-use the logic in the template instead of having to copy it?
Maybe use ViewData.TemplateInfo.HtmlFieldPrefix instead of ViewData.ModelMetadata.PropertyName.
I am not sure but I thought that HtmlFieldPrefix an PropertyName have the same value as long as you do not iterate a collection.
You can modify the HtmlFieldPrefix property with the htmlFieldName parameter from Html.Editor.
You can use the UIHint in your model. give it the name of the template you want to use
[UIHint("DateWithTime")]
You still use EditorFor with this.

Persisting Selected Value in MVC DropDownLists

I'm new to MVC, but I've been all over this, read all the documentation and all the questions and all the blog posts I can find, and all I'm doing is getting completely wrapped around the axle.
I'm trying to make a "create" Action and View. My data entry is relatively straight forward, and common: I have a drop down list and a text box. In my case, I'm creating a user contact channel, and the drop down box chooses between email and textmsg, and the text box then enters the relevant contact information, either a well formed email address, or a mobile phone number.
Here's a (slightly simplified form of) my View page:
<tr>
<td><%= Html.DropDownList("ChannelDescription", Model.ChannelDescription, "Select a Channel", new { id = "ChannelDDL", onchange="ChannelDDLChanged()" })%>
<br />
<%= Html.ValidationMessage("ChannelDescription", "Please Select a Channel") %>
</td>
<td>
<%= Html.TextBox("SubscriberNotificationAddr") %> <br />
<%= Html.ValidationMessage("SubscriberNotificationAddr", "Please enter a contact address or number") %>
</td>
</tr>
I'm using a strongly typed ViewData model, rather than using the ViewDataDictionary. The ChannelDescription element is a SelectList, which is initialized with the list of choices and no selection.
The initial display of the form, the data entry into the form, and the extraction of the data from the form by the controller goes fine.
My problem is if the data contains an error, such as a mal-formed email address or cell phone number, and I have to return to the view, I have not been successful in getting the drop down list selection redisplayed. The ChannelDescription element is recreated in the controller with the user's choice as the selected item. I have set breakpoints on that line of the View, and verified that the selected element of the list of items has the Selected property set to true, but it still displays the default "Select a Channel".
This seems like it would be a very common situation, and shouldn't be this hard. What am I doing wrong?
FYI, this is with MVC 1.0 (Release), Windows 7, and VS 2008, running under Firefox 3.5.2.
After viewing the answer above, I wanted to check it out, because all the examples I had seen had, indeed, used ViewDataDictionary, rather than a strongly typed ViewDataModel.
So I did some experiments. I constructed a very simple view that used a plain ViewDataDictionary, and passed values in by named keys. It persisted the selected item just fine. Then I cut and pasted that View (and controller) to another one, changing only what was necessary to switch to a strongly typed ViewData Model. Lo, and behold, it also persisted the selected item.
So what else was different between my simple test and my application? In my test, I had used simply "Html.DropDownList("name", "optionLabel")". However, in my application, I had needed to add HTML attributes, and the only overloads available that included HtmlAttributes also include the select List.
It turns out that the DropDownList overload with a select list parameter is broke! Looking at the downloaded MVC source code, when DropDownList is called with just a name, or a name and an optionLabel, it ends up retrieving the target select list from the ViewData, and then invoking the private SelectInternal method by the following call:
return SelectInternal(htmlHelper, optionLabel, name, selectList, true /* usedViewData */, false /* allowMultiple */, (IDictionary<string, object>)null /* htmlAttributes */);
However, if it's called with a selectList parameter, it ends up with the following:
return SelectInternal(htmlHelper, optionLabel, name, selectList, false /* usedViewData */, false /* allowMultiple */, htmlAttributes);
The difference is that in the first one (which will work correctly) the "usedViewData" parameter is true, while in the second one, it is false. Which is actually okay, but exposes an internal defect in the SelectInternal routine.
If usedViewData is false, it gets a object variable "defaultValue" from the ViewData model.
However, defaultValue is used as though it is either a string or an array of strings, when, in fact what is returned from the ViewData is a SelectList. (IEnumerable<SelectListItem>).
If usedViewData is true, then defaultValue will be either null or a string.
Then if defaultValue is not null, it ends up going into a block of code which contains this:
foreach (SelectListItem item in selectList) {
item.Selected = (item.Value != null) ? selectedValues.Contains(item.Value) : selectedValues.Contains(item.Text);
newSelectList.Add(item);
selectList is the original selectList that was passed in, so the item is a SelectListItem (string Text, string Value, and bool Selected). But selectedValues was derived from the defaultValue, and becomes a List of SelectLists, not a List of strings. So for each of the items, it's setting the Selected flag based on whether the selectedValues list "Contains" the item.Value. Well, a List of SelectLists is never going to "Contain" a string, so the item.Selected never gets set. (Correction: actually, after more tracing with the debugger, I found that selectedValues is derived from the defaultValue by a "ToString()" call. So it actually is a list of strings, but instead of containing the values that we want, it contains "System.Web.Mvc.SelectList" - the result of applying "ToString()" to complex object like a SelectList. The result is still the same - we're not going to find the value we're looking for in that list.)
It then substitutes the newly constructed "newSelectList" for the original "selectList", and proceeds to build the HTML from it.
As cagdas (I apologize for butchering your name, but I don't know how to make those characters on my US Keyboard) said above, I think I'll have to build my own method to use in place of the DropDownList HtmlHelper. I guess since this release 1 and Release2 is in Beta 2, we can't really expect any bug fixes unless we do it ourselves right?
BTW, if you've followed me this far, this code is in src\SystemWebMvc\Mvc\Html\SelectExtensions.cs, at around line 116-136
I had some discussions with Brad Wilson, from the MVC team, and he explained to me that I was misunderstanding how the DropDownList helper method should be used (a misunderstanding that I think might be fairly common, from what I've read).
Basically, EITHER give it the SelectList in the named parameter of the ViewModel, and let it build the drop down list from that with the appropriate items selected, OR give it the SelectList as a separate parameter and let the named parameter of the ViewModel be just the value strings for the selected item(s). If you give it a SelectList parameter, then it expects the named value to be a string or list of strings, NOT a SelectList.
So, now your ViewModel ends up having two elements for one conceptual item in the view (the dropdown list). Thus, you might have a model that has
string SelectedValue {get; set;}
SelectList DropDownElements { get; set;}
Then you can pre-populate the DropDownElements with the choices, but in your model view binding, you just need to deal with SelectedValue element. It seems to work pretty well for me when I do it that way.
Yes, I too had so many problems getting DropDownList to respect the selected item I've given to it.
Please check my answer in this question. As far as I can remember, that was the only way I could get it to work. By passing the list via ViewData.
FYI, I stopped using that HtmlHelper method. I'm now simply outputting the <select> and <option> tags myself with a loop and setting the selected property of the option tag by checking it myself.

Resources