FluentValidation message for nested properties - c#

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

Related

Automapper exception : must resolve to top-level member

How to map following ItemId (as Source) with DataId (as Output):
Source:
public class Source
{
public InputData InputItem { get; set; }
}
public class InputData
{
public int ItemID { get; set; }
}
Output:
public class Output
{
public List<OutputData> OutputItem { get; set; }
}
public class OutputData
{
public string[] DataID { get; set; }
}
I'm trying to map it in following way:
CreateMap<Source, Output>().ForMember(d => d.OutputItem[0].DataID,
option => option.MapFrom(s => s.InputItem != null ? new string[] { $"item_{s.InputItem.ItemID}" } : null));
Getting the exception:
Expression 'd => d.OutputItem.get_Item(0).DataID' must resolve to top-level member and not any child object's properties. Use a custom resolver on the child type or the AfterMap option instead. Parameter name: lambdaExpression
Can someone please help me in mapping these objects.
Thanks
The error basically says that you can only define a mapping for one level of the object. You can not define the mapping to directly write to a property of a child object.
Solve this by defining separate mappings for the objects on each level.
First define the mapping for the child objects:
CreateMap<InputData, OutputData>().ForMember(d => d.DataID,
option => option.MapFrom(s => s != null ? new string[] { $"item_{s.ItemID}" } : null));
Then the mapping for the parent object can use this definition to map its child objects. (Collections like List<OutputData> should be filled by AutoMapper automatically.)
CreateMap<Source, Output>().ForMember(d => d.OutputItem,
option => option.MapFrom(s => s.InputItem));

Is there a way to resolve a GraphQl type by referencing another type?

Im using the Graphql .Net library to build a GraphQl API.
The following is a domain example of what we currently have, where, the area has a list of sampling point identifiers:
public class AreaRoot {
public String Id { get; set; }
public List<String > SamplingPointIds { get; set; }
}
public class SamplingPointRoot {
public String Id { get; set; }
public String Description { get; set; }
}
And the types are defined as follow:
public class AreaType : ObjectGraphType<AreaRoot>
{
public AreaType()
{
Name = "Area";
Field(x => x.Id, type: typeof(IdGraphType));
Field(x => x.SamplingPointIds, type: typeof(ListGraphType<StringGraphType>));
}
}
public class SamplingPointType : ObjectGraphType<SamplingPointRoot>
{
public SamplingPointType()
{
Name = "SamplingPoint";
Field(x => x.Id, type: typeof(IdGraphType));
Field(x => x.description, type: typeof(StringGraphType));
}
}
Is there any way to retrieving everything from the sampling point without changing the domain classes? there is an example in the conference GraphQL vs Traditional Rest API, in the 25:41 min, but this example is in java, and we could not make the same using the graphQl .net.
The next example illustrates the type of query we want to make:
query GetAreas(){
areas(){
Id
samplingPoints{
Id
description
}
}
}
So the question is: Is there a way to this as in the video above, as we pass the samplingPoints, and resolve it, retrieving the samplingPoints for that area (in some query)?
Question resolved on github. For those trying to do the same, its really easy actually, we just have to had resolver inside the AreaType like this:
public class AreaType : ObjectGraphType<AreaRoot>
{
public AreaType(IRepository repository)
{
Name = "Area";
Field(x => x.Id, type: typeof(IdGraphType));
Field<ListGraphType<SamplingPointType>>("areaSamplingPoints",
resolve: context =>
{
return repository.GetAllByAreaId(context.Source?.Id);
});
}
}
notice the context.Source?.Id used to access the Area Id...
And also, if you are trying to access the arguments of the top level context, well, you can't, as stated here, but instead, you can access the variables passed to the query, not the best, but not the worst, so use: context.Variables.ValueFor("variableName")

Validating hierarchical objects with FluentValidation fails due to a hidden property

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.

C# MongoDB AutoGenerateId without attributes

I have the following class:
public class Foo : IFoo
{
public object Id { get; set; }
public string someProperty { get; set;}
}
If you notice, the Id property it's an object type. This is very important because I don't wanna to have any MongoDb dependence on my Models, thus the same models will be used in others databases repositories.
So I have my FooMongoDBRepository where my class map are defined:
public class FooMongoDBRepository : IFooRepository{
public MongoDbSubscriberRepository (MongoDatabase database){
BsonSerializer.LookupSerializer(typeof(Foo));
if (!BsonClassMap.IsClassMapRegistered (typeof(Foo))) {
BsonClassMap.RegisterClassMap<Foo> (cm => {
cm.AutoMap ();
cm.SetIdMember (cm.GetMemberMap (c => c.Id));
});
}
}
}
This work fine for inserts, but when try to Upsert, my _id key is always NULL. Can someone help me, how to force the Id field to be generated without using annotations ?
Thanks in advanced!
Edit: Now It's working!, here is the code
cm.AutoMap ();
cm.GetMemberMap(c => c.Id).SetIgnoreIfDefault(true);
cm.SetIdMember (cm.GetMemberMap (c => c.Id));
cm.IdMemberMap.SetIdGenerator(ObjectIdGenerator.Instance);
You need to add
[BsonIgnoreIfDefault] to your Id
BsonClassMap.RegisterClassMap<MyClass>(cm => {
cm.AutoMap();
cm.GetMemberMap(c => c.SomeProperty)
.SetDefaultValue("abc")
.SetIgnoreIfDefault(true);
});
The sample above can be found here

AutoMapper auto create createMap

I have a services that is calling another services. Both of the services are using "the same classes". The classes are named same and have the same properties but has different namespace so I need to use AutoMapper to map from one of the type to the other type.
No it's pretty simple since all I have to do is the CreateMap<>, but the problem is that we have around hundreds of classes that I manually needs to write the CreateMap<> from, and it's works wired to me. Isn't there any Auto CreateMap function. So if I say CreateMap() then AutoMapper workes thru Organisation and finds all classes and automatically does the CreateMap for these Classes and it's subclasses etc etc…
Hope for a simple solution, or I guess some reflection can fix it...
Just set CreateMissingTypeMaps to true in the options:
var dto = Mapper.Map<FooDTO>
(foo, opts => opts.CreateMissingTypeMaps = true);
If you need to use it often, store the lambda in a delegate field:
static readonly Action<IMappingOperationOptions> _mapperOptions =
opts => opts.CreateMissingTypeMaps = true;
...
var dto = Mapper.Map<FooDTO>(foo, _mapperOptions);
UPDATE:
The approach described above no longer works in recent versions of AutoMapper.
Instead, you should create a mapper configuration with CreateMissingTypeMaps set to true and create a mapper instance from this configuration:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
var mapper = config.CreateMapper();
If you want to keep using the old static API (no longer recommended), you can also do this:
Mapper.Initialize(cfg =>
{
cfg.CreateMissingTypeMaps = true;
// other configurations
});
UPDATE 2 - Automapper 9 and later:
Starting from Automapper version 9.0, the CreateMissingTypeMaps API was removed. Automapper documentation now suggests to explicitly configure maps, manually or using reflection.
https://docs.automapper.org/en/stable/9.0-Upgrade-Guide.html#automapper-no-longer-creates-maps-automatically-createmissingtypemaps-and-conventions
CreateMissingTypeMaps can be set within your profile. It's however recommended to explicitly use CreateMap for each mapping and call AssertConfigurationIsValid in your unit tests for each profile to prevent silent errors.
public class MyProfile : Profile {
CreateMissingTypeMaps = true;
// Mappings...
}
AutoMapper has a DynamicMap method which you might be able to use: here's an example unit test illustrating it.
[TestClass]
public class AutoMapper_Example
{
[TestMethod]
public void AutoMapper_DynamicMap()
{
Source source = new Source {Id = 1, Name = "Mr FooBar"};
Target target = Mapper.DynamicMap<Target>(source);
Assert.AreEqual(1, target.Id);
Assert.AreEqual("Mr FooBar", target.Name);
}
private class Target
{
public int Id { get; set; }
public string Name { get; set; }
}
private class Source
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Set CreateMissingTypeMaps option to true. This is package AutoMapper.Extensions.Microsoft.DependencyInjection's example for ASP.NET Core:
public class Startup {
//...
public void ConfigureServices(IServiceCollection services) {
//...
services.AddAutoMapper(cfg => { cfg.CreateMissingTypeMaps = true; });
//...
}
//...
}
In case someone is still interested in this topic, I've created a NuGet package that gives the automatic mapping functionality since AutoMapper removed it in a certain version.
It's available under wakiter.AutoMapper.Extensions name.
To use it, invoke the CreateAutoMap extension method and it'll do the work for you.
Today I needed this in some generic code as well. I tried something like this:
private static IMapper CreateMapper<T1, T2>()
{
return new MapperConfiguration(cfg => FillMapperConfig(cfg, typeof(T1), typeof(T2)))
.CreateMapper();
}
private static void FillMapperConfig(IMapperConfigurationExpression cfg, Type T1, Type T2)
{
if (T1 == T2)
{
return;
}
cfg.CreateMap(T1, T2);
foreach (PropertyInfo propertyInfo in T1.GetProperties())
{
PropertyInfo correspondingProperty =
T2.GetProperties()
.FirstOrDefault(p =>
p.Name == propertyInfo.Name);
if (correspondingProperty != null)
{
if (propertyInfo.PropertyType.IsGenericType &&
correspondingProperty.PropertyType.IsGenericType)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType.GetGenericArguments()[0],
correspondingProperty.PropertyType.GetGenericArguments()[0]);
}
else if (propertyInfo.PropertyType.IsClass &&
correspondingProperty.PropertyType.IsClass)
{
FillMapperConfig(
cfg,
propertyInfo.PropertyType,
correspondingProperty.PropertyType);
}
}
}
}
Then I can do something like this:
IMapper mapper = CreateMapper<ClassA, ClassB>();
Which creates a map from ClassA to ClassB with all sub properties of ClassA and ClassB if they have the same name and recursively for sub sub properties.
Example:
public class ClassA {
public int IntProperty { get; set; }
public ClassASubProperty SubProperty { get; set; }
public List<ClassAListItem> ListItems { get; set; }
}
public class ClassB {
public int IntProperty { get; set; }
public ClassBSubProperty SubProperty { get; set; }
public List<ClassBListItem> ListItems { get; set; }
}
This should result in the IMapper equivalent:
new MapperConfiguration(cfg => {
cfg.CreateMap<ClassA, ClassB>();
cfg.CreateMap<ClassASubProperty, ClassBSubProperty>();
cfg.CreateMap<ClassAListItem, ClassBListItem>()
}).CreateMapper();

Categories

Resources