Validating hierarchical objects with FluentValidation fails due to a hidden property - c#

I have a set of base DTOs without Id property (for creating objects) and i have a corresponding set of regular DTOs (inheriting from base and extending by Id column). Additionally those DTOs contain subobject with exactly the same approach - DeviceBaseDto contains VehicleBaseDto (without id), DeviceDto contains VehicleDto (inheriting from VehicleBaseDto and adding id property).
public class DeviceBaseDto
{
[Required]
public string Name { get; set; }
public VehicleBaseDto Vehicle {get;set;}
//a lot more properties
}
public class DeviceDto : DeviceBaseDto
{
public int Id { get; set; }
public new VehicleDto Vehicle { get; set; }
}
Now let's move to validator objects. What i'm trying to achieve is to contain all baseDto rules in base validator object. Then i put additional rules (for properties in inheriting class) in a regular validator.
public class DeviceBaseValidator<TDeviceBase> : AbstractValidator<TDeviceBase> where TDeviceBase : DeviceBaseDto
{
public DeviceBaseValidator()
{
RuleFor(x => x.Vehicle).SetValidator(new VehicleBaseValidator<VehicleBaseDto>());
RuleFor(x => x.Vehicle).NotNull();
//a lot of validation
}
}}
public class DeviceValidator : DeviceBaseValidator<DeviceDto>
{
public DeviceValidator()
{
RuleFor(x => x.Id).GreaterThan(0);
}
}
public class VehicleValidator : VehicleBaseValidator<VehicleDto>
{
public VehicleValidator()
{
RuleFor(x => x.Id).GreaterThan(0);
}
}
When i try to run validation:
var deviceValidator = new DeviceValidator();
var validationResult = deviceValidator.Validate(deviceDto);
it fails stating that VehicleDto is null. Because we used "new" keyword in DeviceDto class for Vehicle property, it expects to get VehicleBaseDto but it gets VehicleDto instead. In other words, VehicleValidatorBase fails on:
RuleFor(x => x.Vehicle).NotNull();
but if i put exactly the same rule in VehicleValidator it will pass the validation. So what i want is:
when DeviceDto will be received, i want DeviceValidator to run DeviceValidatorBase on DeviceDtoBase properties and VehicleValidatorBase rules on DeviceDto.Vehicle property.
i want to run any extension rules from VehicleValidator on DeviceDto.Vehicle as well as all other rules on DeviceDto using DeviceValidator.
Perhaps the design i've created isn't exactly great so i'll be grateful to hear any advices on how to structure those objects.

Related

Dictionary derived type as non-navigation property in efcore6

I am trying to move one of our projects from efcore5 to efcore6. We have a type that's derived from Dictionary as follows.
public class LinkRelations : Dictionary<string, LinkRelation>
where LinkRelation is simply;
public class LinkRelation
{
public string Href { get; set; }
}
Our ef entity Notification has LinkRelations as;
public class Notification
{
public LinkRelations Links { get; private set; }
// other stuff
}
We configure the Notification entity for EF as follows;
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{
public void Configure(EntityTypeBuilder<Notification> builder)
{
builder.Property(n => n.Links).HasConversion(new JsonValueConverter<LinkRelations>());
}
}
public class JsonValueConverter<T> : ValueConverter<T, string>
{
public JsonValueConverter() : base(
v => JsonConvert.SerializeObject(v),
s => JsonConvert.DeserializeObject<T>(s))
{
}
}
When I try to create a new migration (to test), I get the error: System.InvalidOperationException: 'Links' cannot be used as a property on entity type 'Notification' because it is configured as a navigation. at the NotificationConfiguration.Configure call above.
However, we are actually storing Links as a json string. Since, LinkRelations is derived from Dictionary, I believe EFCore6 is concluding that it must be a navigation property and then falling over.
So, my question is, how can I configure ef-core-6 to not make my property a navigation property? Or any other suggestion to avoid this error?
PS: This is an existing system in prod, and I cannot really modify the current design / schema.

FluentValidation message for nested properties

I have a class with a complex property:
public class A
{
public B Prop { get; set; }
}
public class B
{
public int Id { get; set; }
}
I've added a validator:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.A.Id)
.NotEmpty()
.WithMessage("Please ensure you have selected the A object");
}
}
But during client-side validation for A.Id I still have a default validation message: 'Id' must not be empty. How can I change it to my string from the validator?
You can achieve this by using custom validator for nested object:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
}
class BValidator : AbstractValidator<B>
{
public BValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("Please ensure you have selected the B object");
}
}
}
public class A
{
public B B { get; set; }
}
public class B
{
public int Id { get; set; }
}
There is an alternative option here. When configuring FluentValidation in your Startup class you can set the following configuration.ImplicitlyValidateChildProperties = true;
So the full code might look something like this
services
.AddMvc()
.AddFluentValidation(configuration =>
{
...
configuration.ImplicitlyValidateChildProperties = true;
...
})
So you would still have two validators one for class A and one for class B, then class B would be validated.
The documentation states:
Whether or not child properties should be implicitly validated if a matching validator can be found. By default this is false, and you should wire up child validators using SetValidator.
So setting this to true implies that child properties will be validated.
Pretty old question but for future generations - you can either use a child validator or define child rules inline as described in the official documentation: https://fluentvalidation.net/start#complex-properties
It depends on where you want to use your validators:
In the common scenario, where we have an application with N layers,
in the ASP.net layer, for the validation of View Models, DTOs or Commands (e.g. to achieve fail-fast validation), just enable ImplicitlyValidateChildProperties, as #StevenYates said:
services.AddMvc().AddFluentValidation(fv =>
{
fv.ImplicitlyValidateChildProperties = true;
});
When this is enabled, instead of having to specify child validators using SetValidator, MVC’s validation infrastructure will recursively attempt to automatically find validators for each property automatically.
IMHO, this is great, because besides being practical, it prevents us from forgetting some .SetValidator (...)!
Note that if you enable this behaviour you should not use SetValidator
for child properties, or the validator will be executed twice.
Doc: https://docs.fluentvalidation.net/en/latest/aspnet.html#implicit-vs-explicit-child-property-validation
But, in addition (or instead) of that, if you want to use FluentValidation in other layers (e.g. Domain), you need to use the SetValidator(...) method, as #t138 said, for example:
RuleFor(customer => customer.Address).SetValidator(new AddressValidator());
Doc: https://docs.fluentvalidation.net/en/latest/start.html#complex-properties
public class A
{
public B B { get; set; }
}
public class B
{
public int Id { get; set; }
}
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.B).NotNull().SetValidator(new BValidator());
}
class BValidator : AbstractValidator<B>
{
public BValidator()
{
RuleFor(x => x.Id).NotEmpty().WithMessage("message ....");
}
}
}
----------------------------------------------------------------------
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
fv.ImplicitlyValidateChildProperties = true;
});
}
You can use ChildRules method:
public class AValidator : AbstractValidator<A>
{
public AValidator()
{
RuleFor(x => x.Prop)
.ChildRules(x => x.RuleFor(b => b.Id))
.NotEmpty()
.WithMessage("Please ensure you have selected the A object");
}
}

AutoMapper TwoWay Mapping with same Property Name

Given these two objects
public class UserModel
{
public string Name {get;set;}
public IList<RoleModel> Roles {get;set;}
}
public class UserViewModel
{
public string Name {get;set;}
public IList<RoleViewModel> Roles {get;set;} // notice the ViewModel
}
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
App Config
Mapper.CreateMap<UserModel, UserViewModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Mapper.CreateMap<UserViewModel, UserModel>()
.ForMember(dest => dest.Roles, opt => opt.MapFrom(src => src.Roles));
Implementation
_userRepository.Create(Mapper.Map<UserModel>(someUserViewModelWithRolesAttached);
Is this the most optimal way to do the mapping, or is AutoMapper capable of mapping Roles to Roles on its own?
If the property names are identical, you should not have to manually provide a mapping:
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Just make sure the inner types are mapped as well (RoleViewModel ↔ RoleModel)
What this means, however, is that if you change a source or destination property name, AutoMapper mappings can fail silently and cause hard to track down problems (e.g., if you changed UserModel.Roles to UserModel.RolesCollection without changing UserViewModels.Roles).
AutoMapper provides a Mapper.AssertConfigurationIsValid() method that will check all of your mappings for errors and catch misconfigured mappings. It's useful to have a unit test that runs with the build that validates your mappings for this kind of problem.
You don't need to map the properties. Just make sure that the property names match and there is a mapping defined between them.
Mapper.CreateMap<UserModel, UserViewModel>();
Mapper.CreateMap<UserViewModel, UserModel>();
Mapper.CreateMap<RoleModel, RoleViewModel>();
Mapper.CreateMap<RoleViewModel, RoleModel>();
Or with the cooler way I just found out:
Mapper.CreateMap<UserModel, UserViewModel>().ReverseMap();
Mapper.CreateMap<RoleModel, RoleViewModel>().ReverseMap();
All the other answers, are much better (which I gave an upvote to each).
But what I wanted to post here is a quick playground that you could copy and past right into LinqPad in C# program mode and play your idea's without messing with your actual code.
Another awesome thing about moving all your conversions into a TyperConverter class is that your conversions are now Unit Testable. :)
Here you will notice that the model and viewmodel are almost identical except for one property. But through this process the right property is converted to the correct property in the destination object.
Copy this code into LinqPad and you can run it with the play button after switching to C# Program mode.
void Main()
{
AutoMapper.Mapper.CreateMap<UserModel, UserViewModel>().ConvertUsing(new UserModelToUserViewModelConverter());
AutoMapper.Mapper.AssertConfigurationIsValid();
var userModel = new UserModel
{
DifferentPropertyName = "Batman",
Name = "RockStar",
Roles = new[] {new RoleModel(), new RoleModel() }
};
var userViewModel = AutoMapper.Mapper.Map<UserViewModel>(userModel);
Console.WriteLine(userViewModel.ToString());
}
// Define other methods and classes here
public class UserModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; }
public string DifferentPropertyName { get; set; }
}
public class UserViewModel
{
public string Name {get;set;}
public IEnumerable<RoleModel> Roles { get; set; } // notice the ViewModel
public string Thingy { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("Name: {0}", Name));
sb.AppendLine(string.Format("Thingy: {0}", Thingy));
sb.AppendLine(string.Format("Contains #{0} of roles", Roles.Count()));
return sb.ToString();
}
}
public class UserModelToUserViewModelConverter : TypeConverter<UserModel, UserViewModel>
{
protected override UserViewModel ConvertCore(UserModel source)
{
if(source == null)
{
return null;
}
//You can add logic here to deal with nulls, empty strings, empty objects etc
var userViewModel = new UserViewModel
{
Name = source.Name,
Roles = source.Roles,
Thingy = source.DifferentPropertyName
};
return userViewModel;
}
}
public class RoleModel
{
//no content for ease, plus this has it's own mapper in real life
}
Result from the Console.WriteLine(userViewModel.ToString());:
Name: RockStar
Thingy: Batman
Contains #2 of roles
Inside the Startup.cs in the Configure() method:
Mapper.Initialize(config => {
config.CreateMap<UserModel, UserViewModel>().ReverseMap();
// other maps you want to do.
});

How to map key properties to differing column types on inherited entities in EF CodeFirst?

I'm trying to implement a TPC inheritance model in EF 4.3 CodeFirst for an existing Oracle database (over which I have no control). I have several sub-types that each map to its own table. Unfortunately, some of the key columns are of datatype number(18,0) instead of integer. EF seems to hate me now.
Here's my base class:
public abstract class Vehicle
{
public virtual int Id { get; set;}
public virtual string Color { get; set; }
//more properties
}
Here are some example sub-types:
public class Car : Vehicle
{
//more properties
}
public class Truck : Vehicle
{
//more properties
}
public class Motorcycle : Vehicle
{
//more properties
}
And here's my DbContet:
public class VehicleDataContext : DbContext
{
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Car> Cars { get; set; }
public DbSet<Truck> Trucks { get; set; }
public DbSet<Motorcycle> Motorcycles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Vehicle>().HasKey(x => x.Id);
modelBuilder.Entity<Car>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Car>().Property(x => x.Id).HasColumnType("decimal");
modelBuilder.Entity<Truck>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Truck>().Property(x => x.Id).HasColumnType("int");
modelBuilder.Entity<Motorcycle>().Map(m => m.MapInheritedProperties());
modelBuilder.Entity<Motorcycle>().Property(x => x.Id).HasColumnType("decimal");
base.OnModelCreating(modelBuilder);
}
}
So, I already know to MapInheritedProperties so that all the properties of the base and sub-type are mapped to one table. I'm assuming that I have to tell the base that it HasKey so that EF doesn't complain that my DbSet<Vehicle> doesn't have a key mapped. I'd like to be able to assume that I can "tell" each entity how to map its own key's column type like I've done above. But I think that's not quite it.
Here's a test that fails:
[TestFixture]
public class when_retrieving_all_vehicles
{
[Test]
public void it_should_return_a_list_of_vehicles_regardless_of_type()
{
var dc = new VehicleDataContext();
var vehicles = dc.Vehicles.ToList(); //throws exception here
Assert.Greater(vehicles.Count, 0);
}
}
The exception thrown is:
The conceptual side property 'Id' has already been mapped to a storage
property with type 'decimal'. If the conceptual side property is
mapped to multiple properties in the storage model, make sure that all
the properties in the storage model have the same type.
As mentioned above, I have no control over the database and it's types. It's silly that the key types are mixed, but "it is what it is".
How can I get around this?
You cannot achieve it through mapping. This is limitation of EF code first. You can map each property (including the key) in inheritance structure only once. Because of that you can have it either integer or decimal in all entities in the inheritance tree but you cannot mix it.
Btw. what happens if you try to use int or decimal for the whole inheritance tree? Does it fail for loading or persisting entity? If not you can simply use the one (probably decimal if it can use whole its range) for all entities.

Automapper nested mapping

I've read the nested mapping wiki page but it appears to not like multiple levels of nesting. I've got the following maps created and classes defined.
AutoMapper.Mapper.CreateMap<Address, AddressDTO>();
AutoMapper.Mapper.CreateMap<MatchCompanyRequest, MatchCompanyRequestDTO>();
public class MatchCompanyRequest
{
Address Address {get;set;}
}
public class MatchCompanyRequestDTO
{
public CompanyInformationDTO {get;set;}
}
public class CompanyInformationDTO {get;set;}
{
public string CompanyName {get;set;}
public AddressDTO Address {get;set;}
}
But the following code...
// works
matchCompanyRequestDTO.companyInformationDTO.Address =
AutoMapper.Mapper.Map<Address, AddressDTO>(matchCompanyRequest.Address);
// fails
matchCompanyRequestDTO =
AutoMapper.Mapper
.Map<MatchCompanyRequest, MatchCompanyRequestDTO>(matchCompanyRequest);
Does this deep nesting work and I have it configured improperly? Or is this kind of nesting not yet supported?
-- Edit
For anyone interested, I am not in control of the DTOs.
It lacks the mapping from Address to CompanyInformationDTO, as those objects are on the same nest-level.
The map is created for MatchCompanyRequest -> MatchCompanyRequestDTO, but it is unable to figure out whether it can map Address to CompanyInformationDTO.
So your MatchCompanyRequestDTO could in fact have same declaration as your CompanyInformationDTO:
public class MatchCompanyRequestDTO
{
public string CompanyName {get;set;}
public AddressDTO Address {get;set;}
}
This of course only affects you if you want to use automatic mapping. You still can configure your maps manually, but it seems like the DTOs should be fixed instead, let's try anyway:
public class CustomResolver : ValueResolver<Address, CompanyInformationDTO>
{
protected override CompanyInformationDTO ResolveCore(Address source)
{
return new CompanyInformationDTO() { Address = Mapper.Map<Address, AddressDTO>(source) };
}
}
// ...
AutoMapper.Mapper.CreateMap<MatchCompanyRequest, MatchCompanyRequestDTO>()
.ForMember(dest => dest.companyInformationDTO, opt => opt.ResolveUsing<CustomResolver>().FromMember(src => src.Address)); // here we are telling to use our custom resolver that converts Address into CompanyInformationDTO
The important thing is you define how deeper is your navigation, to previne the stackoverflow problems. Imagine this possibility:
You have 2 entities Users and Notifications in NxN model (And
you have DTOs object to represent that), when you user auto mapper
without set MaxDepth in you mapper expression, "Houston we have a
problem" :).
The code below show a workaround to resolve this for all Mappers. If you want can be defined to each mapper. Like this Question
Solution 1 (Global Definition)
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(mapperConfiguration =>
{
mapperConfiguration.AddProfile<DomainModelToYourDTOsMappingProfile>();
mapperConfiguration.AddProfile<YourDTOsToDomainModelMappingProfile>();
mapperConfiguration.AllowNullCollections = true;
mapperConfiguration.ForAllMaps(
(mapType, mapperExpression) => {
mapperExpression.MaxDepth(1);
});
}
}
Solution 2 (For each Mapper)
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.CreateMap<User, DTOsModel>()
.MaxDepth(1);
}
}
Consider the following instead:
public class MatchCompanyRequest
{
Address Address {get;set;}
}
public class MatchCompanyRequestDTO
{
public string Name {get;set;}
public AddressDTO Address {get;set;}
}
public class AddressDTO
{
....
}
Your DTO objects need to have the same structure as your domain objects for the default mapping conventions to work in AutoMapper.
Look at this: https://github.com/AutoMapper/AutoMapper/wiki/Projection It will explain the Projection for you, you could customize it to work the way you have it.

Categories

Resources