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

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/

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 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.

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.

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

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