HTML.TextBoxFor is set readonly before value set - asp.net-mvc

I have the following HTML helper textbox:
#Html.TextBoxFor(m => m.Email, Model.Active ? new { #readonly = "readonly", #style = "background:#E8E8E8" } : new Object { })
When I change the email value in the action (in the model being returned) then set the active=true (which is also in the model) to make it readonly, the email textbox value isnt updated with the new value coming from the model, and i checked to confirm that the model is going back with the new email and active=1.
Its as if the readonly is being set before the value from the model is being rendered.
Any help is appreciated, thankx

When you post back a model, its values are added to ModelState. Html helpers bind to the vales in ModelState, not the values of the model properties, so modifying the value of a model property in the POST method will not be reflected in the view unless you first clear model state before setting the value using
ModelState.Clear(); // clears all properties
or
if (ModelState.ContainsKey("active"))
{
ModelState["active"].Errors.Clear(); //clears the property 'active'
}
The reason for this behavior is explained in the second part of this answer.
However, clearing ModelState should be used with caution as it also clears validation errors, and in any case the correct approach is to follow the PRG pattern

Related

Bind Value with DropDownListFor [duplicate]

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.

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.

How will model binder deals with Readonly & disabled

I have the following two items , one which is readonly:-
#Html.TextBoxFor(model => model.Technology.Tag, new
{ #readonly = "readonly" })
While the other is Disabled:-
#Html.DropDownListFor(model => model.Customer.NAME, ((IEnumerable<TMS.Models.AccountDefinition>)ViewBag.Customers).Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.ORG_NAME),
Value = option.ORG_NAME.ToString(),
Selected = (Model != null) && (Model.Customer != null) & (option.ORG_NAME == Model.Customer.NAME)
}), "Choose...", new { disabled = "disabled" })
so will the asp.net mvc model binder bind the two items? , or it will ignore any read-only and any disabled fields ?
It will bind them, but as long as you have populated them with the correct data, that shouldn't matter. Also, you can have your code that maps these models to the entity, assuming you are using view models, just ignore the values in question. Assuming this is a standard HTTP Post from the form, HTTP will not post disabled or readonly fields, which means they will be null or the default value in the model, so you need to account for that.
If you want the binder to ignore these values, use TextBox and DropDownList and make sure they are not named the same as your properties. If you do not use 'For' you will need to add code in the view to set the values.
ReadOnly text fields get bound.
I've been trying to find a way around the unbound dropdownboxfor values. Here's what I came up with:
$('#xxx').change(function() {$(this).val(lastSel_xxx);});
var lastSel_xxx = $("#xxx option:selected").val();
Use the code above for each HTML element in your view (this will require you to document each ID output) and replace 'xxx' with the element name.
When a user selects another option besides the initial option selected, it reverts back to the original.
HOpe this helps!

View does not affect models changes

I have an get action, which has a couple of parameters. In view I have a form, that fills after get action. Action creates an instance of model using received parameters, but if parameter has special value then action set to model some default values despite parameters. After that action return view with model.
The problem is that after changing the model in View shows data of the parameters, and not from the modified model.
This happens because, by design, all HTML helpers first look at the ModelState when binding and after that in the model itself. So if you intend to modify some value that was part of the POST request you will need to remove it from ModelState first if you want this change to be reflected in the view:
[HttpPost]
public ActionResult SomeAction(MyViewModel model)
{
// We remove the Bar property that was part of the request because
// we modify its value here
ModelState.Remove("Bar");
model.Bar = "Some modified value";
return View(model);
}
This assumes that in the corresponding view you have an input field for it:
#Html.EditorFor(x => x.Bar)

Why is a value that I update in my model inside an MVC3 controller not rendered on the client?

I have a controller action UpdateCustomer(CustomerDto customer) that returns a PartialViewResult with a model that is also a CustomerDto:
[HttpPost]
public PartialViewResult UpdateCustomer(CustomerDto customer)
{
CustomerDto updatedCustomer = _customerService.UpdateCustomer(customer);
updatedCustomer.Name = "NotThePostedName";
return PartialView("CustomerData", updatedCustomer);
}
In my view, I have the following line:
#Html.TextBoxFor(model => model.Name)
So far, so good. In my view I do an asynchronous post to this action method, the model binder does its work and I can update a customer in the database. Then I want to render the updated customer to the client. For example, I'd like to change the customer name in my controller. However, what gets rendered is always the properties from the posted customer, not the properties from updatedCustomer.
I decided to include the MVC3 source code in my project to see what really happens. It appears to be a feature (bug?) of MVC3 that it always takes the value from ViewData.ModelState instead of the value from ViewData.Model.
This happens at lines 366-367 of System.Web.Mvc.Html.InputExtensions:
string attemptedValue =
(string) htmlHelper.GetModelStateValue(fullName, typeof(string));
tagBuilder.MergeAttribute("value",
attemptedValue ?? ((useViewData)
? htmlHelper.EvalString(fullName)
: valueParameter), isExplicitValue);
As you can see, attemptedValue comes from ModelState. It contains the old value for CustomerDto.Name (the value that was posted to the controller action).
If this is a feature, why does it work this way? And is there a way to work around it? I would expect that if I update my model, the update gets rendered, not the old value I posted.
Well yes it's a feature (ModelState is always checked before actual Model), you can clear the ModelState, or update just the value you need:
ModelState["Name"].Value = updatedCustomer.Name;

Resources