Partial server side validation for complex property - c#

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

Related

Data annotations are not regenerated in the class when I reload my model

I'm developping a web application linked to a database using MVC Pattern and EF 6.0. To ensure that the datas entered on my web application are correct, I want to use data annotations.
I've followed this tutorial but I'm currently stuck at the last part.
It's written that:
Adding the validation attributes directly to the model class works
when you do not expect the database to change; however, if your
database changes and you need to regenerate the model class, you will
lose all of the attributes you had applied to the model class. This
approach can be very inefficient and prone to losing important
validation rules.
To avoid this problem, you can add a metadata class that contains the
attributes. When you associate the model class to the metadata class,
those attributes are applied to the model. In this approach, the model
class can be regenerated without losing all of the attributes that
have been applied to the metadata class.
Therefore I followed the steps creating a metadata class and partial classes.
The last part of the tutorial ask us to refresh the Model which I did.
However by doing so, the data annotations that should be present in my class are nowhere to be found. They were present before the refresh but got erased of my class.
Pre updated/refreshed class is working fine.
Post updated class doesn't seem to apply any restriction , for instance I can insert a new client with an age of 11 even though a value higher than 10 should not be accepted.
This is my class pre-refresh :
public partial class TaClient
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public TaClient()
{
this.TaBracelets = new HashSet<TaBracelet>();
}
public int IdClient { get; set; }
public string FirstName { get; set; }
[StringLength(50)] //data annotation for FirstName
public string LastName { get; set; }
[StringLength(50)] //data annotation for LastName
public string Sex { get; set; }
public int Height { get; set; }
[Range(3,10)] // data annotation for Age
public int Age { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<TaBracelet> TaBracelets { get; set; }
}
This is my class post refresh :
public partial class TaClient
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public TaClient()
{
this.TaBracelets = new HashSet<TaBracelet>();
}
public int IdClient { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Sex { get; set; }
public int Height { get; set; }
public int Age { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<TaBracelet> TaBracelets { get; set; }
}
this is my metadata class:
public class TaClientMetadata
{
[StringLength(50)]
[Display(Name = "First Name")]
public string FirstName;
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName;
[Range(3, 10)]
[Display(Name = "Age")]
public int Age;
}
And this is my partial class :
public class PartialClasses
{
[MetadataType(typeof(TaClientMetadata))]
public partial class TaClient
{
}
}
I probably miss understood something.
You need to extend the original partial class. To do that you need to define exactly the same class in exactly the same namespace, both of them marked with the partial keyword. In the auto-generated code, the partial keyword is already present. For your custom partial class definition you have to do this manually:
[MetadataType(typeof(TaClientMetadata))]
partial class TaClient
{
}
This has to be defined directly in the namespace. This, however is not what you are doing. You are defining a nested partial class within another class named PartialClasses. While this will compile fine, you will end up with two classes: TaClient (which is auto-generated) and PartialClasses.TaClient (which is created by you).
On a more general note: When you read through tutorials, it is sometimes necessary to familiarize yourself with the underlying concepts. In this case this would be the concept of partial classes.

Deriving a database model for view model in ASP.MVC

I am creating some view models for my ASP MVC web app.
I created "code first" models for database. Is it a good way to derive view models from database models?
Example database model:
public class Project
{
[Key]
public int Id { get; set; }
public int? CustomerId { get; set; }
public int TypeId { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
View model:
public class ViewModelProject : Project
{
[NotMapped]
public DateTime? Start { get; set; }
[NotMapped]
public DateTime? End { get; set; }
[NotMapped]
public string Manager { get; set; }
}
Is this the right way or is it completely false?
EDIT (subquestion):
I have some very simple database models like ProjectType, which only contains i.e. two properties. Should I also fragment those models in model view or can I make it that way:
Simple database model:
public class ProjectType
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? Code { get; set; }
}
Can I use it like so:
public class ProjectVM
{
public string Name { get; set; }
public int Number { get; set; }
public ProjectType Type { get; set; }
}
Or does it have to be fragmented like so:
public class ProjectVM
{
public string Name { get; set; }
public int Number { get; set; }
public string Type { get; set; }
public int TypeCode { get; set; }
}
I would not recommend doing it this way. I (and many others) have tried it and it doesn't work well. You will inadvertedly run into troubles, since an MVC model has to be tailored to the view and what you get from the DB rarely fits. Sure, you can hammer it into place, but the code quickly gets messy and store-related and UI code starts to mangle together. This even shows in your example, since you have to put the NotMappedAttribute (which is related to data storage), to ViewModelProject (a class at UI level).
There are many other examples to show this problem, but an especially good one I find when you want to serialize a model object to JSON and send it to a JavaScript client. The JSON serializer takes the values of all public properties and adds them to the JSON. If you want to exclude a property, you have to mark it with a ScriptIgnoreAttribute, which you would also have to apply to the base class, which breaks separation between UI and store-related code.
The better way to go is to keep the staorage model and the MVC model separated and to map the data from one to the other (there are already pre-existing frameworks that help you with that, such as Automapper). This comes with additional advantages, for example better testability, since you are now not dependent on a specific data store to create model instances.

Alternative to multiple inheritance in Entity Framework

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

Exclude Fields From Model Validation

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.

Multiple Attributes in one?

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.

Categories