After trying around the whole day with model bindings, without results, i decided to ask here.
I have got an asp.net razor view where a user (aka Seller) can edit his user details. Furthermore the user should be able to change his password.
I made a ViewModel:
public class EditSellerViewModel
{
public Seller Seller { get; set; }
public ChangePasswordModel ChangePasswordModel { get; set; }
}
My view has two forms which result in two "Submit" buttons. In my action i check which button was clicked. If the "Passwords" form has been submitted, i want to set the new Password in the Seller entity (that actually works) and SaveChanges() which does not change anything in the database (and does not throw any exception). It simply does nothing.
Furthermore if the "Seller Detail" form was submitted, i want to save the sellers data. But TryUpdateModel is always false, even if i use the second parameter which enables the prefix for ViewModels.
[HttpPost]
public ActionResult EditUser(string btnSubmit, FormCollection formValues, EditSellerViewModel editSellerViewModel)
{
int uid = baseFunc.GetIdForUsername(User.Identity.Name);
var seller = bmDBCont.SellerSet.Single(s => s.Id == uid);
if (btnSubmit == "saveSellerPassword")
{
seller.Password = editSellerViewModel.ChangePasswordModel.NewPassword;
bmDBCont.ObjectStateManager.ChangeObjectState(seller, System.Data.EntityState.Modified);
bmDBCont.SaveChanges(); //<-- does nothing
}
if (TryUpdateModel(seller, "Seller")) //<-- never true
{
bmDBCont.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Titles = CommonListsProvider.GetTitles();
ViewBag.Countries = CommonListsProvider.GetCountries();
return View(editSellerViewModel);
}
Here some debug info screenshots:
formcollection with seller form submitted
formcollection with password form submitted
Please can anyone help me?
See the documentation about TryUpdateModel, its says "Updates the specified model instance using values from the controller's current value provider and a prefix."
The Prefix to use when looking up values in the value provider.
Try use TryUpdateModel(seller) simple method without the "prefix" parameter.
if(TryUpdateModel(seller))
http://msdn.microsoft.com/en-us/library/dd493137(v=vs.108).aspx
Related
suppose I have the following class
class A
{
[Required]
public string Name {get; set;}
[Required]
public string NickName {get; set;}
[Required]
public string UserId {get; set;}
}
and from the form I am passing only the Name and NickName to controller and before checking the model state simply I assign the user id to the UserId property as below
[HttpPost]
public IActionResult Save(A model)
{
model.UserId = User.GetLoggedInUserId<string>();
if (!ModelState.IsValid)
{
return View(model);
}
}
even though I have assign the user id before checking the model state it still returns the validation state false and complaining the for the user id. one way to come out of this problem is to create a view model which makes things more complex, because of assigning the values from view model to class it self.
any idea how to solve this.
Note: the question is not only for the User Id in the class there maybe other properties as well that may not be passed from the form to controller and the values maybe assigned to them from controller
You could try to remove the 'UserId' from the model validation before calling ModelState.IsValid. Code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId"); //using Remove method to remove the specified object from the model-state dictionary.
var state2 = ModelState.IsValid; // true
a.UserId = "SN1001";
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The screenshot as below:
Besides, you could also try to use the TryValidateModel() method to validate the model again in the controller, code like this:
[HttpPost]
public IActionResult CreateA(A a)
{
var state1 = ModelState.IsValid; // false
ModelState.Remove("UserId");
a.UserId = "SN1001";
if (!TryValidateModel(a, nameof(a)))
{
var state2 = ModelState.IsValid;
}
if (ModelState.IsValid)
{
var data = a.UserId;
}
return RedirectToAction(nameof(Index));
}
The result like this:
Reference: Model State Rerun validation
Edit
[Note] If the ModelState.IsValid is false first, before rerun validation using the TryValidateModel method, we have to remove the error from the ModelState.
You can also pass the value of UserId field using a hidden field with a default value, like:
#Html.HiddenFor(m => m.UserId, new { #Value = User.Identity.Name });
This answer might help What does ModelState.IsValid do?
ModelState.IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process.
There is no way to solve this. If you don't need this validation, then remove it altogether with the attributes and add the appropriate value handling logic with code.
The isValid will just validate the state on binding time and that's that.
I have a problem with validation of one view with multiple ViewModels. My situation is, that I have one Basic form, which is same for lot of pages. By ID parameter, I render new external fields to this Basic form. These external fields are type of ActionResult, using own ViewModel and own Controller. In Main controller on Post action I want to control if ModelState.IsValid, but I have problem - it validate all ViewModels of all external fields, but I want to validate only ViewModel of active external fields (and Basic form too).
It looks like this:
ViewModel of all view models
public class AllFieldsVm
{
public BasicFormVm BasicFormVm { get; set; }
public ExternalFieldXyVm ExternalFieldXyVm { get; set; }
public AnotherExternalFieldVm AnotherExternalFieldVm { get; set; }
}
In controller of external fields I create new instance of AllFieldsVm and in this create new instance of ExternalFieldXyVm (if I need, I prefill these fields). This I render whitout layout like partial view (using #{Html.RenderAction("Action", "Controller", new {#someOptionalData = value});} ), when some condition is true.
In controller of Basic form on Post action I have something like this and I want to use something like this code if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)):
[POST("someurl-id{someId}")]
public ActionResult SaveFormData(int someId, AllFieldsVm model)
{
//Here I want something like
//if (ModelState.IsValid(model.BasicFormVm) && ModelState.IsValid(model.ExternalFieldXyVm)) or something like that...
var se = new SomeEntity();
se.property1 = model.property1;
se.property2 = model.property2;
using (var dbc = _db.Database.BeginTransaction())
{
try
{
_db.Add(se);
_db.SaveChanges();
//My Condition - when save external data
if (someId == (int) MovementTypes.SomeEnumInt)
{
var rd = new ExternalFieldEntity
{
PropertyA = se.property0,
PropertyB = Convert.ToDateTime(model.ExternalFieldXyVm.SomeExternalFieldName)
};
_db.Add(rd);
_db.SaveChanges();
}
dbc.Commit();
}
catch (Exception)
{
dbc.Rollback();
}
}
return RedirectToAction("Action", "Controller");
}
So, my question is, how can I validate ExternalFieldXyVm separatly based on some conditions?
Is it possible, or I have to create all own validators, without using basic DataAnnotations or FluentValidation? I have no experience with these types of forms, so please be patient...
Thanks to all for help!!
Great, I got it. I play with this for two days, don't know how it is possible that I didn't see that.
Result is: When view with own view model which is included in main viewmodel, isn't rendered into view, this viewmodel is not validate on post action. So my Basic form is validate everytime, and ExternalFields are validate only when are rendered. So sorry, for so stupid question....
I've been searching around and I'm not able to find an answer on what seems like a simple requirement:
With MVC Data Annotation validation, can you show the validation message ('must be a string with a maximum length of 5') in the validation summary or next to field, but clear the value of the text box (when validation fails).
I've tried to use ModelState.Clear() and ModelState.Remove("CompanyName"), but this clears both the value and validation message (validation state).
I'm asking this because recently we had a penetration test and one of the recommendations was to not pre-populate secure values (credit card number etc) if validation fails. This is obviously a minor issue, but the recommendation was to not send the value back across the internet (from the server) if we didn't have to.
Here is the code I'm working with:
public ActionResult Edit()
{
return View();
}
[HttpPost]
public ActionResult Edit(CompanyInput input)
{
if (ModelState.IsValid)
{
return View("Success");
}
//ModelState.Clear // clears both the value and validation message
//ModelState.Remove("CompanyName") // same result
return View(new CompanyInput());
}
And the view model:
public class CompanyInput
{
[Required]
[StringLength(5)]
public string CompanyName { get; set; }
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
}
And the view:
#model Test.Models.CompanyInput
<h2>Edit</h2>
#using (Html.BeginForm("Edit", "Company"))
{
#Html.EditorForModel()
<button type="submit">Submit</button>
}
The ModelState of each field holds more than just the value, so removing it from the collection outright removed your error message as expected. I believe you should be able to clear just the value however, by doing something like.
ModelState["CompanyName"].Value = null;
EDIT: Upon closer inspection I found that the Value property is of type ValueProviderResult, simply nulling it doesn't give the desired result, and because the properties of this class appear to be getters only you have to replace the instance with your own. I've tested the following and it works for me.
ModelState["CompanyName"].Value = new ValueProviderResult(string.Empty, string.Empty, ModelState["CompanyName"].Value.Culture);
Because the ModelState isn't valid, you will either have to create a custom validator or a jQuery ajax/json call to determine if the data needs to be cleared or not.
Just changing the model property to string.Empty or something like that won't do the trick because the entire view gets re-rendered with the previous successful posted model but with the ModelState validation errors.
Yes you can add error message like this
[Required(ErrorMessage = "must be a string with a maximum length of 5")]
Update after clarity from OP:
To clear e.g. Input.Field = string.Empty;
You can create a custom validation class which is inherited from ValidationAttribute class
The following link gives a clear idea about how to implement custom validation class suitable for your problem.
Custom Data Annotation
I was wondering what the best way to approach this problem in ASP.NET MVC would be. The following is a trivial example of what I'd like to be able to do:
I have a webpage with textbox and a submit button. When the submit button is pressed the I would like the contents to be displayed on the same webpage. When it is pressed again I would like what was already displayed from the first submission to be displayed as well as the new data that was just submitted.
I have tried saving this data to a model, but the model is wiped clean every time the form posts. How could I do this and keep the data from the post before the last one (and the post before that)?
If you want data to persist between requests, as a starting point I would use 'TempData'. The TempData property value is stored in session state and exists until it is read or until the Session expires.
Example ViewModel:
public class SomeClass
{
public string Something { get; set; }
public List<string> RetainedValues { get; set; }
}
Example Controller:
[HttpGet]
public ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult Index(SomeClass postedValues)
{
// retrieve retained values
var retained = (List<string>) TempData["RetainedValues"] ?? new List<string>();
retained.Add(postedValues.Something);
// save for next post
TempData["RetainedValues"] = retained;
// setup viewmodel
var model = new SomeClass
{
RetainedValues = retained
};
return View("Index", model);
}
Example View (strongly typed):
<div>
#foreach(var item in Model.RetainedValues)
{
<div>#item</div>
}
</div>
#using(Html.BeginForm())
{
#Html.EditorFor(m=>m.Something)
<input type="submit"/>
}
Just put an hidden field for your model property then your previews value will be loaded on it and passed it back to the next post.
Ex.: #Html.HiddenFor(model => model.YourProperty)
So knowing that you could have two properties ex.: one named newValue and other called allValues.
the allValues you use it with an hidden field and your newValue you use to insert the new ones. So on post you just add the newValue to the allValues.
Something like that:
model.allValues += newValue;
--UPDATE
Or you can use session or tempdata as mentioned by #Jesse
For this case I would prefer to use hidden fields as it has a lower complexity and its data didnt need be secure as it will be shown to the user anyway.
I'm using ASP.NET MVC 3 code-first and I have added validation data annotations to my models. Here's an example model:
public class Product
{
public int ProductId { get; set; }
[Required(ErrorMessage = "Please enter a name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a description")]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required(ErrorMessage = "Please provide a logo")]
public string Logo { get; set; }
}
In my website I have a multi-step process to create a new product - step 1 you enter product details, step 2 other information etc. Between each step I'm storing each object (i.e. a Product object) in the Session, so the user can go back to that stage of the process and amend the data they entered.
On each screen I have client-side validation working with the new jQuery validation fine.
The final stage is a confirm screen after which the product gets created in the database. However because the user can jump between stages, I need to validate the objects (Product and some others) to check that they have completed the data correctly.
Is there any way to programatically call the ModelState validation on an object that has data annotations? I don't want to have to go through each property on the object and do manual validation.
I'm open to suggestions of how to improve this process if it makes it easier to use the model validation features of ASP.NET MVC 3.
You can call the ValidateModel method within a Controller action (documentation here).
ValidateModel and TryValidateModel
You can use ValidateModel or TryValidateModel in controller scope.
When a model is being validated, all validators for all properties are
run if at least one form input is bound to a model property. The
ValidateModel is like the method TryValidateModel except that the
TryValidateModel method does not throw an InvalidOperationException
exception if the model validation fails.
ValidateModel - throws exception if model is not valid.
TryValidateModel - returns bool value indicating if model is valid.
class ValueController : Controller
{
public IActionResult Post(MyModel model)
{
if (!TryValidateModel(model))
{
// Do something
}
return Ok();
}
}
Validate Models one-by-one
If you validate a list of models one by one, you would want to reset ModelState for each iteration by calling ModelState.Clear().
Link to the documentation
//
var context = new ValidationContext(model);
//If you want to remove some items before validating
//if (context.Items != null && context.Items.Any())
//{
// context.Items.Remove(context.Items.Where(x => x.Key.ToString() == "Longitude").FirstOrDefault());
// context.Items.Remove(context.Items.Where(x => x.Key.ToString() == "Latitude").FirstOrDefault());
//}
List<ValidationResult> validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(model, context, validationResults, true);
if (!isValid)
{
//List of errors
//validationResults.Select(r => r.ErrorMessage)
//return or do something
}
I found this to work and do precisely as expected.. showing the ValidationSummary for a freshly retrieved object on a GET action method... prior to any POST
Me.TryValidateModel(MyCompany.OrderModel)