0

I like to define my data automation for data validation in a separate partial class, not the generated one by EF. I tried to build the Metadata class in partial class like this:

public partial class PersonViewModel
{
    public string Fname { get; set; }
}

[MetadataType(typeof(PersonViewModelMetaData))]
public partial class PersonViewModel
{
}

public class PersonViewModelMetaData
{
    [Required]
    public string Fname { get; set; }
}

by the way, this method not working no data validation occurred! I guess maybe that happens by missing something in start up class: These are the files:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //DI 
           services.AddDbContext<HomeSunSystem>(
            option => option.UseSqlServer(Configuration.GetConnectionString("xxx")));

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(option=>
                {
                    option.LoginPath = "/Login";
                }
                );
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();        
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Login}/{action=Index}/{id?}");
            });
        }
    }
}

and

 public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();   
                });
    }

this is .net core project.

Am I miss something?

  • What do you mean by `data automation`? All programming is data automation. Data *validation* isn't performed by EF, it's a separate .NET namespace uses together with the application stack (WinForms, WPF, MVC, Razor, Web API). Validators and messages are displayed by the UI, not EF. DTOs and models are validated by the stack, not EF. – Panagiotis Kanavos Mar 09 '21 at 09:31
  • Your code contains *no* validation attributes or methods. If you want to make a property required, add the `Required` attribute. Check [Model validation in ASP.NET Core MVC and Razor Pages](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-5.0). A DTO that fails validation won't result in an exception automatically, you'll have to check its validation state – Panagiotis Kanavos Mar 09 '21 at 09:40
  • actually, that partial class is not mine just copied it from the net to let you what I mean by separating the Metadata. I used [Required] and [MinLentgh(3)] in mine but they do not affect my data validation in practice.There is no error but no data validation either. – paria shiri Mar 09 '21 at 10:23
  • Why not use the Fluent API through ModelBuilder in OnModelCreating. Doing so your validation is separated from the Entity class. – Majid Shahabfar Mar 09 '21 at 10:29
  • @majid-shahabfar I know that is another way. but why this code should not work? – paria shiri Mar 09 '21 at 10:56
  • Because MetadataType doesn't work in .NET Core and you should use ModelMetadataType instead which existed in Microsoft.AspNetCore.Mvc assembly. But Microsoft.AspNetCore.Mvc should only be at the web level and this is against a good architecture which forces you to push this into some business area. – Majid Shahabfar Mar 11 '21 at 07:40

2 Answers2

1

As far as I know, MetadataTypeAttribute does not work in .NET Core. Check out this issue to get more information on this.

Instead of using Metadata attributem use Microsoft.AspNetCore.Mvc.ModelMetadataType:

[ModelMetadataType(typeof(PersonViewModelMetaData))]
public partial class PersonViewModel
{
}

This works for MVC validation but bears in mind it doesn't work if models are in different assemblies.

One workaround for EF Core could be this https://stackoverflow.com/a/49997365/1385614

First create a Mapper class:

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

Then override SaveChanges and SaveChangesAsync methods of DbContext:

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();
}
Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66
1

If you are persisting on using MetadataType on .NET Core you can try this:

Entity class:

namespace MyProject.Persistence
{
    public partial class User
    {
        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; }
    }
}

ModelMetadataType: Put those properties that need validation in an interface and drive the entity class from the interface in a partial class.

namespace MyProject.Persistence
{
    [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; }
    }
}
Majid Shahabfar
  • 4,010
  • 2
  • 28
  • 36