2

I'm building a project in MVC template using AspNet Core.

I've used Entity Framework and I've scaffolded an existing DB. Now, I want to add some Data Annotations to some class, but I don't wanna edit the class autogenerated by the scaffolding, so I've tried with the Metadata and the overriding of an existing method, the saveChanges.

Users.cs autogenerated by Scaffolding

public partial class Users
{
    public int UserId { get; set; }
    ...
    // If I have [MaxLength(5, ErrorMessage = "Too short")] here, it works
    public string Email { get; set; }
}

UsersMetadata.cs (also tried Users.Metadata.cs or else, nothing changed)

[ModelMetadataType(typeof(UsersModelMetaData))]
public partial class Users { }

public class UsersModelMetaData
{
    [MaxLength(5, ErrorMessage = "Too short")]
    public string Email { get; set; }
}

MyContext : DbContext Class

public override int SaveChanges()
{
    var entities = from e in ChangeTracker.Entries()
                   where e.State == EntityState.Added
                       || e.State == EntityState.Modified
                   select e.Entity;
    foreach (var entity in entities)
    {               
    var validationContext = new ValidationContext(entity);
        Validator.ValidateObject(entity, validationContext, validateAllProperties: true);
    }

    return base.SaveChanges();
}

So, even if this seems to be the correct solution (i've search all morning), it doesn't work: the problem seems to be that the Data Annotation inside UsersModelMetaData aren't read, because if I put the Data Annotation directly in Users.cs file, method saveChanges() will throw an exception.

I did found this solution -> https://stackoverflow.com/a/30127682/6070423 <- but it's based on AspNet, and using AspNet Core I cannot use AssociatedMetadataTypeTypeDescriptionProvider.

Any idea how I could resolve this?

Jolly
  • 1,678
  • 2
  • 18
  • 37
  • See if this helps (you are not showing your usings, so I don’t know if it is this case): https://stackoverflow.com/a/37375987 – Luís Antunes Apr 21 '18 at 09:09
  • 1
    @LuísAntunes in that question, the answer is just to use ModelMetadataType instead of MetadataType, but i'm already using ModelMetadataType.. Thanks anyway – Jolly Apr 24 '18 at 08:44

1 Answers1

0

So, after I've asked on the ASP.Net Core forum, thanks to Edward Z answer, I did found a way to make it work.

Basically, instead of creating a partial class with ModelMetadataType, I've created a Class containing all the property that need to be validated (so, I will have a class of this kind for each class created by scaffolding). Something like this.

// Class generated by scaffolding
public partial class Users
    {
        public int UserId { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Email { get; set; }
    }

// Class I did create. Notice that the name could be anything
//   but should contain at least "Users", which is the name of
//   the table generated by scaffolding.
// Also notice that isUnique is a Custom Attribute I've wrote.
public class UsersMetaData
    {
        [MinLength(2, ErrorMessage = " too short")]   
        public string UserName { get; set; }
        [MinLength(2, ErrorMessage = " too short psw")]
        public string Password { get; set; }
        [IsUnique(ErrorMessage = "Email already exists")]
        public string Email { get; set; }            
    }

After that, I created a mapper which map an instance of the class auto-generated by scaffolding, to the relative class I've craeted. Something like this:

public static object MapEntity(object entityInstance)
   {
        var typeEntity = entityInstance.GetType();
        var typeMetaDataEntity = Type.GetType(typeEntity.FullName + "MetaData");

        if (typeMetaDataEntity == null)
            throw new Exception();

        var metaDataEntityInstance = Activator.CreateInstance(typeMetaDataEntity);

        foreach (var property in typeMetaDataEntity.GetProperties())
        {
            if (typeEntity.GetProperty(property.Name) == null)
                throw new Exception();

            property.SetValue(
                metaDataEntityInstance,
                typeEntity.GetProperty(property.Name).GetValue(entityInstance));
        }

        return metaDataEntityInstance;
    }

Notice that, in the first lines, starting from the type of the instance (which is Users), I retrieve the corresponding class I've created, by adding to that name the string "Metadata". Then, what I do is just to create an instance of the UserMetaData class assigning the same value of the Users instance.

Finally, when this mapper is called? In SaveChanges(), when I do the validation. That's because the validation is made on the UserMetaData class.

public override int SaveChanges()
        {
            var entities = from e in ChangeTracker.Entries()
                           where e.State == EntityState.Added
                               || e.State == EntityState.Modified
                           select e.Entity;
            foreach (var entity in entities)
            {
                var metaDataEntityInstance = EntityMapper.MapEntity(entity);
                var validationContext = new ValidationContext(metaDataEntityInstance);
                Validator.ValidateObject(
                    metaDataEntityInstance,
                    validationContext,
                    validateAllProperties: true);
            }

            return base.SaveChanges();
        }

It's kinda tricky, but it all works!

Jolly
  • 1,678
  • 2
  • 18
  • 37