Validate model data - c#

I have an issue regarding validating my model, depending on which text fields have a value. I have a simple model:
public class Person
{
[DisplayName("Forename")]
[Required(ErrorMessage = "Please enter a forename")]
public string Forename { get; set; }
[DisplayName("Surname")]
[Required(ErrorMessage = "Please enter a surname")]
public string Surname { get; set; }
[DisplayName("Country")]
[Required(ErrorMessage = "Please enter a country")]
public string Country { get; set; }
[DisplayName("Phone Number")]
[Required(ErrorMessage = "Please enter a phone number")]
public string Phone { get; set; }
[DisplayName("Mobile Number")]
[Required(ErrorMessage = "Please enter a mobile number")]
public string Mobile { get; set; }
}
In my view I display Forename, Surname, Country & Phone as text fields using the following code:
#Html.LabelFor(x => x.Forename)
#Html.TextBoxFor(x => x.Forename)
#Html.ValidationMessageFor(x => x.Forename)
If a user doesn't have a phone number, they click on a button and this reveals another text field for Mobile number. The "Phone" text field then gets reset. If the user enters a mobile number and then submits the form it fails. Is it possible to have a conditional statement in my model validation to only validate a property if another property doesn't have a value.
So if "Mobile" has a value, but "Phone" doesn't, validation will validate "Mobile" but ignore "Phone" and vice versa. Apologies if the last paragraph wasn't clear enough. Any help will be greatly appreciated.

You could always do the validation in the controller, Pseudo-code below
if (Condition)
{
ModelState.AddModelError("PropertyNameHere", "ErrorMessageHere");
}
Keep your view exactly as it is. Just remove the [Required()] tag from your model. You also want to add this code above the if (ModelState.IsValid) code.

You could do it manually with a simple check like:
if (ModelState.ContainsKey("Phone Number") && !ModelState.ContainsKey("Mobile Number"))
ModelState.Remove("Phone Number");
else if (!ModelState.ContainsKey("Phone Number") && ModelState.ContainsKey("Mobile Number"))
ModelState.Remove("Mobile Number");

Related

C# asp.net data annotation setting field to only number but not required

I have a viewmodel class which I set to validate with data annotations in my project like this:
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid minimum price (e.g. 20.00)")]
public double MinPrice { get; set; }
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid maximum price (e.g. 20.00)")]
public double MaxPrice { get; set; }
What I would like to do is to make the field trigger validation only if something is entered, and that something is not a valid double number... If the field is left empty I'd just like to ignore it...
The way I did it now if I enter firstly something and then try to leave the field empty, the validation triggers and says the field is required, which is not what I want...
What am I doing wrong here?
[RegularExpression(#"-?\d+(?:\.\d+)?", ErrorMessage = "Please enter valid minimum price (e.g. 20.00)")]
[Range(0, double.MaxValue, ErrorMessage = "Please enter valid minimum price (e.g. 20.00)")]
public double? dub { get; set; }
Change your model to double? if you are going to allow nulls
You should use 'RegularExpression' instead of 'range'. Something like:
[RegularExpression(#"^-?[0-9]\d{0,2}(\.\d{0,1})?$", ErrorMessage = "Invalid value")]

C# Passing params to another view

I have a set up where a Company can have none or one or many clients. So there is no strict relationship between the Client table and the Company table. I have created a Search view where all companies are populated. Then using a button a client can be attached to the company. I thought using an ActionLink I would be able to achieve this, so my Search (view) has,
#Html.ActionLink("Book", "Book", new { id = a.CompanyId })
Where the Model is looped over to get all the company list. Now when I click the link, it populates the Address with the right params, Companies/Book/1 the ID I am playing with is 1. Which is correct, however the View I am landing at is a new Customer Model.
public class CustomerModel
{
[HiddenInput(DisplayValue = true)]
public long CompanyId { get; set; }
[HiddenInput(DisplayValue = false)]
public long CustomerId { get; set; }
[Display(Name = "Customer Name")]
[Required(ErrorMessage = "* required")]
public string CustomerName { get; set; }
[Display(Name = "Address Line 1")]
[Required(ErrorMessage = "* required")]
public string AddressLine1 { get; set; }
[Display(Name = "Postcode")]
[Required(ErrorMessage = "* required")]
public string Postcode { get; set; }
[Display(Name = "Phone Number")]
[Required(ErrorMessage = "* required")]
[RegularExpression(#"\d*", ErrorMessage = "Not a valid phone number")]
public string PhoneNo { get; set; }
}
Even though I am able to see the ID being passed (using FireBug) is 1, somehow when I click the button to submit the view to the controller I get a 0. Why would this be? Could anyone help me?
EDIT - 1
This is the controller.
public ActionResult Book()
{
return View(new CustomerModel());
}
[HttpPost]
public ActionResult SaveCustomer(CustomerModel model)
{
_companyService.SaveCustomer(model);
return RedirectToAction("Index");
}
I have tried using the CompanyId instead of id, it came up with another error.
Before submitting the Form, Address bar has : http://localhost:53294/Companies/Book?CompanyId=1
After submitting the Form, Address Bar has : http://localhost:53294/Companies/SaveCustomer
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Customer_dbo.Company_CompanyId". The conflict occurred in database "BoilerServicing", table "dbo.Company", column 'CompanyId'.
The statement has been terminated.
The save method by itself,
public void SaveCustomer(CustomerModel customer)
{
using (var db = new BoilerServicingDbContext())
{
Customer entity;
if (customer.CustomerId > 0)
{
entity = db.Customers.First(x => x.Id == customer.CustomerId);
}
else
{
entity = new Customer();
db.Customers.Add(entity);
}
entity.Name = customer.CustomerName;
entity.TelephoneNumber = customer.PhoneNo;
entity.AddressLine1 = customer.AddressLine1;
entity.PostCode = customer.Postcode;
entity.CompanyId = customer.CompanyId;
db.SaveChanges();
}
}
Okay, after trying so many ways, I have come to this. I changed the Action method on the controller.
public ActionResult Book(long id)
{
return View(new CustomerModel
{
CompanyId = id
});
}
This seems to have passed in the CompanyId I am passing into the Book view. Took me a while, but I got there. Thanks for all your help !

Is it possible to set the ValidationAttribute.ErrorMessage value when an object is instantiated?

I have a class with a Required attribute and an ErrorMessage:
public class Color
{
[Required(ErrorMessage = "Please Select Color")]
public int Id { get; set; }
public string Name { get; set; }
}
I'm using objects of the class in two different dropdowns and would like to change the validation error message that is displayed to be specific to the dropdown. So for one dropdown the message would be "Please Select New Color" and for the other dropdown the message would be "Please Select Old Color".
So when I create an object as shown below, can I also set the ErrorMessage on the Required Attribute?
Fruit newFruit = new Fruit()
{
Id = 1,
Name = "Apple"
}
Values passed to attributes must be known to the compiler at compile
time.
source: MSDN
So I guess this way you cannot, but you can change the errormessage in other places, or use an ErrorMessage property in your class.
Suggestion 1:
// in class
[Required(ErrorMessage = "Please select {0} color")]
public int Id { get; set; }
//gui
string errormessage = ""; //get errormessage here
string state = "old" //get state;
errormessage = string.Format(errormessage, state);
//set errormessage
Suggestion 2:
How to customize validation attribute error message?

Ignore REGEX in MVC model or Select another REGEX depending upon User input

I have two fields in one of my class for address like
public string Country { get; set; }
[Required(ErrorMessage = "Postcode is required")]
[RegularExpression(#"REGEX",
ErrorMessage = "Please enter a valid UK Postcode)]
public string postcode { get; set;}
However if the user selects a country other than UK then I want my Postcode field to ignore the REGEX atleast and in an ideal world validate using another REGEX depending upon the country. Can anyone suggest if this is possible in the model itself?
There are a few different options for you:
Create a 100% custom validation attribute that combines the Required and RegularExpression attributes to your needs. So inside that custom attribute you would do all the validation that you need and compare the value to the Country property to selectively apply the RegEx as needed.
Create a different postcode attribute per country that you care about and use something like the `RequiredIfAttribute (see RequiredIf Conditional Validation Attribute) to determine which one is actually required. You can then use Javascript to show/hide the appropriate input fields.
You can do this with IValidatableObject:
class MyClass : IValidatableObject {
public string Country { get; set; }
[Required(ErrorMessage = "Postcode is required")]
public string postcode { get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
if (!String.IsNullOrEmpty(Country)
&& !String.IsNullOrEmpty(postcode)) {
switch (Country.ToUpperInvariant()) {
case "UK":
if (!Regex.IsMatch(postcode, "[regex]"))
yield return new ValidationResult("Invalid UK postcode.", new[] { "postcode" });
break;
default:
break;
}
}
}
}

#Html.DropDownListFor fails in partial view but not in Full Page View

I am having an issue where my PartialView DropDownListFor gets the error:
The ViewData item that has the key PlanId is of type System.int32 but must be of type
IEnumerable<SelectListItem>
#Html.DropDownListFor(model => model.PlanId, (SelectList)ViewBar.PlanNameSelectList, new {#class = "short" })
This error does not pop up when I go to the page that originally held this code. What I have done is gutted the core part of the code which has worked previously with another partialView, as long as I took out the DropDownListFor elements in the code. I did not need them for that partialView, but now that I need them the problem has come full circle.
I would greatly appreciate any help that can be given to me to help solve this problem. Other resources like calls to the partial are below
#Html.Partial("location", new MAO.Models.ViewModels.CreateTemplateModel{})
This is the model
public class CreateTemplateModel {
[Required(ErrorMessage = "{0} is required.")]
[RegularExpression("^[0-9]+$", ErrorMessage="Template Id can only contain numbers")]
[Display(Name = "Template ID")]
public string TNumber { get; set; }
[Required(ErrorMessage = "{0} is required.")]
[RegularExpression("^.[0-9]{4}(-[0-9]{3})?$", ErrorMessage = "H# Must follow either #XXXX or #XXXX-XXX pattern")]
[Display(Name = "HNumber")]
public string HNumber { get; set; }
[RequiredIfOtherIsEmpty("NewPlanName", ErrorMessage = "Please enter a Plan Name")]
[Display(Name = "Select Existing Plan Name")]
public int PlanId { get; set; }
[MaxLength(500, ErrorMessage="{0} can't be longer than 500 characters")]
[Display(Name = "Enter New Plan Name")]
public string NewPlanName { get; set; }
[RequiredIfOtherIsEmpty("NewParentOrganization", ErrorMessage = "Please enter a Parent Organization")]
[Display(Name = "Select Existing Parent Organization")]
public string ParentOrganization { get; set; }
[MaxLength(500, ErrorMessage = "{0} can't be longer than 500 characters")]
[Display(Name = "Enter New Parent Organization")]
public string NewParentOrganization { get; set; }
[Required(ErrorMessage = "{0} is required.")]
public int TemplateTypeId { get; set; }
}
There is a controller that is pretty long so I am not going to post that. If there are parts of the controller that would be helpful I can post those parts as well as anything else that I might have forgotten to include
Based on your comments, I'm suspecting that you're never rebinding your drop down list when you are returning your partial view. Your controller action for the partial should be building your dropdown list in an identical manner to the controller action that renders the full view. Compare the two and make sure that they match.
UPDATE: Your partial view action should look something like the following:
public ActionResult Location()
{
ViewBag.PlanNameSelectList = new SelectList(plans.Distinct(), "Id", "Name", plans.FirstOrDefault(plan => plan.Name == selectedPlan));
attachSelectLists(ViewBag);
return PartialView("Location");
}
What you are currently doing with
#Html.Partial("location", new MAO.Models.ViewModels.CreateTemplateModel{})
Is rendering the partial view "location" using a NEW CreateTemplateModel object, not an existing one. Instead, a better way to do it is to duplicate your controller actions. Create a new one specifically for your partial view (this is a simpler use case for now).
public ActonResult TestPartialView()
Instead of using #Html.Partial which renders a partial, try calling your new controller action instead, which will build your drop down list for you.
#Html.RenderAction("TestPartialView").
This will call your new controller action and render the partial on the page, preserving the controller logic. If you use #Html.Partial, it simply renders the partial view passing in whatever object you give it which, in this case, is a new, empty CreateTemplateModel.

Categories