This question already has answers here:
Reset the value of textarea after form submission
(9 answers)
Closed 8 years ago.
I have an MVC application that displays a value. This is the controller:
public ActionResult Index(DataSites DataSiteList)
{
if (DataSiteList.Latitude != null)
{
DataSites test = new DataSites();
test.Latitude = "LATITUDE";
return View(test);
}
return View(DataSiteList);
}
public ActionResult SomeInformation()
{
DataSites test1 = new DataSites();
test1.Latitude = "LATITUDE2";
return RedirectToAction("Index", test1);
}
The View:
#model miniproj2.Models.DataSites
<p>
#Html.TextBoxFor(x => x.Latitude)
</p>
And the Model:
public class DataSites
{
public string Latitude { get; set; }
}
When I go to /Home/SomeInformation, the DataSites' Latitude property is set to "LATITUDE2". Then redirects to the Index() action in the controler, sets the property to "LATITUDE" and returns the view.
When it shows the view, it displays the value "LATITUDE2" as set in the redirect. Shouldn't "LATITUDE" be displayed?
Your problem is (step by step)
Your SomeInformation() method sets the value of test1.Latitude
to "LATITUDE2".
You then pass that model to your Index() method using the overload
of RedirectToAction that accepts an object. Internally this uses
reflection to build a RouteValueDictionary based on the properties
of your model (in this case its simply latitude="LATITUDE2").
When you hit the Index method the model is bound by the DefaultModelBinder and now the value of DataSiteList.Latitude is "LATITUDE2" (which is why you enter
the if block)
In the process of binding, the DefaultModelBinder sets the
ModelStatevalue of Latitude to "LATITUDE2". Any attempts to set
the value of Latitude are now ignored because the view uses
ModelState value to render the control.
It not clear what your trying to do here. You can make it work as you expect by adding ModelState.Clear(); as the first line of your Index() method. This clears all existing ModelState values an you can now set the value to "LATITUDE".
But your if block makes no sense. Perhaps you were just doing some kind of test, but you may as well remove the parameter from the Index() method and just initialize a new instance of DataSites in the method.
Edit
To give a bit more information as to why updating a model property has no affect once ModelState has been set.
Imagine you have a form to collect user information where the model contains int Age. The user is asked to enter their age and someone enters "I'm five next week!". Of course this wont bind to an int so the DefaultModelBinder adds the value (the attemptedValue) and adds a ModelStateError.
When the view is returned it will typically display an error message such as "The field Age must be a number". If the html helper rendering the control used the model value, then it would display "0" (the default value for int). It would be somewhat confusing for the user to see "0" in the textbox and next it a message saying it must be a number (What! but zero is a number and what the heck happened to what I entered?). So instead, the helper uses the value from ModelState and now the users sees "I'm five next week!" and an associated error message that makes sense for the value.
So even though you thoughts were that "its not logical", there is actually some logic to this behavior.
You are not setting Altitude. It will be null so the code will never go into this block and set Lattitude to "LATTITUDE"
if (DataSiteList.Altitude != null)
{
DataSites test = new DataSites();
test.Latitude = "LATITUDE";
return View(test);
}
Related
I'm using jquery popup overlay (http://dev.vast.com/jquery-popup-overlay/) to display the contents of the Credit action (shown below).
With the code shown below, I am expecting the fields in the popup to be cleared since I am returning the partial view with a newly created model with no values set; however the fields are not updated at all.
The crazy thing is, if I comment out the "return PartialView(model);" statement and uncomment the "return Json("Yay it worked!");" then the popup gets replaced with "Yay it worked!". (Yes, I also have to change the return type of the action when I uncomment that line).
Why would Ajax.BeginForm have no problem replacing the target div with the text of a Json return value, but totally ignore the results of a PartialViewResult?
public PartialViewResult Credit()
{
CreditPaymentModel model = new CreditPaymentModel();
return PartialView(model);
}
[HttpPost, ValidateAntiForgeryToken]
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]
public PartialViewResult Credit(CreditPaymentModel model) {
model = new CreditPaymentModel(); // breakpoint here
return PartialView(model);
//return Json("Yay it worked!");
}
EDIT: I just added a function to process the OnSuccess event. I can step through the view as it's being processed (after submitting and it returns the view with an empty model) and verify that the values are in fact null, however when it gets to the OnSuccess method the data it receives shows the original data that was submitted rather than the blank values I'm expecting. Normally I would expect this to be due to caching, but I have an OutputCache attribute over the Credit action and I have set the AllowCache property to false in the AjaxOptions so I don't understand how this could be happening?!
The HtmlHelper methods your using in your partial view form use values from ModelState rather than from your model if they exist, which in your case they do because your POST method has a parameter which is typeof CreditPaymentModel and each value of CreditPaymentModel has been added to ModelState by the DefaultModelBinder.
You would need to use ModelState.Clear; before you return the model if you want to display the default values for CreditPaymentModel. For a more detailed explanation of the behavior, refer TextBoxFor displaying initial value, not the value updated from code.
Note however, because your returning a new partial, all client side validation will be lost unless you re-parse the $.validator (refer this answer for an example). All this will be easier is you use the $.ajax() method which gives far more flexibility, and in your case, have the method returns the partial is ModelState is invalid and you want to display validation errors, or return null otherwise. In the ajax success callback, if the result is null, just reset the existing form controls, otherwise replace the partial and re-parse the $.validator.
I'm making a simple Search page in MVC with some filters in it. The filters are represented by properties in my ViewModel. My ViewModel is binded to a GET form in the cshtml so my filter will appears in the querystrings and the user will be able to bookmark his search.
What I want to do is to assign a default value to some of my filters.
My (simplified) ViewModel :
public class SearchViewModel
{
//Filter I want to set a default value to
public OrganizationType? OrganizationType {get; set;}
//Results of the search
public IEnumerable<ItemViewModel> Items {get; set;}
}
I'd like to set a default value for OrganizationType. I can't simply set it in the constructor of SearchViewModel because it depends on the current user :
public void InitViewModel(SearchViewModel vm)
{
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
}
First solution was simply to check if OrganizationType is null, then assign a default value :
public ActionResult Search(SearchViewModel vm)
{
if(vm.OrganizationType == null)
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
return View(vm);
}
But this solution doesn't work as a null value corresponds to an empty filter and it's a choice that the user can make. So I can't override it.
The second solution I tried was to specify that the default value of the controller should be null in the Search action :
public ActionResult Search(SearchViewModel vm = null)
{
if (vm == null)
{
vm = new SearchViewModel();
InitViewModel(vm);
}
...
return View(vm);
}
But in practice, the variable vm is never null, so the default values are never setted.
I also tried having two Action, one wihout a ViewModel where I instanciate a new ViewModel with the default values and then call the second action :
public ActionResult Search()
{
var vm = new SearchViewModel();
InitViewModel(vm);
//Simply call the second action with the initizalied ViewModel
return Search(vm);
}
public ActionResult Search(SearchViewModel vm)
{
...
return View(vm);
}
But it doesn't work because there is now an ambiguity between the two action, and asp.net doesn't know which one to choose.
So in summary, I'd like to find a way to set a default value for a ViewModel, without setting it in the constructor and overriding user choices.
Another way to say it, how can I distinguish an "empty" ViewModel from one where some values are binded from the form.
Any idea ?
Ok I think I found a solution to my own problem...
I can use the ModelState property of the controler to check it the ViewModel is empty or was binded from the form :
public ActionResult Search(SearchViewModel vm = null)
{
if (ModelState.Count == 0)
{
InitViewModel(vm);
}
...
return View(vm);
}
So if ModelState.Count equals to 0 it means that user didn't change any filters. So the form is empty and we can bind our default values. As soon as the user will change one of the filters or submit the request, the ModelState.Count will be greater than 0 so we shouldn't set the default value. Otherwise we would override an user choice.
The logic of what you're doing is a little iffy. Generally speaking, if a value is nullable then null is the default value. However, it seems that you're trying to make a distinction here between whether the value is null because it's not set or null because the user explicitly set it to null. This type of semantic variance is usually a bad idea. If null has a meaning, then it should always carry that meaning. Otherwise, your code becomes more confusing and bugs are generally introduced as a result.
That said, you can't count on ModelState having no items. I've honestly never played around with ModelState enough in scenarios where there's not post data, but it's possible there's some scenario where there's no post data and yet ModelState may have items. Even if there isn't, this is an implementation detail. What if Microsoft does an update that adds items to ModelState in situations where it previously had none. Then, your code breaks with no obvious reason why.
The only thing you can really count on here is whether the request method is GET or POST. In the GET version of your action, you can reasonably assume that the user has made no modifications. Therefore, in this scenario, you can simply set the value to whatever you like without concern.
In the POST version of your action, the user has made some sort of modification. However, at this point, there is no way to distinguish any more whether the value is null because it is or because the user explicitly wanted it to be. Therefore, you must respect the value as-is.
It seems that in ASP.NET Core, the value in asp-* attributes (e.g. asp-for) is taken from the request payload before the model. Example:
Post this value:
MyProperty="User entered value."
To this action:
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View();
}
OR this action
[HttpPost]
public IActionResult Foo(MyModel m)
{
m.MyProperty = "Change it to this!";
return View(m);
}
View renders this:
<input asp-for="MyProperty" />
The value in the form input is User entered value. and not Change it to this!.
First of all, I'm surprised that we don't need to pass the model to the view and it works. Secondly, I'm shocked that the request payload takes precedence over the model that's passed into the view. Anyone know what the rationale is for this design decision? Is there a way to override the user entered value when using asp-for attributes?
I believe this is the expected behavior/by design. Because when you submit the form, the form data will be stored to ModelState dictionary and when razor renders your form elements, it will use the values from the Model state dictionary. That is why you are seeing your form element values even when you are not passing an object of your view model to the View() method.
If you want to update the input values, you need to explcitly clear the Model state dictionary. You can use ModelState.Clear() method to do so.
[HttpPost]
public IActionResult Create(YourviewModel model)
{
ModelState.Clear();
model.YourProperty = "New Value";
return View(model);
}
The reason it uses Model state dictionary to render the form element values is to support use cases like, showing the previously submitted values in the form when there is a validation error occurs.
EDIT : I found a link to the official github repo of aspnet mvc where this is confirmed by Eilon (asp.net team member)
https://github.com/aspnet/Mvc/issues/4486#issuecomment-210603605
I can confirm your observation. What's really going to blow your mind is that this:
[HttpPost]
public IActionResult Foo (MyModel m)
{
m.MyProperty = "changed";
var result = new MyModel { MyProperty = "changed" };
return View(result);
}
...gives you the same result.
I think you should log a bug: https://github.com/aspnet/mvc/issues
Edit: I now remember this issue from previous encounters myself and concede that it isn't necessarily a bug, but rather an unintended consequence. The reasons for the result of executing this code is not obvious. There likely isn't a non-trivial way to surface a warning about this, but PRG is a good pattern to follow.
I'm building a validation form in my application. In that form there are two buttons. One to accept and one to reject. When the user press reject the rejection reason field must be provided. I check this serverside.
I first check what button is pressed and then if the field is empty I add a moddel error to the modelstate. But, because all fields in the form are readonly, those are not posted back to the server and therefor when I return the view back to usern there is no data. I'm probably missing something obvious, but cant find what to do. (I know I can make all fields in my form hidden, but due to the large amount of fields this would be really ugly)
This is my code.
[HttpPost]
public virtual ActionResult Validate(string action, Record dto) {
if(action == Global.Accept) {
ciService.Store(dto);
return RedirectToAction("Index", "Ci");
} else {
if(string.IsNullOrEmpty(dto.RejectionReason)) {
ModelState.AddModelError("RejectionReason", "REQUIRED!!!!");
return View("Validate", dto);
}
ciService.Reject(dto);
return RedirectToAction("Index", "Ci");
}
}
You need to recreate the model from the database and then change it to match whatever changes are posted in dto. Then use that combined model in the view.
Instead of passing the DTO back from the browser, I would use a hidden HTML field or a querystring parameter containing the ID that identifies the DTO. Then your POST action method would look something like:
[HttpPost]
public virtual ActionResult Validate(string action, int id)
{
// reload the DTO using the id
// now you have all the data, so just process as you did in your question
if (action == Global.Accept) { ... }
...
}
Your GET method might look something like the following then...
[HttpGet]
public virtual ActionResult Validate(int id)
{
// load the DTO and return it to the view
return View();
}
In this way you have all the data you need within your POST action method to do whatever you need.
You need to have hidden fields corresponding to each property displayed in UI.
e.g.,
#Html.LabelFor(m=>m.MyProperty) - For Display
#Html.Hiddenfor(m=>m.MyProperty) - ToPostback the value to server
If I understand right, the problem is because you don't use input.
To solve your problem insert some input hidden in your form with the value you need to be passed to the controller
#Html.HiddenFor(model => model.Myfield1)
#Html.HiddenFor(model => model.Myfield2)
that should fix the values not passed back to your actions
If you don't need these fields on the server side, simply create a new ViewModel
RecordValidateViewModel and this contains only the fields in it that need to be validated. The model binder will populate then and you will have validation only on the fields in that model rather than all the other fields you don't seem to want there.
If you need them to validate, then post them back to the server. Its not 'ugly' if hidden.
I'm trying to use MVC for a new project after having been around the block with all the samples and tutorials and such. However, I'm having a hard time figuring out where certain things should take place.
As an example, I have an entity called Profile. This entity contains the normal profile type stuff along with a DateOfBirth property that is of type DateTime. On the HTML form, the date of birth field is split into 3 fields. Now, I know I can use a custom model binder to handle this, but what if the date entered is not a valid date? Should I be checking for that in the model binder? Should all my validation go in the model binder? Is it ok to have only a few things validated in the model binder and validate the rest in the controller or the model itself?
Here's the code I have now, but it just doesn't look right to me. Seems dirty or smelly.
namespace WebSite.Models
{
public class ProfileModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
DateTime birthDate;
var form = controllerContext.HttpContext.Request.Form;
var state = controllerContext.Controller.ViewData.ModelState;
var profile = new Profile();
profile.FirstName = form["FirstName"];
profile.LastName = form["LastName"];
profile.Address = form["Address"];
profile.Address2 = form["Address2"];
profile.City = form["City"];
profile.State = form["State"];
profile.Zip = form["Zip"];
profile.Phone = form["Phone"];
profile.Email = form["Email"];
profile.Created = DateTime.UtcNow;
profile.IpAddress = controllerContext.HttpContext.Request.UserHostAddress;
var dateTemp = string.Format("{0}/{1}/{2}",
form["BirthMonth"], form["BirthDay"], form["BirthYear"]);
if (string.IsNullOrEmpty(dateTemp))
state.AddModelError("BirthDate", "Required");
else if (!DateTime.TryParse(dateTemp, out birthDate))
state.AddModelError("BirthDate", "Invalid");
else
profile.BirthDate = birthDate;
return profile;
}
}
}
Building on the sample code above, how would you do the validation message for a 3 part field? In the case above, I'm using a completely separate key that doesn't actually correspond to a field in the form, because I don't want an error message to appear beside all 3 fields. I only want it to appear to the right of the Year field.
I think it is reasonable to do validation in the model binder. As Craig points out, the validation is mostly the property of your business domain, however:
Sometimes your model is just a dumb presentation model, not a business object
There are various mechanisms you can use to surface the validation knowledge into the model binder.
Thomas gives you an exmaple of #1.
An example of #2 is when you declaratively desribe validation knowlege using attributes (like the DataAnnotation attribute [Required]), or inject some business layer validation service into a custom model binder. In these situations the model binder is an ideal place to take care of validation.
That being said, model binding (finding, converting, and shuffling data into an object) and validation (data meets our specifications) are two seperate concerns. You could argue that they should be seperate phases/components/extensibility points, but we have what we have, although the DefaultModelBinder makes some distinction between these two responsibilities. If all you want to do is provide some validation for a specific type of object you can derive from the DefaultModelBinder and override the OnPropertyValidating method for property level validations or OnModelUpdated if you need the holistic view.
Here's the code I have now, but it
just doesn't look right to me. Seems
dirty or smelly.
For your specific code I would try to write a model binder for DateTime only. The default model binder can take care of binding firstname, lastname, etc., and delegate to your custom model binder when it reaches a DateTime property on the Profile. In addition, try using the valueProvider in the bindingContext instead of going directly to the form. These things can give you more flexibility.
More thoughts here: 6 Tips for ASP.NET MVC Model Binding.
Sometimes the model is a view-model, not a domain model. In this case you could benefit from separating those two and design the view model to match your view.
Now you can let the view model validate the input and parse the three fields into a DateTime. Then it can update the domain model:
public ActionResult SomeAction(ViewModel vm)
{
if (vm.IsValid)
{
var dm = repositoryOrSomething.GetDomainModel();
vm.Update(dm);
}
// more code...
}
I had the same exact situation the other day...below is my model binding code. Basically it binds all the DateTime? fields of a model to month/day/year fields from a form (if possible) So, yes, I do add in the validation here, since it does seem appropriate to do so.
public class DateModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
string DateMonth = _GetDateValue(bindingContext, propertyDescriptor.Name + "Month");
string DateDay = _GetDateValue(bindingContext, propertyDescriptor.Name + "Day");
string DateYear = _GetDateValue(bindingContext, propertyDescriptor.Name + "Year");
// Try to parse the date if we have at least a month, day or year
if (!String.IsNullOrEmpty(DateMonth) || !String.IsNullOrEmpty(DateDay) || !String.IsNullOrEmpty(DateYear))
{
DateTime fullDate;
CultureInfo enUS = new CultureInfo("en-US");
// If we can parse it, set the model property
if (DateTime.TryParse(DateMonth + "/" + DateDay + "/" + DateYear,
enUS,
DateTimeStyles.None, out fullDate))
{
SetProperty(controllerContext, bindingContext, propertyDescriptor, (DateTime?)fullDate);
}
// The date is invalid, so we need to add a model error
else
{
string ModelPropertyName = bindingContext.ModelName;
if(ModelPropertyName != "")
{
ModelPropertyName += ".";
}
ModelPropertyName += propertyDescriptor.Name;
bindingContext.ModelState.AddModelError(ModelPropertyName, "Invalid date supplied for " + propertyDescriptor.Name);
}
}
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
// Get a property from binding context
private string _GetDateValue(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
//Didn't work? Try without the prefix if needed...
// && bindingContext.FallbackToEmptyPrefix == true
if (valueResult == null)
{
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
}
if (valueResult == null)
{
return null;
}
return (string)valueResult.ConvertTo(typeof(string));
}
}
Note: I had some problems with bindingContext.FallbackToEmptyPrefix always being false...can't find any useful info on that, but you get the idea.
Validation should be done in multiple places, according to the functionality of each place. For example, if your model binder cannot find the submitted values into a proper DateTime value, then the binder can add a model state error. If, on the other hand, your business logic requires the date to be within a certain range, this would not be appropriate to do and the model binder; it should be in the business logic layer. Controllers can potentially add validation errors as well if, for example, the edit model cannot be transformed into an entity model.
A validation framework such as xVal makes this much simpler.
The Contact Manager sample application on the http://www.asp.net/mvc site has an excellent description of separating out your validation logic into a service layer from your controller and model.
It's well work a read
I tired of creating little small-purpose ViewModels that only touched parts of my mile-wide domain model.
Thus, I cooked up my own method for addressing this. My ViewModel is a typeOf DomainModel, and I use a custom model binder to ensure its identity properties load first - once identity is set - it triggers a DomainModel.Load, and the remainder of the binding activity essentially performs a 'merge'.
Again, when my ViewModel is bound (e.g. on a form POST), after essential fields comprising the ID are set - it immediately loads up the domain model from the database. I just had to come up with a replacment for the DefaultModelBinder. My custom model binder posted here on StackOverflow allows you to control the binding order of the properties.
Once I can guarantee that identity properties are bound, (the internals of my viewmodel listen for the completion of identity setters) I trigger a load of my domain model, as the rest of the properties are bound, they are overwriting, i.e. 'merging' into the loaded domain model.
Basically, I can have all my various razor views, whether they expose 5 form fields or 50 fields of the model.. all submit to a controller action that looks like this (granted, I still make separate actions where needed to do appropriate custom business stuff.. but the point is, my controller actions are focused and succinct)
<HttpPost()>
<Authorize(Roles:="MYCOMPANY\activeDirRoleForEditing")>
Function Edit(<Http.FromBody()> ByVal mergedModel As OrderModel) As ActionResult
'notice: NO loading logic here - it already happened during model binding
'just do the right thing based upon resulting model state
If Me.ModelState.IsValid Then
mergedModel.SaveAndReload("MyServiceWebConfigKey")
ViewBag.SuccessMessage = String.Format("You have successfully edited the order {0}", mergedModel.Id)
Return View("Edit", mergedModel)
Else
ViewBag.ErrorText = String.Format("Order {0} not saved. Check for errors and correct.", mergedModel.Id)
Return View("Edit", mergedModel)
End If
End Function