Can I use data annotations to specify an optional object property that has required properties? - data-annotations

I have a simple member registration data model with a few required properties and some optional properties. The BillToAddress property is optional.
public class MemberRegistration
{
[Required(ErrorMessage = "First Name is required.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required.")]
public string LastName { get; set; }
[Required(ErrorMessage = "Email Address is required.")]
public string EmailAddress { get; set; }
public string PhoneNumber { get; set; }
[Required(ErrorMessage = "Password is required.")]
[MinLength(7, ErrorMessage = "Password must be at least 7 characters.")]
public string Password { get; set; }
private Address m_BillToAddress;
public Address BillToAddress
{
get
{
if (m_BillToAddress == null)
{
m_BillToAddress = new Address();
}
return m_BillToAddress;
}
}
}
If the user enters an optional bill to address, I would like the street, city, country, and postal code to be required. So I defined the Address data model, marking those properties as required.
public class Address
{
[Required(ErrorMessage = "Street Line 1 is required.")]
public string Street1 { get; set; }
public string Street2 { get; set; }
public string Street3 { get; set; }
public string Street4 { get; set; }
public string Street5 { get; set; }
[Required(ErrorMessage = "City is required.")]
public string City { get; set; }
[Required(ErrorMessage = "State or Province is required.")]
public string StateOrProvince { get; set; }
[Required(ErrorMessage = "Country is required.")]
public string Country { get; set; }
[Required(ErrorMessage = "Postal Code is required.")]
public string PostalCode { get; set; }
}
But now the model validation fails when I don't supply a bill to address. How can I annotate my models so that the bill to address is optional, but if it is supplied, then it has to include street, city, country, and postal code?

The problem was in MemberRegistration class. Because the BillToAddress property getter always returned a new, empty Address model, BillToAddress was never null and always correctly triggered the Address class validation. I changed BillToAddress to a simple auto-implemented property:
public Address BillToAddress { get; set; }
Now if I post a MemberRegistion without a BillToAddress, it passes validation because BillToAddress is an optional property.

Related

How to set validation message on dropdown list?

Here's the problem. I have a [Range] attribute above the SalesCenterId property of my ViewModel. On my view side I have dropdown list with a default option "---Select---" and some Sales Centers with different Id-s. For example, if I set the Range between 0 and 1, then when selecting a sales center with Id = 2, my custom error message works. But when i select the default option "---Select---" (id = 0 , range between 1 and 1000) then the default error message is displayed: "The value '---Select---' is not valid for SalesCenterId." Why is this? I've checked through debugging and saw that when choosing "---Select---" Id = 0.
I'm using MVC core 2.2
ViewModel:
public class OrderViewModel
{
[Required(ErrorMessage = "*The Last Name field is required.")]
public string LastName { get; set; }
[Required(ErrorMessage = "*The First Name field is required.")]
public string FirstName { get; set; }
[Required(ErrorMessage = "*The Middle Name field is required.")]
public string MiddleName { get; set; }
[Required(ErrorMessage = "*The Pin field is required.")]
public string Pin { get; set; }
[Required(ErrorMessage = "*The Phone field is required.")]
public string Phone { get; set; }
[Required(ErrorMessage = "*The Email field is required.")]
public string Email { get; set; }
[Required(ErrorMessage = "*The Serial Number field is required.")]
public string SerialNumber { get; set; }
[Range(1, 1000, ErrorMessage="jgsjfdngi")]
public int SalesCenterId { get; set; }
[Range(1, 999, ErrorMessage = "jgsjfdngi")]
public int OrderTimeId { get; set; }
[JsonIgnore]
[IgnoreDataMember]
public SalesCenter SalesCenter { get; set; }
public List<CartItem> Cart { get; set; }
public OrderTime OrderTime { get; set; }
public decimal TotalPrice { get; set; }
}
Validation message span:
<p><span asp-validation-for="SalesCenterId" class="error"></span></p>
Select list:
<td>
<select id="SCID" asp-for="SalesCenterId" asp-items="#ViewBag.SalesCenterId" class="input" style="width:200px; height:30px; padding:1.5%">
<option>---#Localizer["Select"]---</option>
</select><br />
</td>
I expect the output to be "jgsjfdngi", but the actual output is "The value '---Select---' is not valid for SalesCenterId."
Have you used ModelState.IsValid in your controller??
Try this
if (ModelState.IsValid)
{
// your code
return View();
}
return View();

How to define two classes for one DB table in MVC

I have one table np_user_dtls with 4 fields in sql server
user_name
user_last_name
arabic_name
nationality
In mvc i created a model class as below
public partial class np_user_dtls
{
[Display(Name = "First Name*")]
[Required(ErrorMessage = "First name is required.")]
public string user_name { get; set; }
[Required(ErrorMessage = "last name is required.")]
public string user_last_name { get; set; }
[Required(ErrorMessage = "arabic name is required.")]
public string arabic_name { get; set; }
[Required(ErrorMessage = "nationality is required.")]
public string nationality { get; set; }
}
and in context model i define the relationship between model class and db table as follow
public System.Data.Entity.DbSet<****.Models.np_user_dtls> np_user__dtls { get; set; }
now i need an another class in which arabic name is not mandatory
so i create a model like below
public partial class np_user_dtls_1
{
[Display(Name = "First Name*")]
[Required(ErrorMessage = "First name is required.")]
public string user_name { get; set; }
[Required(ErrorMessage = "last name is required.")]
public string user_last_name { get; set; }
[Required(ErrorMessage = "nationality is required.")]
public string nationality { get; set; }
}
but when i create an entry for this in db context like below
public System.Data.Entity.DbSet<****.Models.np_user_dtls_1> np_user__dtls { get; set; }
its giving me an error
The type ****.Models.***** already contains a definition for
np_user__dtls
is it not possible to define multiple model for a single table?
You can set one entity model for one table. then you can bind different view model with your entity model.
entity class.
public partial class np_user_dtls
{
public string user_name { get; set; }
public string user_last_name { get; set; }
public string arabic_name { get; set; }
public string nationality { get; set; }
}
you can use this class for database table.
public partial class np_user_dtls_response1
{
[Display(Name = "First Name*")]
[Required(ErrorMessage = "First name is required.")]
public string user_name { get; set; }
[Required(ErrorMessage = "last name is required.")]
public string user_last_name { get; set; }
[Required(ErrorMessage = "arabic name is required.")]
public string arabic_name { get; set; }
[Required(ErrorMessage = "nationality is required.")]
public string nationality { get; set; }
}
you can use this class as view model. accept request by this class. then map this class np_user_dtls and save information in db.
later you can use another view model class such as
public partial class np_user_dtls_response2
{
[Display(Name = "First Name*")]
[Required(ErrorMessage = "First name is required.")]
public string user_name { get; set; }
[Required(ErrorMessage = "last name is required.")]
public string user_last_name { get; set; }
[Required(ErrorMessage = "nationality is required.")]
public string nationality { get; set; }
}
bind this class again with class np_user_dtls and save information in db. In this you can use two kind of validation in application level save data in database. Previously you can set validation database level. thats why you cant set two class

TryValidateModel not assigning object, returning all nulls, MVCStore Example

In the Checkout Controller I have the code
[HttpPost]
public ActionResult AddressAndPayment(FormCollection values)
{
var order = new Order();
TryValidateModel(order);
....
The model looks like this
[Bind(Exclude="OrderId")]
public partial class Order
{
[ScaffoldColumn(false)]
public int OrderId { get; set; }
[ScaffoldColumn(false)]
public string Username { get; set; }
[Required(ErrorMessage= "First Name is required")]
[DisplayName("First Name")]
[StringLength(160)]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last Name is required")]
[DisplayName("Last Name")]
[StringLength(160)]
public string LastName { get; set; }
[Required(ErrorMessage="Address is required")]
[StringLength(70)]
public string Address { get; set; }
[Required(ErrorMessage = "City is required")]
[StringLength(40)]
public string City { get; set; }
[Required(ErrorMessage = "State is required")]
[StringLength(40)]
public string State { get; set; }
[Required(ErrorMessage = "Postal Code is required")]
[DisplayName("Postal Code")]
[StringLength(10)]
public string PostalCode { get; set; }
[Required(ErrorMessage="Country is required")]
[StringLength(40)]
public string Country { get; set; }
[Required(ErrorMessage= "Phone is required")]
[StringLength(24)]
public string Phone { get; set; }
[Required(ErrorMessage="Email Address is required")]
[DisplayName("Email Address")]
[RegularExpression(#"[A-za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", ErrorMessage="Email is not valid.")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
[ScaffoldColumn(false)]
public decimal Total { get; set; }
[ScaffoldColumn(false)]
public DateTime OrderDate { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
}
I can stop right before the TryValidateModel line and look at form values like
? Request.Form["FirstName"]
"Michael"
? values["FirstName"]
"Michael"
So why does TryValidateModel(order); return false and the order object does not get populated?
Update
To clarify my question I know false means it can not bind but I do not know why it can not bind. Or that it should through the TryValidateModel(or even the ValidateModel)
But what is interesting is that if I change my method signature to
public ActionResult AddressAndPayment(Order order)
order gets populated correctly. So if it is able to bind in the Method call why not TryValidateModel(or even the ValidateModel)?
I am using MVC 4
TryValidateModel returns false when validation of the Form Model against your Orders Model Fails, thus Binding fails.
I hate using
TryValidateModel(order);
and prefer
ValidateModel(order);
early on while developing my page, because binding is a delicate process. This way, if the model fails to bind, I get an exception and an indicative error msg.

MVC3 Model Binding with Castle

I am quite new to MVC. I am using an interface as a property for my model.
I noticed that my Data Annotation Attributes were being ignored. I also got an error while submitting the form:
Cannot create an instance of an interface.
I soon figured out that I will have to use a custom ModelBinder
I am having hard time figuring what needs to be done inside the CreateModel method of the ModelBinder
I have the following RegistrationModel:
public class RegistrationModel
{
#region Properties (8) 
public string Email { get; set; }
public string FirstName { get; set; }
public Gender Gender { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
public string PasswordConfirmation { get; set; }
public IPlace Place { get; set; }
public string Username { get; set; }
#endregion Properties 
}
Here is the IPlace interface and implementation:
public interface IPlace
{
#region Data Members (7) 
string City { get; set; }
string Country { get; set; }
string ExternalId { get; set; }
Guid Id { get; set; }
string Name { get; set; }
string Neighborhood { get; set; }
string State { get; set; }
#endregion Data Members 
}
public class Place : IPlace
{
#region Implementation of IPlace
public Guid Id { get; set; }
[StringLength(100, ErrorMessage = "City is too long.")]
public string City { get; set; }
[StringLength(100, ErrorMessage = "Country is too long.")]
public string Country { get; set; }
[StringLength(255, ErrorMessage = "External ID is too long.")]
public string ExternalId { get; set; }
[Required(ErrorMessage = "A name is required.")]
[StringLength(450, ErrorMessage = "Name is too long.")]
[DisplayName("Location")]
public string Name { get; set; }
[StringLength(100, ErrorMessage = "Neighborhood is too long.")]
public string Neighborhood { get; set; }
[StringLength(100, ErrorMessage = "State is too long.")]
public string State { get; set; }
#endregion
}
You should try to avoid using interfaces and abstract types in your view models. So in your case if the controller action taking this view model can never have any other implementation of IPlace than Place, than simply replace the interface.
If for some reason you needed it, as you have already found out, you will have to write a custom model binder in which you specify which implementation you want to create. Here's an example.

Problems with editing using a custom model

I have this data model:
public class User
{
public long UserID { get; set; }
[Required(ErrorMessage = "User name is required.")]
[MaxLength(50, ErrorMessage = "User name cannot be longer than 50 characters.")]
public string UserName { get; set; }
[Email]
[Required(ErrorMessage = "Email is required.")]
[MaxLength(100, ErrorMessage = "Email cannot be longer than 100 characters.")]
public string Email { get; set; }
[Required(ErrorMessage = "Password is required.")]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[MaxLength(150, ErrorMessage = "Full name cannot be longer than 150 characters.")]
public string FullName { get; set; }
public int UserTypeID { get; set; }
public virtual UserType UserType { get; set; }
public virtual ICollection<Page> Pages { get; set; }
}
and I'm using this model to only edit some fields (password shouldn't be editable):
public class EditUserModel
{
public long UserID { get; set; }
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Email]
[Required(ErrorMessage = "Email is required.")]
[MaxLength(100, ErrorMessage = "Email cannot be longer than 100 characters.")]
public string Email { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Full name")]
[MaxLength(150, ErrorMessage = "Full name cannot be longer than 150 characters.")]
public string FullName { get; set; }
public int UserTypeID { get; set; }
public virtual UserType UserType { get; set; }
}
but I'm confused on how to pass the EditUserModel to my data context to update it. Sorry if seems elementary, but I'm really stumped.
This is the auto-generated edit action that I modified:
[IsAdministrator]
[HttpPost]
public ActionResult Edit(EditUserModel user)
{
if (ModelState.IsValid)
{
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.UserTypeID = new SelectList(db.UserTypes, "UserTypeId", "Name", user.UserTypeID);
return View(user);
}
This is the line I'm having trouble with:
db.Entry(user).State = EntityState.Modified;
The reason I created a custom class was to avoid exposing the password from the view.
This can't work because you're trying to save view model.
You could use AutoMapper to rewrite data from view model to your data model. After that you should be able to save changes.
User userModel = Mapper.Map<EditUserModel, User>(user);
userModel = // todo: get password from database
// todo: attach your model to context and save changes
I'm using Entity Framework Code First and that approach works great.

Resources