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

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.

Related

ASP.NET Core 2.0 ModelState Filter before Validation?

Can I do something like this in ASP.NET Core 2.0?
For example, I have this ViewModel:
public class TodoViewModel
{
[Required(ErrorMessage = "Required !")] // <--- Required
public int? Key { get; set; } // Is Nullable
[Required(ErrorMessage = "Required !")] // <--- Required
public int? Value { get; set; } Nullable
[Required(ErrorMessage = "Required !")] // <--- Required
public byte Type { get; set; }
}
In View for Example, I have this:
#Html.BeginForm(FormMethod.Post)
{
#if (Model.Type == 1)
{
#Html.TextBoxFor(x => x.Key)
#Html.ValidationMessageFor(x => x.Key)
}
#if (Model.Type == 2)
{
#Html.TextBoxFor(x => x.Value)
#Html.ValidationMessageFor(x => x.Key)
}
}
and in Action I have this:
[HttpPost]
public IActionResult Todo(TodoViewModel model)
{
// My problem
// if model.type = 1 return ModelState has error validate for Property (Value) is Required
// and if model.type = 2 return ModelState has error validate for Property (Key) is Required
if (!ModelState.IsValid) // <-- Here is my problem
return View(model);
}
I do not want to use #Html.HiddenFor()
To override validation for other property
I tried to use ActionFilterAttribute:
public class TodoActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
TodoViewModel model = (TodoViewModel) context.ActionArguments["model"];
if (model.Type == 1)
model.Value = 0;
if (model.Type == 2)
model.Key = 0;
}
}
But unfortunately, Execute for (OnActionExecuting) is after ModelState is Validation.
Is there a specific way to do this?
I want to exclude ModelState validate for "Key" property if "Type" property equal 1,
and exclude ModelState validate for "Value" property if "Type" property equal 2
Implement the IValidatableObject Interface.
using System.ComponentModel.DataAnnotations;
public class TodoViewModel : IValidatableObject
{
public int? Key { get; set; }
public int? Value { get; set; }
[Required(ErrorMessage = "Required !")]
public byte Type { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Type == 1)
{
yield return
new ValidationResult(errorMessage: "Property (Value) is Required",
memberNames: new[] { "Value" });
}
if (Type == 2)
{
yield return
new ValidationResult(errorMessage: "Property (Key) is Required",
memberNames: new[] { "Key" });
}
}
}
This is a clear indication that you actually need two ViewModels. Either that or you do just a rough validation on the ViewModel and do a more detailed one in your business logic.
Of course you could write your own validation attribute (but not via ActionFilterAttribute but by inheriting from ValidationAttribute (Custom Validation Attribute) and putting it on the View Model instead of [Required])), which will check if one is required, but this essentially just covers/hides your intent that you have two different type of models.
Validation attributes aren't there to enforce business rules, they should be kinda simple and validate if the data looks good at the first look before the business validations are applied (which may required additional checks, services or database round-trips).
But remember if you go with the custom validation attribute, there won't be any client sided validation (via JavaScript in the browser). You'd also need to implement an AttributeAdapterBase<T> RequiredAttribute Example.

context.SaveChanges() works, but database is never updated? (MVC 3)

I've written a form in ASP.NET MVC3, and I can't get the entry to save the changes I make in the database, but while debugging, I noticed that the changes were reflected in the data context. I am experiencing no errors running this code. Let me know if you need more. Thanks!
Controller
[HttpPost]
public ActionResult Edit(Tool tool, FormCollection collection)
{
if (collection["Tool.Person.PersonID"] != "")
{
tool.Person= context.People.Find(
Convert.ToInt32(collection["Tool.Person.PersonID"])
);
}
if (collection["Tool.Company.CompanyID"] != "")
{
tool.Company = context.Companies.Find(
Convert.ToInt32(collection["Tool.Company.CompanyID"])
);
}
if (ModelState.IsValid)
{
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
The first two if statements are checking to see if the user inputted a person or company, and the information is passed via the FormCollection. PersonID and CompanyID are primary keys for Person and Company, respectively. I went through the method line by line multiple times and achieve the same result - after context.SaveChanges();, the context reflects the changes, but the database entries remain null for both Person_PersonID and Company_CompanyID.
Try using a view model and accessing the database after the user submits the form.
This should get you well on your way.
ViewModel
using System.ComponentModel.DataAnnotations;
namespace Project.ViewModels
{
public class _tools
{
[Required(ErrorMessage="ToolID is required")]
public int32 ToolID{ get; set; } //whatever ID you use to retrieve the Tool from the database.
[Required(ErrorMessage="PersonID is required")]
public int32 PersonID{ get; set; }
[Required(ErrorMessage="CompanyID is required")]
public int32 CompanyID{ get; set; }
}
}
Controller Post
[HttpPost]
public ActionResult Edit(_tool viewModel)
{
if (ModelState.IsValid)
{
Tool tool = db.GetTool(viewModel.ToolID) //whatever method you use to get a current version of the row. You already do this before you send the data to the client, so just copy that code
tool.Person = viewModel.PersonID
tool.Company = viewModel.CompanyID
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
View
#model = _tool
#using(Html.BeginForm("Edit", "ControllerNameHere", FormMethod.Post, null))
{
#Html.HiddenFor(model => model.ToolID)
#*Also add whatever inputs you use to get PersonID and CompanyID from the user.
Make sure to either use the #Html helpers or to give them names that correspond.
i.e. name one input PersonID and the other CompanyID*#
<input type="submit" value="Edit">
}

Multiple same fields from one ViewModel. MVC3

I want to be able to add multiple (zero or more) phones to my contact via it's ViewModel.
I'd write this:
public class ContactsViewModel
{
public string Skype { get; set; }
public string Email { get; set; }
public string Addr { get; set; }
public IEnumerable<PhoneViewModel> Phones { get; set; }
}
public class PhoneViewModel
{
public string Number { get; set; }
}
But, as expected, it does not work.
What is the best way to organize this?
EDIT. Here is my views:
#model RentSite.Web.UI.Models.ContactsViewModel
#{
ViewBag.Title = "AddContact";
}
<h2>AddContact</h2>
#using (Html.BeginForm())
{
#Html.EditorForModel(Model)
#Html.Action("AddPhone")
<input type="submit" value="Add"/>
}
and
#model RentSite.Web.UI.Models.PhoneViewModel
#Html.EditorForModel(Model)
Here is my Controller:
public ActionResult Index()
{
return View(unitOfWork.contactRepository.All().ToList());
}
[Authorize]
public ActionResult AddContact()
{
return this.View();
}
[Authorize]
public ActionResult AddPhone()
{
return PartialView();
}
[Authorize]
[HttpPost]
public ActionResult AddContact(ContactsViewModel contact, IEnumerable<PhoneViewModel> phones)
{
contact.Phones = phones;
return RedirectToAction("AddContact");
}
The rendering of multiple phone number will work as you expect. I'm guessing that you are having an issue "posting" multiple phone numbers.
Well the solution depends on what you exactly want to achieve. Here are two examples:
Solution 1: The user can only post one phone number at the time. Render all the existing phone numbers, and create a form based on PhoneViewModel.
Solution 2: The user can add many phone numbers and post them all at the same time. You need some Javascript here. Implement client side functionality allowing the user to "add" a phone number field. Using javascript, uniquely name each input field (i.e phone1, phone2, ... phonen). Use the same pattern server side to get all the phone numbers from the POST data.
There are more solutions. Please specify what you exactly would like to achieve.
Edit: Sample code for Solution 2
[HttpPost]
public ActionResult PostPhoneNumbers(FormCollection formCollection)
{
var phoneNumbers =
from k in formCollection.AllKeys
where k.StartsWith("phoneNumber")
select formCollection[k];
// validate and process the phone numbers
return View("your view");
}
enter code here

Model Validation / ASP.NET MVC 3 - Conditional Required Attribute

I'm having trouble with my ASP.NET MVC 3 application. I have 2 propertiesin my model whereby I only want 1 of them required in my view based on whichever one is empty. So for example, if I enter a phone number then email is no longer required and vice versa, but if I leave both empty, then either 1 should be required, below is my model:
[Display(Name = "Contact Phone Number:")]
[MaxLength(150)]
public string ContactPhoneNumber { get; set; }
[Display(Name = "Contact Email Address:")]
[MaxLength(100)]
public string ContactEmailAddress { get; set; }
Would I need to create a custom attribute to validate my model and if so, how would I achieve this?
You can implement IValidatableObject on your class and provide a Validate() method that implements your custom logic. Combine this with custom validation logic on the client if you prefer to ensure that one is supplied. I find this easier than implementing an attribute.
public class ContactModel : IValidatableObject
{
...
public IEnumerable<ValidationResult> Validate( ValidationContext context )
{
if (string.IsNullOrWhitespace( ContactPhoneNumber )
&& string.IsNullOrWhitespace( ContactEmailAddress ))
{
yield return new ValidationResult( "Contact Phone Number or Email Address must be supplied.", new [] { "ContactPhoneNumber", "ContactEmailAddress" } );
}
}
}
To get everything working at client side you'll need to add the following script to your view:
<script type="text/javascript">
$(function() {
$('form').validate();
$('form').rules('add', {
"ContactPhoneNumber": {
depends: function(el) { return !$('#ContactEmailAddress').val(); }
}
});
});
</script>
Annotation-based conditional validation can be defined using ExpressiveAnnotations:
[RequiredIf("ContactPhoneNumber == null",
ErrorMessage = "At least email or phone should be provided.")]
public string ContactEmailAddress { get; set; }
[RequiredIf("ContactEmailAddress == null",
ErrorMessage = "At least email or phone should be provided.")]
public string ContactPhoneNumber { get; set; }
Here is a MSDN blog entry about conditional validations: http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx
I know you already have a solution, but I had a similar situation, so maybe my solution will prove helpful to someone else. I implemented a custom attribute with client-side validation. Here is my blog post: http://hobbscene.com/2011/10/22/conditional-validation/

Compare (password) attribute

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);
}

Resources