Meta
Meta

Reputation: 489

ASP.NET Core MetaDataType Attribute not working

I'm using the MetaDataType Attribute on my domain model class. It it supposed to move the attribute information from the referenced class into the class that the MetadataType attribute has been set. But it doesn't do as advertised. What is causing the issue here?

[MetadataType(typeof(ComponentModelMetaData))]
public partial class Component
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Repo> Repos { get; set; }
    public string Description { get; set; }   
}


public class ComponentModelMetaData
{
    [Required(ErrorMessage = "Name is required.")]
    [StringLength(30, MinimumLength = 3, ErrorMessage = "Name length should be more than 3 symbols.")]
    public string Name { get; set; }
    public ICollection<Repo> Repos { get; set; }
    [Required(ErrorMessage = "Description is required.")]
    public string Description { get; set; }        
}

Upvotes: 43

Views: 19564

Answers (4)

Guilherme Duarte
Guilherme Duarte

Reputation: 3441

ASP.NET Core uses

Microsoft.AspNetCore.Mvc **ModelMetadataType** 

instead of

System.ComponentModel.DataAnnotations.**MetadataType** 

source

Try changing your attribute to [ModelMetadataType(typeof(ComponentModelMetaData))]

Note
System.ComponentModel.DataAnnotations is still required for annotations like
[Display(Name = "")]
[Required]

Upvotes: 91

Javier Contreras
Javier Contreras

Reputation: 1123

Entity class:

namespace CoreProject.Persistence.EFCore
{
    public partial class User
    {
        public User()
        {
            Reader = new HashSet<Reader>();
            Writer = new HashSet<Writer>();
        }

        public int UserId { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public string PasswordHashKey { get; set; }
        public byte Role { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime CreatedUtc { get; set; }
        public DateTime LastUpdateUtc { get; set; }
        public byte Status { get; set; }
        public bool Deleted { get; set; }
        public DateTime? ActivatedUtc { get; set; }
        public bool Test { get; set; }

        public virtual ICollection<Reader> Reader { get; set; }
        public virtual ICollection<Writer> Writer { get; set; }
    }
}

IMPORTANT: Both classes have to use the same namespace. You need to add Microsoft.AspNetCore.Mvc nuget package.

Metadata class:

namespace CoreProject.Persistence.EFCore
{
    [ModelMetadataType(typeof(IUserMetadata))]
    public partial class User : IUserMetadata
    {
        public string FullName => FirstName + " " + LastName;
    }

    public interface IUserMetadata
    {
        [JsonProperty(PropertyName = "Id")]
        int UserId { get; set; }

        [JsonIgnore]
        string Password { get; set; }
        [JsonIgnore]
        string PasswordHashKey { get; set; }
        [JsonIgnore]
        byte Role { get; set; }
    }
}

Upvotes: 6

Simon Miller
Simon Miller

Reputation: 970

I'm in a similar situation, where the database existed before the coding began. So DB first seemed the natural choice. Generated classes, none of which have annotations. So added MetadataType, and found this stackOverflow page - and so tried ModelMetadataType. I tried it against a class, as well as an interface.

I tried using var validationContext = new ValidationContext(model);

instead of var editContext = new EditContext(model);

and got further when I used var result = Validator.TryValidateObject(model, validationContext, xxxxx, validateAllProperties: true);

I thought I figured it out when I tried editContext.AddDataAnnotationsValidation();

Although this works for a class with annotations, it doesn't work (for me) for partial classes using either [MetadataType] or [ModelMetadataType] attributes.

The only work-around I've found to work (for me) is to create a new class that wraps the model you're wanting to annotate. That means same properties but getters and setters pointing at original model. But at least the annotations will work!

public class Model
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class AnnotatedModel
{
    private readonly Model model;

    public AnnotatedModel(Model model)
    {
        this.model = model;
    }

    [Range(1, int.MaxValue)]
    public int ID { get => model.ID; set => model.ID = value; }

    [Required]
    [StringLength(maximumLength: 10, ErrorMessage = "Name can't be longer than 10 characters.")]
    public string Name { get => model.Name; set => model.Name = value; }
}

So to use the above, I needed to write this:

var model = new Model();
var annotatedModel = new AnnotatedModel(model);
var editContext = new EditContext(annotatedModel);
editContext.AddDataAnnotationsValidation();
var result = editContext.Validate();

If anyone can tell me what I'm missing, great! Otherwise I hope this work-around will be useful for somebody.

Upvotes: 1

ashimaz
ashimaz

Reputation: 41

Another way... use same namespace

public class ApirolesMetaData
{
    [Required]
    public string Name { get; set; }
}


[ModelMetadataType(typeof(ApirolesMetaData))]
public partial class Apiroles
{

}

Upvotes: 3

Related Questions