DataAnnotations.EnumDataTypeAttribute doesn't work from Silverlight - data-annotations

In SL5 I have a DataForm that shows data from a DomainService.metadata generated from an Entity Model.
I'm using DataAnnotation.ValidationAttribues such as Required, StringLength, etc, to adorn the data properties in the DomainService.metadata.
These validation attributes work well, once I compile and run, the validations are present in the DataForm.
But I can't get the EnumDataType attribute to work in the DataForm, it's as if it wasn't there:
public partial class Student {
internal sealed class StudentMetadata {
public enum MyEnum {
One = 1,
Two = 2
}
private StudentMetadata() {
}
[EnumDataType(typeof(MyEnum), ErrorMessage = "Type 1 or 2")]
public Nullable<int> Other { get; set; }
[Required]
public int Age { get; set; }
For example, in the DataForm the field Age can't be left empty, but if I type 4 in the TypeOfRoom field, the error message doesn't show.
I know that I could use a ComboBox or something else, but I'm trying to learn the usage of the EnumDataType validation attribute.

You need to change the type of your property Other to the type of the Enum (in your case MyEnum) you support.
[EnumDataType(typeof(MyEnum), ErrorMessage = "Type 1 or 2")]
public MyEnum Other { get; set; }

Related

Prevent to use default model data annotations in ViewModel

I started working on my first serious MVC project for school unfortunately without defining the data annotations to the model first (so did not set "required" annotation, limit to size of attribute names etc.). I work with a viewmodel, and after adding the annotations the model is causing my ViewModel state to get invalid when posting the form.
It seems like it's the email required that is causing the issue. It is not used on viewmodel and in the form and it seems the viewmodel expects it will get it. Is there a way the form to stop demanding this field by setting some limitation in viewmodel (or controller). I would really prefer not to change the structure of the application (if I start from the scratch I would probably do this a bit different, but not much time is left to finalize the project)
Customer (Model)
public Class Customer(){
public int Id { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage = "Message"]
public string Name { get; set; }
public string Logo { get; set; }
//[Required(ErrorMessage = "Email required")]
//[Display(Name = "E-mail")]
//[RegularExpression(xxxx, ErrorMessage = "not correct")]
public string Email { get; set; }
public int UserId { get; set; }
}
ViewModel
public class CustomerEditViewModel
{
public Customer Customer { get; set; }
[FileTypes("jpg,jpeg,png")]
[FileSize(1024 * 1024, ErrorMessage = "Max x bytes")]
public HttpPostedFileBase File { get; set; }
}
You can remove errors from the modelstate in your controller, e.g.
this.ModelState[key].Errors.Clear();
where key is the bit to be cleared, so if it's email it's most likely -
this.ModelState["Customer.Email"].Errors.Clear();

mvc 5: Optional model property

I have a model called Project
public class Project
{
[Key]
public int ID { set; get; }
public string Title { set; get; }
public string Image { set; get; }
public double? gained { set; get; }
}
I use this model with two stored procedures one returns all the properties and the other without the property gained. And I got this error
The data reader is incompatible with the specified 'Test.Models.Project'. A member of the type, 'Gained', does not have a corresponding column in the data reader with the same name.
I don't want to write separate models for each stored procedure.
How to solve that please ?
The datareader is kind of dumb in the sense that it will only match what was sent back to it. If a column is missing, it fails, as you can see.
The easiest way to solve this would be to update your second SELECT statement in your stored procedure to pass back a column named gained.
SELECT ID, Title, Image, NULL as gained FROM table
Here, we are passing back no data (NULL) as the gained column. This should make the data reader happy, keep you from needing multiple models and not send back any extra data.
The other possibility would be to use inheritance in your models. Have a base model that does not include gained, and have a second model that inherits from the base model that does include gained.
public class ProjectBase
{
[Key]
public int ID { set; get; }
public string Title { set; get; }
public string Image { set; get; }
}
public class ProjectGained : ProjectBase{
public double? gained { set; get; }
}

Keeping validation on classes in .dbml file

I've been trying to validate user inputs by adding HTML-attributes to properties on classes in .dbml file. (in the designer section). which it works. but when I change the design of database and try to refresh the .dbml file, those lines (HTML-attributes) gets removed from my classes even though the classes I assigned the attributes to , are not changed. I tried to add the attributes in different file since the main classes are partial, that's not possible. it doesn't let me change properties. so what should I do?
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_at_name", DbType="NVarChar(50) NOT NULL", CanBeNull=false)]
[Required(ErrorMessage = "account name is necessary")]
public string at_name
{
get
{
return this._at_name;
}
set
{
if ((this._at_name != value))
{
this.Onat_nameChanging(value);
this.SendPropertyChanging();
this._at_name = value;
this.SendPropertyChanged("at_name");
this.Onat_nameChanged();
}
}
}
Using Data Annotations you can put validation in a seperate class and then reference that class from your partial class definition with an attribute. For example:
[MetadataType(typeof(PersonValidator))]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public class PersonValidator
{
[Required]
public string Name { get; set; }
[Range(0, int.MaxValue, ErrorMessage = "Invalid age")]
public int Age { get; set; }
}
You can put the MetadataType on your partial class (not the one generated by VS).

MVC.net EF validation when using View Models

I'm working on an MVC application and i'm trying to implement some validation. I've strucuture the site to use EF for storage and a set of view models with automapper.
I want to add some validation which i'm sure would work if i added it to the View Models however i'm assuming it would be better to put validation in with the EF model so if in the future i create another interface the same validation would also apply.
First of is this the correct approach and second how do i get MVC to actually test the validation before saving the object. Currently it just skips my EF validation.
The address model is auto generated so i created this partial class to add the validation:
public partial class Address : IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrWhiteSpace(this.AddressLine1) &&
!string.IsNullOrWhiteSpace(this.AddressLine2) &&
!string.IsNullOrWhiteSpace(this.AddressLine3) &&
!string.IsNullOrWhiteSpace(this.Town) &&
!string.IsNullOrWhiteSpace(this.City) &&
!string.IsNullOrWhiteSpace(this.County) &&
!string.IsNullOrWhiteSpace(this.Postcode))
yield return new ValidationResult("Address cannot be blank.");
}
}
This is my view model class with the display names changed
public class AddressVM
{
public int? ID { get; set; }
[Display(Name = "Address line 1")]
public string AddressLine1 { get; set; }
[Display(Name = "Address line 2")]
public string AddressLine2 { get; set; }
[Display(Name = "Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name = "Town")]
public string Town { get; set; }
[Display(Name = "City")]
public string City { get; set; }
[Display(Name = "County")]
public string County { get; set; }
[Display(Name = "Postcode")]
public string PostCode { get; set; }
}
This is my controller
public ActionResult AddAddress(AddressVM vm)
{
IncidentAddress theAddress = Mapper.Map<AddressVM, Address>(vm);
if (ModelState.IsValid)
{
UOW.Addresses.Add(theAddress);
UOW.Save();
}
return PartialView("AddressVM-edit", vm);
}
if (ModelState.IsValid)
This will always be true for your object, as it will look for validity of your model, which is AddressVM (you receive that from view so this is your model) and does not have any validators. ModelState does not know that you have mapped this object to some other which implements validation. You need to run validation on your other object manually and add validation errors to ModelState.
If you want to have this separated, you can implement IValidatableObject on AddressVM, and internally perform validation by creating a instance of Address, mapping it from AddressVM (this) and returning result of it's Validate method. You also can expose the same constructed Address object as a property and use it to perform entity operation.
Example of AddressVM:
public class AddressVM : IValidatableObject
{
public int? ID { get; set; }
[Display(Name = "Address line 1")]
public string AddressLine1 { get; set; }
[Display(Name = "Address line 2")]
public string AddressLine2 { get; set; }
[Display(Name = "Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name = "Town")]
public string Town { get; set; }
[Display(Name = "City")]
public string City { get; set; }
[Display(Name = "County")]
public string County { get; set; }
[Display(Name = "Postcode")]
public string PostCode { get; set; }
//// I added this and interface in class definition:
public IncidentAddress GetIncidentAddress()
{
return Mapper.Map<AddressVM, Address>(this);
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return this.GetIncidentAddress().Validate(validationContext);
}
}
This way your logic stays in your business object, and your viewmodel uses it without having copy of it or some other dependency.
The Address class and AddressVm are not bound to each other in your case - AutoMapper does not do validation stuff, it just copies values. So you do not get ModelState populated and validations performed.
There're two workarounds i'm thinking of
Define the validations on AddressVm. If ModelState.IsValid, then map AddressVm to Address and save.
You do not need AddressVm at all. Change Action signature to expect Address parameter. That way, ModelState.IsValid will be automatically populated by validation system (Not the best solution).
Ideally, ViewModels should be defined for specific scenarios. In your case, I would define AddAddressModel, use it only for adding addresses and define only the properties needed to create address. Then, define validations on AddAddressModel and use mapper to map ViewModel to Address instance (So, I prefer first solution, plus defining specific model).
If you need reusable validator classes, you could check out FluentValidation. It has good support of asp.net-mvc too.

How to make EFCodeFirst generate a column of type int for a property of type enum?

I have a model as follows:
namespace MvcApplication1.Models
{
public enum Sex { Male, Female }
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Required(ErrorMessage = "Please select either Female or Male.")]
public Sex? Sex { get; set; }
}
}
I successfully generated a database using EFCodeFirst. Unfortunately, only Id and Name columns are created in the database and no Sex column is generated.
How to make EFCodeFirst generate a column of type int for a property of type enum?
As of EF CTP5, enums are still NOT supported. This is something that the EF team are working on though and we will likely get it on the RTM. For now you would have to use an int? for the Sex property.
EF Code First will ignore any property that is not a primitive type when creating a DB from your model.
There is a simple workaround you can use:
http://daniel.wertheim.se/2010/06/09/dealing-with-enumerations-and-entity-framework-4-code-first/
..but you might have to do a little refactoring once EF supports enums.

Resources