I have these properties (A,B) :
[DataType(DataType.MultilineText)]
[Required(ErrorMessage = "A is required"), DisplayName("A")]
[StringLength(Constants.MaximunStringSize)]
public string A { get; set; }
[DataType(DataType.MultilineText)]
[Required(ErrorMessage = "B is required"), DisplayName("B")]
[StringLength(Constants.MaximunStringSize)]
public string B { get; set; }
I can create a class that "inherits" all the attributes (DataType, Required, StringLength, DisplayName) and the set through its constructor?. By example:
[MyAttribute("A","A is required")]
public string A { get; set; }
[MyAttribute("B","B is required")]
public string B { get; set; }
In general, no.
However, for validation attributes, you could create your own validation attribute that contains all of the logic in the existing attributes.
To emulate [DataType], you'll need to implement IMetadataAware.
There is no multiple inheritance in C#, so no, you can't do this.
You can, however write your own Attribute that incorporates all the functionality of those attributes.
Related
In ASP.NET Core MVC app, I need to skip model validation for certain complex property in controller's action.
Let's say I have a following model structure:
public class Person
{
public int PersonID { get; set; }
public PersonalInfo PersonalInfo { get; set; }
public ContactInfo ContactInfo { get; set; }
public Person()
{
PersonalInfo = new PersonalInfo();
ContactInfo = new ContactInfo();
}
}
public class PersonalInfo
{
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
}
public class ContactInfo
{
[Required(ErrorMessage = "Phone is required")]
public string Phone { get; set; }
[Required(ErrorMessage = "Email is required")]
public string Email{ get; set; }
}
In post action, I would like to skip validation for ContactInfo, although it is a part of model and is submitted. Something like this:
[HttpPost]
public ActionResult SavePerson(Person model)
{
ModelState.Remove("ContactInfo");
if (ModelState.IsValid)
{
(...)
}
}
This would work if ContactInfo was simple (is "scalar" correct term?) property.
I know that some of you would suggest me to use seperate viewmodels, but I do not think it is applicable in this case (I'm trying to create a form with multiple steps, and all the data has to be on form in order to be submitted in order to be preserved between steps...)
Also, I guess I could use ModelState.Remove for each property of ContactInfo class, but it seems repetitive and difficult to maintain, especially because my classes contain much more properties.
ModelState.Remove("ContactInfo");
Seems that the ModelState doesn't contain the "ContactInfo" key, so it will not work. If you want to disable the validation in the child class, as you guess, you need to remove all properties in it, such as:
ModelState.Remove("ContactInfo.Phone");
ModelState.Remove("ContactInfo.Email");
but it seems repetitive and difficult to maintain, especially because my classes contain much more properties.
It is indeed repetitive when there are many properties, but you can use reflection to simplify it.
foreach (var property in model.ContactInfo.GetType().GetProperties())
{
ModelState.Remove("ContactInfo." + property.Name);
}
If you have arrays try following. If you do not use XmlElement with an array Xml Serialization requires two Xml Tags. Using XmlElement requires only one tag. :
[XmlElement()]
public List<PersonalInfo> PersonalInfo { get; set; }
[XmlElement()]
public List<ContactInfo> ContactInfo { get; set; }
I'm using EF6 (Code First) in a project.
By having below class:
public class State
{
public int ID { get; set; }
[Required]
[StringLength(10)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
public Country Country { get; set; }
}
I expect to have Code as nvarchar(10) in database but I get nvarchar(3). I see the correct length for Name column but can't figure out why Code is not created correctly.
Edit:
I have the Country class as below:
public class Country
{
[Key]
[StringLength(3)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
I think, EF thinks the Code in State class is the Code in Country class as there is association between them.
Now, question is how should I tell EF that the Code in State class is not the Foreign Key to Country class?
Use MaxLength instead, EF will decide how large to make a string value field when it creates the database.
StringLength is data annotation, that will used to validate user input.
MSDN:
MaxLength - Specifies the maximum length of array or string data allowed in a property.
StringLength - Specifies the minimum and maximum length of characters that are allowed in a data field.
Since Question Updated:
Use [ForeignKey("CountryCode")] atribute, change your Code in Country class to CountryCode (or whatever you prefer) and specify your column name by Column["Code"] attribute:
public class State
{
public int ID { get; set; }
[Required]
[StringLength(10)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[ForeignKey("CountryCode")]
public Country Country { get; set; }
}
public class Country
{
[Key]
[StringLength(3)]
[Column["Code"]]
public string CountryCode { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
}
MSDN Links: Column Attribute, ForeignKey Attribute
Or just change your Codes to StateCode and CountryCode and use [ForeignKey("CountryCode")] attribute.
Even after working with it for a long time, EF is still surprising me. Until now I was thinking that by default EF is searching for property named {Navigation Property Name}{Referenced Entity PK Property Name} as default explicit FK property. But with your sample (verified), seems like it also does the same for property named {Referenced Entity PK Property Name}.
Since ForeignKey attribute cannot be used to specify the table column name (it can only specify FK/navigation property name), if you want to keep the model classes exactly as they are, you should use the MapKey fluent configuration, for instance:
modelBuilder.Entity<State>()
.HasRequired(s => s.Country)
.WithMany(s => s.States)
.Map(s => s.MapKey("CountryCode"));
Since you are referring to another class in the State model it will create a foreign key for you based on the name of the properties , so to avoid making the EF decide what the name of the column for you add the following in the state class :
public string CountryId { get; set; }
if you want to choose another name other than CountryId , suppose you want to change it to CountryForeignKey you can use the following:
using System.ComponentModel.DataAnnotations.Schema;
.
.
.
[ForeignKey("CountryForeignKey")]
public Country Country { get; set; }
public string CountryForeignKey { get; set; }
and this is what you get in the database
I would like to use inheritance to enforce consistency in Entity Framework model classes. For example, if X different models all have an address, they could inherit from:
public abstract class EntityAddress
{
[MaxLength(400)]
[Display(Name = "Street address")]
[DataMember]
public string AddressLine1 { get; set; }
[MaxLength(400)]
[Display(Name = "Address line 2")]
[DataMember]
public string AddressLine2 { get; set; }
[MaxLength(100)]
[Display(Name = "City")]
[DataMember]
public string City { get; set; }
[MaxLength(100)]
[Display(Name = "State")]
[DataMember]
public string State { get; set; }
[MaxLength(40)]
[Display(Name = "Zip code")]
[DataType(DataType.PostalCode)]
[DataMember]
public string ZipCode { get; set; }
}
This would ensure that all addresses are consistently implemented across the product (yes, if a model has two addresses, we have an issue, but I'll wave that away for the purposes of this discussion).
I would also like the ability to have a class use an unlimited number of these concepts. For example, if a model has an address and a full name, it could do this:
public class Customer : EntityAddress, EntityFullName
{
}
Multiple inheritance, however, is not supported in C#.
Does anyone have any ideas on good alternate methods to achieve what I am trying to do here? I don't believe interfaces will work because I can't embed the attributes with the properties. I don't believe a class property will work because I want the columns in the DB associated with the base classes to be in the same table as the model class properties.
Complex types appear to be an answer to this question (credit to Ivan Stoev).
https://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-2-complex-types
You could wrap in a class. Its not pretty, but it will achieve similar results
public class Customer
{
public EntityAddress address {get;set;}
public EntityFullName fullname {get;set;}
}
Okay, the problem is this. I have multiple models that make use of enum properties, in all but one of them I've had no problem with this. In this model however code first does not recognise this property and will not create a column in the respective table. What's bizarre is that I have a very similar model to the non-functioning one that works fine, the only difference being that it's in a different namespace.
I'll add that:
My project is targeting .NET 4.5 (and indeed the functioning and non-functioning models are within the same project).
I'm using Entity Framework 5
My enums are defined publicly in the namespace, not nested within a class.
This eliminates the most common causes of problems when trying to use enums in EF.
Here are the code snippets, first the non-working one:
public enum Commodities
{
Test1,
Test2,
Test3
}
[Table("Suppliers")]
public class Suppliers
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Required]
[MaxLength(7)]
[Display(Name = "Vendor ID")]
public string VendorID { get; set; }
[Required]
[Display(Name = "Supplier Name")]
public string SupplierName { get; set; }
public Commodities Commodity { get; set; }
public bool Visible { get; set; }
}
Now here is one that works fine, only difference being that it's in a different namespace and class file (both sit in the same project and have identical using declarations):
public enum Commodities
{
Test4,
Test5,
Test6
}
[Table("Buyers")]
public class Buyer
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Buyer ID")]
[Required]
public string BuyerID { get; set; }
[Display(Name = "Buyer Name")]
[Required]
public string Name { get; set; }
[Display(Name = "Windows Logon ID")]
public string WinUserID { get; set; }
[RegularExpression(#"^([a-zA-Z0-9_\-\.]+)#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage = "Please enter a valid e-mail adress")]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
public Commodities Commodity { get; set; }
[Display(Name = "Active?")]
public Boolean IsActive { get; set; }
}
In the "Buyer" table code first correctly initialising the Commodity column and maps it to the enum in the model. In the "Suppliers" table however it's only initialised with the columns: VendorID, VendorName, and Visible with Commodity no where to be found. My question is why is this happening and what can I do to fix it? I'm at my wits end with this.
EDIT: Seems the issue might have to do with using two enums with the same name. Although the compiler has no complaints about ambiguity (and it shouldn't as they exist in different namespaces) it seems EF doesn't like it. Changing the name of one of the enums causes CF to recognise the property.
Let's say I have a following ViewModel :
public class PersonViewModel
{
[Required]
public String Email { get; set; }
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
}
This is a ViewModel not a original Entity, I use this model in two places, in the first one I want to validate all fields, but in another one I want to exclude Email field from model validation. Is there anyway to specify to exclude field(s) from validation?
You can use
ModelState.Remove("Email");
to remove entries in model state, that are related to hidden fields.
The best solution is to divide view model into two:
public class PersonViewModel
{
[Required]
public String FirstName { get; set; }
[Required]
public String LastName { get; set; }
}
public class PersonWithEmailViewModel : PersonViewModel
{
[Required]
public String Email { get; set; }
}
An ugly solution:
ModelState.Remove("Email");
Recommended solution:
Create another ViewModel. A VM is supposed to represent your view, so if your view has no Email field, make a suitable VM for it.