This is weird, or at least i think. I have a simple password change form, which has a control for taking the user input for the password. Now, the problem is whatever the user enters, the view only posts back an empty string.
This is the control on my view
#Html.PasswordFor(model => model.Password)
Model attribute:
[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 10)]
[Display(Name = "Password")]
public string Password { get; set; }
Code:
[HttpGet]
public ActionResult Change(int userid)
{
try
{
var model = GetUser(userid);
return View(model);
}
catch (Exception e)
{
//log error
throw;
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Change(User model)
{
try
{
model = ChangeUserCredentials(model); //break point here
return View(model);
}
catch (Exception e)
{
//log error
throw;
}
}
What I have tried:
removed caching in IE, firefox and chrome
tried changing 'passwordfor' to 'editorfor'
fwiw, it was working fine until a few days back, nothing changed in the view or the code in the controller.
add a parameter to your password helper like this:
#Html.PasswordFor(model => model.Password,new{#name="mypassword"})
then add this parameter to your action method
public ActionResult Change(User model,string mypassword)
this will work! but it's not a really good way.
if you want to do it right you must be sure you did Model Binding right.
I know this sounded stupid. But this is what fixed the issue, just in case anyone else faces the same:
1) Per this post , I added new { value = Model.CurrentPassword }) to the password attribute in the view
2) I am deliberately setting the password to 'null' in the GET method
Related
I'm working on a website, and just trying to get my head around the general structure. I have a database in the background that I'm accessing using the "Repository Pattern". I have the below code in my UserRepository class:
public bool IsValid(User user)
{
if (_context.Users.Any(c => c.EmailAddress == user.EmailAddress))
{
Message = "Email address already in use";
return false;
}
return true;
}
And that is implemented here
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Register(User user)
{
if (ModelState.IsValid)
{
var context = new Context("DatabaseContext");
var userRepo = new UserRepository(context);
if (userRepo.IsValid(user))
{
userRepo.Add(user);
// return to different view
}
else
{
// display userRepo.Message on page
return View(user);
}
}
return View(user);
}
My problem is I don't think I'm going around the "message" bit correctly, but I can't find anything online to help me out (specifically around repository). I think I should be changing the return type of IsValid to something like a result (as I've seen with dialog boxes), but again, I'm not sure.
Any help would be much appreciated.
Thanks.
One way to achieve this is exactly like you have suggested - change the return type of IsValid.
In the past I have had similar validation methods in my "business layer" that return a collection of ValidationResult, where each returned record accounts for a validation error within the model and an empty collection would be interpreted as a valid model.
For example:
public class ValidationResult
{
public string FieldName { get; set; }
public string Message { get; set; }
}
public interface IValidator<T>
{
IEnumerable<ValidationResult> IsValid(T model);
}
//...in your implementation
public IEnumerable<ValidationResult> IsValid(User user)
{
//Return a new ValidationResult per validation error
if (_context.Users.Any(c => c.EmailAddress == user.EmailAddress))
{
yield return new ValidationResult
{
Message = "Email address already in use",
FieldName = nameof(user.EmailAddress)
};
}
}
This could then be interpreted by your presentation layer to feedback to the user.
I agree with what you mentioned in question. Returning custom class will be better approach IMO.
public ValidationResult IsValid(User user)
{
ValidationResult validationResult = new ValidationResult(true, "");
if (_context.Users.Any(c => c.EmailAddress == user.EmailAddress))
{
validationResult.Status = false;
validationResult.Message = "Email address already in use";
return validationResult;
}
return validationResult;
}
This way, Status and Message give you all info needed. Check the status first. If it is false, check message for exact details.
Although the solution provided by above user is good but I don't think you are doing this in right way. This kind of validating email address solution has some cons.
Suppose If email is available(not valid because this is not validity) then you have to return view with error.And what if all input email address is available then the end user will get this error multiple time but the whole view will be rendered in every request and this is not good practice.Always try to use remote validation in this kind of scenario.
Use Remoteattribute on your model properties (email) and try to return availability message dynamically in view through JsonResult.I am giving you this demo example feel free to implement in your code.
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public JsonResult IsEmailAvailable(string emailAddress)
{
if (!_context.Users.Any(c => c.EmailAddress == emailAddress))
{
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json("Email address already taken" , JsonRequestBehavior.AllowGet);
}
The OutputCacheAttribute attribute is required in order to prevent ASP.NET MVC from caching the results of the validation methods.
And decorate your model Email property like this:
//inside you model.
[Required]
[Remote("IsEmailAvailable", "YourControllerName")]
[RegularExpression(#"Your email address validation regex here", ErrorMessage = "Email address is not valid .")]
[Editable(true)]
public string Email{ get; set; }
And add this snippet in the Web.config file to allow the use of the Remote attribute:
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
And remove following code from register action method.
if (userRepo.IsValid(user))
{
userRepo.Add(user); //write this only inside code cause you will always get valid email now.
// return to different view
}
else
{
// display userRepo.Message on page
return View(user);
}
I hope this will work. For more information please read this article. https://msdn.microsoft.com/en-us/library/gg508808(vs.98).aspx
I'm messing around with data annotations. When I click on a link to go to a page, the validation messages are being displayed, but I would like to have the validation messages not show unless data has been posted.
View:
#Html.TextBoxFor(m => m.EmailAddress, new { #placeholder = "Enter Email", #class = "form-control" })
#Html.ValidationSummary(true, "Registration Failed. Check your credentials")
#Html.ValidationMessageFor(m => m.EmailAddress, "You must enter a valid Email Address.")
Model:
[Required(ErrorMessage = "Email is required")]
[DataType(DataType.EmailAddress)]
[EmailAddress]
[Display(Name = "Email Address: ")]
public string EmailAddress { get; set; }
Controller:
[HttpGet]
public ActionResult AddUser()
{
return View();
}
[HttpPost]
public ActionResult AddUser(UserCreateViewModel user)
{
if (ModelState.IsValid)
{
var success = UserRepository.AddUser(user);
if (success)
{
return View("Success");
}
}
return View("AddUser");
}
Like I said, my problem occurs on page load of the AddUser view. When I click on the link to view the AddUser page, validation messages are showing after it loads, yet at this point no data has been posted and the model is empty.
You can clear model state after binding user:
ModelState.Clear();
This happens because ModelBinder will set ModelState on binding.
In every action that binds a model and returns a view with the same model you will have this problem.
[HttpPost]
public ActionResult AddUser(UserCreateViewModel user)
{
if (ModelState.IsValid)
{
var success = UserRepository.AddUser(user);
if (success)
{
return View("Success");
}
}
ModelState.Clear(); // <-------
return View("AddUser");
}
Set the validation style to:
.validation-summary-valid { display:none; }
So by default it's hidden. An error will trigger it to display.
.field-validation-valid {
display: none;
}
Whenever the validation triggers on page load, this ".field-validation-valid" value is automatically added to the class attribute of the triggered input element.
By adding CSS to display none as that particular class's value, you'll no longer see the validation messages on initial page load.
The validation messages will still display normally after the particular input element has been touched.
$('.field-validation-error').html("");
In a newly created MVC4 application, insert this function in the account controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AdminLogin(AdminLoginModel model, string returnUrl)
{
if (ModelState.IsValid && WebSecurity.Login("administrator", model.Password, persistCookie: model.RememberMe))
{
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The password provided is incorrect.");
return View(model);
}
and this
public class AdminLoginModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
is put into the accountModel.cs. I also created a new file AdminLogin.cshtml and leave it empty.
In the _loginPartial.cshtml file I insert an action link
<li>#Html.ActionLink("Register", "AdminLogin", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
But when I click that Register link, I then see the 404 error stating that
/Account/AdminLogin is not found.
I miss something along the way inserting that tiny mvc; could you help me ? I am a mvc beginner.
Clicking a link in a browser results in a GET request, but your action method is only available for POST requests.
Add the [HttpGet] attribute or remove the [HttpPost] attribute to resolve this particular problem.
In general you will want to keep using POST requests when submitting data. As such, my recommendation would be to change the client side to use a form (or use client side logic to intercept the link click action and submit the data using an ajax request).
C# valid ModelState fails .isValid
Search Controller
//
// GET: /Search/Create
public ActionResult Create() { return View(); }
//
// POST: /Search/Create
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.SearchSet = "test data";
search.URLParameter = 1432567389;
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
Search Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TalentProfile.Models
{
public class Search
{
public int Id { get; set; }
[Required]
public int URLParameter { get; set; }
[Required, MaxLength(50, ErrorMessage = "Client must be 50 characters or less")]
public string Client { get; set; }
[DataType(DataType.MultilineText), StringLength(150, ErrorMessage = "{0} must be {1} characters or less")]
public string Notes { get; set; }
[Required]
public string SearchSet { get; set; }
[Required]
public DateTime Created { get; set; }
}
}
If I run in debug all the fields in the class are properly set but it fails ModelState.IsValid. If I drill into the ModelState.IsValid it is false. Drilling down further I find the error “The SearchSet field is required”.
The SearchSet field is properly set to “test data”. If I remove the ModelState.IsValid check the save to the database succeeds.
Why am I getting the “field is required” error if the field contains valid data.
Talent Controller
//
// GET: /Talent/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Talent/Create
[HttpPost]
public ActionResult Create(Talent talent)
{
talent.Modified = talent.Created = DateTime.Now;
if (ModelState.IsValid)
{
_db.Talents.Add(talent);
_db.SaveChanges();
CreatePhoto(talent.Id);
return RedirectToAction("Index");
}
return View(talent);
}
Updated: Search Controller and Create View
//
// GET: /Search/Create
public ActionResult Create()
{
Search search = new Search();
search.SearchSet = "test Data";
return View(search);
}
//
// POST: /Search/Create
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.URLParameter = 1435267836;
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
In view put: #Html.HiddenFor(model => model.SearchSet)
There are a couple of things to note here:
1) putting a Required attribute on value types is pointless. Value types must always contain a value, they can't be null.. thus Required will always pass for them.
In particular, DateTime is a value type. Also, your UrlParameter is an int, which is also a value type. it will always contain at least a default value. Required is redundant.
2) As others have said, the ModelState is only set during databinding. data binding only occurs before the method is called, or when you call UpdateModel or TryUpdateModel. Regardless of whether or not you have other errors, you cannot update the model and expect the ModelState to reflect the changes if you have not called UpdateModel or TryUpdateModel.
3) Client is set as required, but you don't seem to be setting it. So if you set Client and SearchSet then do TryUpdateModel(search) it should be valid.
4) Talent works because, like in Search, talent.Modified and talent.Created are DateTimes and are non-nullable, thus they will never fail validation, even if you don't set a value (see for yourself, remove the assignment and see how it still validates). Any other required fields you may have on Talent are likely also value types.
The ModelState is resolved during model binding, so the Search object passed to Create has to have the SearchSet value set. I.e. setting SearchSet in the controller is not going to make the model valid if the value was empty during model binding.
Use TryUpdateModel(search); before your ModelState check. So it should be:
[HttpPost]
public ActionResult Create(Search search)
{
search.Created = DateTime.Now;
search.SearchSet = "test data";
search.URLParameter = 1432567389;
TryUpdateModel(search);
if (ModelState.IsValid)
{
_db.Searchs.Add(search);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(search);
}
The MVC runtime would've already validated the model before executing your action, and it hasn't revalidated it after you set the parameters locally in the action.
So the idea is you do a post with the search model in it, for example from an html form, and MVC checks validity BEFORE going into your action, so you don't have to :)
I have some code that saves a ticket in our system. If there is an error it does a RedirectToAction(). The problem is that I don't seem to have my errors in the new action. How can I fix this?
ModelState.AddModelError("_FORM", "Unable to save ticket");
ModelState.AddModelError("_FORM", "Phone number was invalid.");
ModelState.AddModelError("_FORM", "Lane number is required.");
return RedirectToAction("CreateStep", "Ticket");
I know some have suggested using TempData, but how would I get each error out of the ModelState?
Thanks.
The PRG pattern is ok, but I did this:
Base controller:
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (TempData["ModelState"] != null && !ModelState.Equals(TempData["ModelState"]))
ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
base.OnActionExecuted(filterContext);
}
Action (I'm using xVal):
try
{
user.Login();
AuthenticationManager.SignIn(user);
}
catch (RulesException rex)
{
// on bad login
rex.AddModelStateErrors(ModelState, "user");
TempData["ModelState"] = ModelState;
return Redirect(Request.UrlReferrer.ToString());
}
The action throws an exception, adds the ModelState to TempData and redirects back to the referrer. Since the action is caught, OnActionExecuted is still executed, but the first time around the ModelState is the same as TempData["ModelState"], so you don't want to merge with yourself. When the redirect action is executed, OnActionExecuted fires again. This time, if there's anything in TempData["ModelState"], it merges with this action's ModelState.
You could expand it to multiple models by using TempData["ModelState.user"] = ModelState and then merging every TempData object that starts with ModelState..
I know this thread is old, but this blog about ASP.NET Best Practices has some excellent suggestions.
#13 on the page deals with using 2 Action filters to save and restore ModelState between redirects.
This is the pattern that my work uses, and I love it.
Here's the simplified example:
[ImportModelStateFromTempData]
public ActionResult Dashboard()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post), ExportModelStateToTempData]
public ActionResult Dashboard(string data)
{
if (ValidateData(data))
{
try
{
_service.Submit(data);
}
catch (Exception e)
{
ModelState.AddModelError(ModelStateException, e);
}
}
return RedirectToAction("Dashboard");
}
this blog post describes how you could implement the PRG-Pattern in MVC
http://blog.simonlovely.com/archive/2008/11/26/post-redirect-get-pattern-in-mvc.aspx
hth
Use the TempData[] Collection
The tempdata is stored from one request to the next, then its gone.
What I did to maintain my ModelState no matter where I go with redirects is the following:
In your model, add:
public ModelStateDictionary modelstate { get; set; }
In your model's constructor, add:
this.modelstate = new System.Web.Mvc.ModelStateDictionary();
Sample Post with my model called Models.ContactInformation:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult contact(Models.ContactInformation con)
{
if (string.IsNullOrEmpty(con.SelectedAgencySelectorType))
{
ModelState.AddModelError("", "You did not select an agency type.");
}
con.modelstate = ModelState;
TempData["contact"] = con;
if (!ModelState.IsValid) return RedirectToAction("contactinformation", "reports");
//do stuff
return RedirectToAction("contactinformation", "reports");
}
So now your tempdata has your model and modelstate as is.
The following is my view that is agnostic to the state of anything, unless it has something. Here's the code:
[HttpGet]
public ActionResult contactinformation()
{
//try cast to model
var m = new Models.ContactInformation();
if (TempData["contact"] is Models.ContactInformation) m = (Models.ContactInformation)TempData["contact"];
//restore modelstate if needed
if (!m.modelstate.IsValid)
{
foreach (ModelState item in m.modelstate.Values)
{
foreach (ModelError err in item.Errors)
{
ModelState.AddModelError("", err.ErrorMessage.ToString());
}
}
}
return View(m);
}