Compare (password) attribute - asp.net-mvc-3

I'd like to create a view model for a new user using the code below. The "User" class contains just the two properties (simplified for now) that I will persist to the database; the view model adds a "compare password" field, which is only used in the view. I'd prefer to have the view model use the "User" class directly, rather than repeating all of the fields defined in "User".
My question is how do I properly reference "User.Password" in the [Compare] attribute for the "ComparePassword" field?
public class User
{
[Required]
public string UserName { get; set; }
[Required]
[DisplayName("Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public class NewUserViewModel
{
public User User { get; set; }
[Required]
[DataType(DataType.Password)]
[DisplayName("Re-enter Password")]
[Compare("Password", ErrorMessage="Passwords must match")]
public string ComparePassword { get; set; }
}
The HTML that gets generated for "Password" and "ComparePassword" is below.
<input class="text-box single-line password"
data-val="true"
data-val-required="The Password field is required."
id="User_Password"
name="User.Password"
type="password" value="" />
<input class="text-box single-line password"
data-val="true"
data-val-equalto="Passwords must match"
data-val-equalto-other="*.Password"
data-val-required="The Re-enter Password field is required."
id="ComparePassword"
name="ComparePassword"
type="password" value="" />
The key is how the "data-val-equalto-other" is handled by the Javascript. If I use "Password" or "User_Password" nothing happens - no check is performed. If I use "User.Password" the check is performed but always fails.
I have no real problem doing this directly in jQuery, but would prefer to use the [Compare] attribute if at all possible.

Just found the answer via StackOverflow and Microsoft Connect:
See:
http://connect.microsoft.com/VisualStudio/feedback/details/665793/jquery-unobtrusive-validate-equalto-fails-with-compare-attribute
and
JQuery 1.5 breaks Compare Validate (JQuery Validate 1.8)
To summerize, it looks like a bug in the jquery.validate.unobtrusive file that came with MVC3. The workaround is changing the following line in the jquery.validate.unobtrusive file.
element = $(options.form).find(":input[name=" + fullOtherName + "]")[0];
to
element = $(options.form).find(":input[name=" + fullOtherName.replace(".", "\\.") + "]")[0];
On Microsoft Connect, it says MS has fixed it, but i couldnt find the link to the new version. Anyways, this works for me in the meantime. Hope it helps

I fixed this issue using two fields and comparing on server (via unobtrusive JavaScript):
[Required(ErrorMessage = #"The new password is required")]
[StringLength(25, ErrorMessage = #"The new password must be at least {2} characters long", MinimumLength = 4)]
[DataType(DataType.Password)]
[Display(Name = #"New Password")]
public string NewPassword { get; set; }
[Required(ErrorMessage = #"The confirmation of password is required")]
[StringLength(25, ErrorMessage = #"The confirmation of new password must be at least {2} characters long", MinimumLength = 4)]
[DataType(DataType.Password)]
[Display(Name = #"Confirm New Password")]
public string ConfirmPassword { get; set; }
Server-side code:
[HttpPost]
public ViewResult ChangeUserPassword(ChangePasswordModel model)
{
Logger.Debug(LogBuilder.MethodEntry("ChangeUserPassword"));
if (model == null)
{
throw new ArgumentNullException("model");
}
if (model.NewPassword != model.ConfirmPassword)
{
ModelState.AddModelError("", Messages.ConfirmPasswordError);
return View(model);
}
if (ModelState.IsValid)
{
var changePasswordCompleted = false;
try
{
var userName = CurrentPerson.UserDetails.UserName;
var membershipUser = Membership.GetUser(userName);
if (membershipUser != null)
{
changePasswordCompleted = membershipUser.ChangePassword(model.OldPassword, model.NewPassword);
}
}
catch (Exception exception)
{
changePasswordCompleted = false;
Logger.Error(LogBuilder.LogMethodError("ChangeUserPassword", exception));
}
if (changePasswordCompleted)
{
return View("ChangePasswordCompleted");
}
}
ModelState.AddModelError("", Messages.ChangePasswordError);
return View(model);
}

Related

ASP.NET Core 2.2 Razor Pages - User Input Validation for IP Address

I am struggling to find an example or solution to validate user input on a Razor Page form control for an IP Address.
The IP Address entered could be any value but I just want to check/verify that the format entered is correct i.e. usual checks against too many digits, incorrect range for an octet beyond .254 etc.
I assumed there would be a built in validation attribute that I could add to the Model Class but unsure whether this would require a NuGet add on.
Correct me if I'm wrong but would assume validating this server side may be the better solution here and reduce code in the long run. But given this is more just for ensuring correct user input rather than being a security feature then am happy to explore all avenues, thanks in advance...
Model Class:
[Required]
[Display(Name = "IP Address")]
public string IpAddress { get; set; }
Razor Page:
<div class="form-group">
<label asp-for="ConnectorModel.IpAddress" class="control-label"></label>
<input asp-for="ConnectorModel.IpAddress" class="form-control" />
<span asp-validation-for="ConnectorModel.IpAddress" class="text-danger"></span>
</div>
After further testing I found I was only able to use the above solution for just a single view model instance, however my app requires the user input validation across multiple pages. Through trial and error I found changing the code as per below allowed me to use the same validation attribute class across multiple razor pages. Credit to Nan in helping me reach the final solution.
View Model:
[Required]
[IPAddressAttribute] // This calls the custom validation attribute class
[StringLength(15)] // No need for a message, custom attribute handles this.
[Display(Name = "IP Address")]
public string IpAddress { get; set; }
ValidationAttribute Class:
public class IPAddressAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string IpAddress = (string)value;
const string regexPattern = #"^([\d]{1,3}\.){3}[\d]{1,3}$";
var regex = new Regex(regexPattern);
if (string.IsNullOrEmpty(IpAddress))
{
return new ValidationResult("IP address is null");
}
if (!regex.IsMatch(IpAddress) || IpAddress.Split('.').SingleOrDefault(s => int.Parse(s) > 255) != null)
return new ValidationResult("Invalid IP Address");
return ValidationResult.Success;
}
}
In .NET Core, you can simply create a class that inherits from ValidationAttribute. You can see the full details in this doc .
Based on your requirement ,you can create the attribute like :
public class IPAddressAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
YourViewModel yourviewmodel = (YourViewModel)validationContext.ObjectInstance;
const string regexPattern = #"^([\d]{1,3}\.){3}[\d]{1,3}$";
var regex = new Regex(regexPattern);
if (string.IsNullOrEmpty(yourviewmodel.IpAddress))
{
return new ValidationResult("IP address is null");
}
if (!regex.IsMatch(yourviewmodel.IpAddress )|| yourviewmodel.IpAddress.Split('.').SingleOrDefault(s => int.Parse(s) > 255)!=null)
return new ValidationResult("Invalid IP Address");
return ValidationResult.Success;
}
}
And in your view model used like :
[IPAddressAttribute]
[Display(Name = "IP Address")]
public string IpAddress { get; set; }

ASP.NET MVC 2: Model Validation - username already taken?

I am following Scott Gu's blog: here
In his Blog he talks about client and server side validation.
How does one validate if username has already been taken and display this as a validation error message to the user?
In Scott's blog, this would be the same as validating if Title is unique:
public class Dinner
{
public int DinnerID { get; set; }
[Required(ErrorMessage = "Please enter a Dinner Title")]
[StringLength(20, ErrorMessage = "Title is too long")]
public string Title { get; set; }
[Required(ErrorMessage = "Please enter the Date of the Dinner")]
public DateTime EventDate { get; set; }
[Required(ErrorMessage = "Please enter the location of the Dinner")]
[StringLength(30, ErrorMessage = "Address is too long")]
public string Address { get; set; }
[Required(ErrorMessage = "Please enter your email address")]
[RegularExpression(".+\\#.+\\..+", ErrorMessage = "Please enter a valid email address")]
public string HostedBy { get; set; }
public virtual ICollection<RSVP> RSVPs { get; set; }
}
My first guess is that somehow this is done within the Model Controller, here:
//
// POST: /Home/Create
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
And because the Title is stored in a database server, this would be server side validation.
I know how to check if the Title is unique, but I do not know how to make the validation message appear in the View like it does using declaratives like [Required] or [StringLength()]. For example, here is how I can check for uniqueness:
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
foreach (var existingDinner in nerdDinners.Dinners)
{
if(existingDinner.Title == dinner.Title)
{
**// TODO: display validation error message?**
}
}
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
Using my imagination, and a magic wand, I would want to create a new declarative called [TitleIsUnique] that performs like the other validation declaratives.
Thank you in advance for your assistance.
You could create a custom attribute as mentioned and use IValidateObject but I prefer to add my errors to the ModelState in one of the layers in my application.
For this you can use ModelState.AddModelError
If you use ModelState.AddModelError("Title", "Title must be unique"); it will add an error to the Title field.
If you use ModelState.AddModelError("*", "Title must be unique"); it will add a general error message for the page.
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
if(nerdDinners.Dinners.Any(d => d.Title == dinner.Title))
{
ModelState.AddModelError("Title", "The title is not unique");
return View(dinner);
}
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
You are probably looking at implementing your own attribute derived from CustomAttribute. Take a look at this blog post http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx - it shows how to validate uniqueness. In the post IValidatableObject interface is used to perform validation but you should be able to the same by creating CustomAttribute.

How does one populate a displayed form using data from an associated database entry that is selected via a drop-down

I have the following code that allows a teacher to see a drop-down list of available courses to teach, listed by name. When a teacher selects a dropdown option I would like a form on the view to auto-populate with default values representing the selected course. What is the most efficient way to populate the fields?
note: When "Custom" is selected in the drop-down, I want the form that is displayed below the dropdown to have nothing but blank spaces.
CourseController
// GET: /Course/ApplyToTeach
public ActionResult ApplyToTeach()
{
var course = db.CourseTemplates;
var model = new ApplyCourseViewModel
{
Courses = course.Select(x => new SelectListItem
{
Value = x.Title,
Text = x.Title,
})
};
return View(model);
}
ApplyToTeachViewModel
public class ApplyToTeachViewModel
{
[Display(Name = "selected course")]
public string SelectedCourse { get; set; }
public IEnumerable<SelectListItem> Courses { get; set; }
}
ApplyToTeach (view) - note that all I have here currently is the drop-down menu, I am looking for the most efficient way to add the auto-populating form below this drop-down.
<h2>ApplyToTeach</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Apply To Teach</legend>
<div class="editor-label">
Which class would you like to teach? (select "Custom" if you would like to submit a customized class)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedCourse, Model.Courses, "Custom")
#Html.ValidationMessageFor(model => model.Courses)
</div>
<p>
<input type="submit" value="Submit" />
</p>
</fieldset>
}
The data for the drop-down fields comes from the following model -
CourseTemplates
public class CourseTemplates
{
public int CourseTemplatesID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public int AttendingDays { get; set; } // 10, 8, 3, or custom
public int AttendanceCap { get; set; } // default of 30
public string Location { get; set; }
public string Description { get; set; }
}
The form is actually going to be submitted as a "Course" model, not the "CourseTemplates" model, the "Course" model has more data fields than the "CourseTemplates" model - such as the following:
public DateTime StartDate { get; set; }
public bool Approved { get; set; }
etc. . .
What I have in mind as far as user-experience is that an administrator will go through beforehand and add in a number of possible course options, simply to ease the application process for most teachers (so they don't have to type every detail for every class they apply to teach), but I want the teacher to be able to edit any information before submitting the course for review by an administrator.
Any tips?

Dynamically add HTML5 input attributes from model

I'm trying to populate new HTML5 input attribute values into a Razor partial view. My model looks like this:
public class Answer
{
public int AnswerId { get; set; }
public string AnswerText { get; set; }
public int? Columns { get; set; }
public int? Maximum { get; set; }
public string Placeholder { get; set; }
}
My partial view looks like this:
#model Answer
#{Layout = null;}
#Html.TextBoxFor(x => x.AnswerText, new { #class="textbox", cols="#Model.Columns", max="#Model.Maximum", placeholder="#Model.Placeholder" })
Probably unsurprisingly this generates html that looks like this (I've left out some irrelevant attributes):
<input type="text" class="textbox" cols="#Model.Columns", max="#Model.Maximum", placeholder="#Model.Placeholder">
Whereas what I'm after is html that looks like this, but with whatever value happpens to be in the model:
<input type="text" class="textbox" cols="50", max="30", placeholder="Answer here">
I'm sure this is me having a Homer Simpson moment but I just can't get it to work.
Take out the quotes and use GetValueOrDefault on the nullable fields:
#Html.TextBoxFor(x => x.AnswerText, new { #class="textbox", cols=Model.Columns.GetValueOrDefault(), max=Model.Maximum.GetValueOrDefault(), placeholder=Model.Placeholder })

MVC3 Only posted form values retained?

I am using strongly typed views in an MVC3 web app. I've noticed that when a form is submitted, the ViewModel that is passed to the controller only has values for properties that have form elements associated with them. For instance, the example below shows a simple confirmation View with a checkbox and a phone number that the user must confirm before proceeding. When the form is submitted to the controller action, the UserConfirmed property contains a value, but the PhoneNumber property is null.
Is there any way for the ViewModel to retain all of its values or do I have to repopulate the ViewModel properties that do not have form elements associated with them?
The View
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
The Controller
[HttpPost]
public ActionResult ScheduleConfirmation(ScheduleConfirmationViewModel model)
{
if (model.UserConfirmed)
{
// add ViewModel data to repository
}
else
{
ModelState.AddModelError("ERROR", WebResources.strERROR_ConfirmSchedule);
}
return View(model);
}
Since your writing the phonenumber as output to the page it won't be automatically posted back (you've found out that part) What you can do is populate an hidden or read-only field with the phonenumber so that it will be posted back to your controller. An second option is to make a new call to your datasource and repopulate your object before saving it back to your datasource.
I generally POST back information like this in a hidden input. I personally use this heavily to pass data needed to return the user exactly where they where before pressing edit.
In your case it's as simple as
#model WebMeterReplacement.ViewModels.Appointment.ScheduleConfirmationViewModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(false)
#Html.CheckBoxFor(model => model.UserConfirmed)
<span>Please confirm before proceeding</span>
<div>
#Html.HiddenFor(m => m.PhoneNumber)
Phone Number: #Model.PhoneNumber
</div>
<input type="submit" value="Confirm"/>
For future reference:
If your passing complex objects back you need one hidden field per attribute (Hiddenfor does NOT iterate)
View
WRONG
#Html.HiddenFor(m => m.PagingData)
RIGHT
#Html.HiddenFor(m => m.PagingData.Count)
#Html.HiddenFor(m => m.PagingData.Skip)
#Html.HiddenFor(m => m.PagingData.PageSize)
Action
public HomeController(AViewModel Model)
{
PagingData PagingData = Model.PagingData;
Skip = PagingData.Skip;
}
If your passing Arrays you can do it like this
View
#if (Model.HiddenFields != null)
{
foreach (string HiddenField in Model.HiddenFields)
{
#Html.Hidden("HiddenFields", HiddenField)
}
}
Action
public HomeController(AViewModel Model)
{
String[] HiddenFields = Model.HiddenFields;
}
Well, the form will only POST elements that you have created. As you found out, simply writing the phone number out to the page will not suffice. The model binder can only bind those properties which exist in the posted data.
Generally you have a couple of options here:
1) You can create Input elements for all of the properties in your model, using visible elements (like a textbox) for those properties you want to edit, and hidden elements which should be posted back but have no UI
2) Post back a partial representation of your model (as you are doing now), read the entity back in from it's data source (I assume you're using some kind of data source, EF maybe) and then alter the properties of that entity with the ones from your form.
Both scenarios are common but it really depends on the complexity of your model.
I know this thread is a bit old, but thought I'd resurrect it to get feed back on my solution to this.
I'm in a similar situation where my objects are passed to a view, and the view may only display part of that object for editing. Obviously, when the controller receives the model back from the default model binder, and values not posted back become null.. and saving this means that a DB value becomes null just because it wasn't displayed/returned from a view.
I didn't like the idea of creating a model for each view. I know it's probably the right way... but I was looking for a reusable pattern that can be implemented fairly quickly.
See the "MergeWith" method... as this would be used to take a copy of the object from the database and merge it with the one returned from the view (posted back)
namespace SIP.Models
{
[Table("agents")]
public class Agent
{
[Key]
public int id { get; set; }
[Searchable]
[DisplayName("Name")]
[Column("name")]
[Required]
[StringLength(50, MinimumLength = 4)]
public string AgentName { get; set; }
[Searchable]
[DisplayName("Address")]
[Column("address")]
[DataType(DataType.MultilineText)]
public string Address { get; set; }
[DisplayName("Region")]
[Searchable]
[Column("region")]
[StringLength(50, MinimumLength = 3)]
public string Region { get; set; }
[DisplayName("Phone")]
[Column("phone")]
[StringLength(50, MinimumLength = 4)]
public string Phone { get; set; }
[DisplayName("Fax")]
[Column("fax")]
[StringLength(50, MinimumLength = 4)]
public string Fax { get; set; }
[DisplayName("Email")]
[RegularExpression(#"(\S)+", ErrorMessage = "White space is not allowed")]
[Column("email")]
[StringLength(50, MinimumLength = 4)]
public string Email { get; set; }
[DisplayName("Notes")]
[Column("notes")]
[DataType(DataType.MultilineText)]
public string Notes{ get; set; }
[DisplayName("Active")]
[Column("active")]
public bool Active { get; set; }
public override string ToString()
{
return AgentName;
}
public bool MergeWith(Agent a, string[] fields)
{
try
{
foreach (PropertyInfo pi in this.GetType().GetProperties())
{
foreach (string f in fields)
{
if (pi.Name == f && pi.Name.ToLower() != "id")
{
var newVal = a.GetType().GetProperty(f).GetValue(a,null);
pi.SetValue(this, newVal, null);
}
}
}
}
catch (Exception ex)
{
return false;
//todo: Log output to file...
}
return true;
}
}
}
And to use this in the controller.. you'd have something like..
[HttpPost]
public ActionResult Edit(Agent agent)
{
if (ModelState.IsValid)
{
Agent ag = db.Agents.Where(a => a.id == agent.id).ToList<Agent>().First<Agent>();
ag.MergeWith(agent, Request.Params.AllKeys);
db.Entry(ag).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(agent);
}
This way, during post back, it takes the object from the database, and updates it with object from view... but only updates the values that were posted back.. So if you have a field like "address" or something that doesn't appear in the view.. it doesn't get touched during the update.
I've tested this so far and i works for my purposes, tho i welcome any feedback as I'm keen to see how others have overcome this situation. It's a first version and i'm sure it can be implemented better like through an extension method or something.. but for now the MergeWith can be copy/pasted to each model object.
Yes, Just place hidden fields in the form for those values which you are not using and want to return to server control.
Thanks

Resources