FluentValidation and nested validator - c#

I have a class:
public class ClientInfo
{
public string LabAccount { get; set; }
//....
}
and validator class:
public class ClientInfoFluentValidator : AbstractValidator<ClientInfo>
{
public ClientInfoFluentValidator()
{
RuleFor(d => d.LabAccount)
.NotEmpty()
.WithMessage("LabAccount is required");
RuleFor(d => d.LabAccount)
.Length(8)
.WithMessage("LabAccount is limited by 8 letters");
//....
}
}
then I have class, which has ClientInfo class as property:
public class Order
{
public ClientInfo ClientInfo { get; set; }
//....
}
and validator class:
public class OrderFluentValidator : AbstractValidator<Order>
{
public OrderFluentValidator()
{
//...
RuleFor(d => d.ClientInfo)
.NotNull()
.WithMessage("ClientInfo part is required");
RuleFor(d => d.ClientInfo)
.SetValidator(new ClientInfoFluentValidator());
}
}
When I try to validate only ClientInfo it works:
ClientInfoFluentValidator validator = new ClientInfoFluentValidator();
[TestMethod]
public void ClientInfoInvalidLabAccountLength()
{
ClientInfo model = new ClientInfo
{
LabAccount = "1234567"
//....
};
validator.ShouldHaveValidationErrorFor(d => d.LabAccount, model);
//....
}
but when I try to validate Order class:
OrderFluentValidator validator = new OrderFluentValidator();
[TestMethod]
public void OrderInfoValid()
{
Order model = new Order
{
ClientInfo = new ClientInfo
{
LabAccount = "1234567"
//....
},
//....
};
validator.ShouldHaveValidationErrorFor(d => d.ClientInfo, model);
}
It says, that model class is valid. Why so? why ClientInfo validator does not work?

You need to specify the exact property on the child view model that should have the error message. This appears to be a problem with the assertion, not your view models or validators:
validator.ShouldHaveValidationErrorFor(d => d.ClientInfo.LabAccount, model);

Related

Automapper setting property to null when it should ignore

I was trying to implement a code to ignore a property (therefore mantaining the source value). I used the ignore method, which works most of the time. For some reason I noticed that sometimes the ignore sets the property value to null.
Do you know what could be problem?
I created the following code to reproduce the issue. I was expecting client.ContactDetails.First().Address to have the value "Old".
using AutoMapper;
class Program
{
static void Main(string[] args)
{
ClientMapperProfile clientMapperProfile = new ClientMapperProfile();
var configurationProvider = new MapperConfiguration(c => c.AddProfile(clientMapperProfile));
Mapper mapper = new Mapper(configurationProvider);
var client = new Client()
{
ContactDetails = new []
{
new ContactDetails()
{
Address= "Old"
}
}
};
var clientDto = new ClientDto()
{
ContactDetails = new []
{
new ContactDetailsDto()
{
Address = "New"
}
}
};
mapper.Map(clientDto,client);
Console.WriteLine(client.ContactDetails.First().Address);
}
}
public class Client
{
public ContactDetails[] ContactDetails { get; set; }
}
public class ContactDetails
{
public string Address { get; set; }
}
public class ClientDto
{
public ContactDetailsDto[] ContactDetails { get; set; }
}
public class ContactDetailsDto
{
public string Address { get; set; }
}
public class ClientMapperProfile : Profile
{
public ClientMapperProfile()
{
CreateMap<ClientDto, Client>();
CreateMap<ContactDetailsDto, ContactDetails>()
.ForMember(c => c.Address, opt => opt.Ignore());
}
}

How to map Complex type using automapper

Automapper with complex nested mapping. I am trying to map mydestinationArrayField and
dest1Array, here source objectlist to be copied to dest1array.
here are my classes for source and destination.
namespace AutomapperDemo
{
class Program
{
static void Main(string[] args)
{
try
{
SourceObject request = new SourceObject()
{
sourceTypeField = "1",
SourceObj1Field = new SourceObj1
{
SourceObj1Id = "1",
SourceObjListss = new List<SourceInnerObjList>
{
new SourceInnerObjList
{
SourceObjListItem1Id = 1
},
new SourceInnerObjList
{
SourceObjListItem1Id = 2
}
}
}
};
var mapper = CreateMapper();
DestinationObject destination = new DestinationObject();
destination = mapper.Map<DestinationObject>(request);
}
catch (Exception ex)
{
throw ex;
}
}
public static IMapper CreateMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AllowNullDestinationValues = true;
cfg.CreateMap<SourceObject, DestinationObject>()
.ForMember(dest => dest.destinationTypeField, o => o.MapFrom(src => src.sourceTypeField))
.ForMember(dest => dest.destinationObjectArrayField, o => o.MapFrom(src => new destinationObjectArray()
{
mydestinationArrayField = src.SourceObj1Field.SourceObjListss.Select(x => x.SourceObjListItem1Id).FirstOrDefault().ToString(), //this gives error
//dest1Array = src.SourceObj1Field.SourceObjListss // here source objectlist to be copied to dest1array
}));
});
return config.CreateMapper();
}
}
}
namespace Automapper
{
public class SourceObject
{
public string sourceTypeField;
public SourceObj1 SourceObj1Field { get; set; }
}
public class SourceObj1
{
public string SourceObj1Id { get; set; }
public ICollection<SourceInnerObjList> SourceObjListss { get; set; }
}
public class SourceInnerObjList
{
public int SourceObjListItem1Id { get; set; }
public int SourceObjListItem2d { get; set; }
}
public class SourceInnerObj2List
{
public int? mycount { get; set; }
public int? yourcount { get; set; }
}
}
namespace Automapper
{
public class DestinationObject
{
public string destinationTypeField;
public destinationObjectArray[] destinationObjectArrayField;
}
public class destinationObjectArray
{
public string mydestinationArrayField;
public string myField1;
public destinationInnerObject1Array[] dest1Array;
public destinationInnerObject2Array[] dest2Array;
}
public class destinationInnerObject1Array
{
public string destinationInnerObjectItem11;
public string destinationInnerObjectItem21;
}
public class destinationInnerObject2Array
{
public string categoryTypeField;
public string valueField;
public string NumberField;
}
}
While executing the mapping i am getting "Missing type map configuration or unsupported mapping."
No matter how I configure ignores or custom mappings it seems to not like this nesting. Any Automapper experts out there who could tell me how a mapping with a complex object like this could be done.
It seems like your second ForMember doesn't work:
.ForMember(dest => dest.destinationObjectArrayField, o => o.MapFrom(src => new destinationObjectArray() //...
Because in you're defining your map to return destinationObjectArray()
and not destinationObjectArray[]!
So there is a map like:
destinationObjectArrayField -> destinationObjectArray()
and there is no map like:
destinationObjectArrayField -> destinationObjectArray[]
And Automapper is telling you that.
You should do something like this:
cfg.CreateMap<SourceObject, DestinationObject>()
.ForMember(dest => dest.destinationTypeField, o => o.MapFrom(src => src.sourceTypeField))
.ForMember(dest => dest.destinationObjectArrayField,
o => o.MapFrom(
src => src.SourceObj1Field
.SourceObjListss
.Select(x => new destinationObjectArray
{
myField1 = $"first_id: {x.SourceObjListItem2d} second_id: {x.SourceObjListItem1Id}"
})
.ToArray()
));
});
Also cleaning and formatting code is highly suggested, it's seems like you've simply got lost in it! IDE's can help with that.

FluentValidation on collection of strings

I have the following code:
public partial class CustomerContactCommunicationValidator : AbstractValidator<CustomerCommunication>
{
public CustomerContactCommunicationValidator()
{
CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.PhoneNumbers).SetCollectionValidator(new FaxPhoneNumberValidator("PhoneNumber"));
RuleFor(x => x.FaxNumbers).SetCollectionValidator(new FaxPhoneNumberValidator("Faxnumbers"));
}
}
public class FaxPhoneNumberValidator : AbstractValidator<string>
{
public FaxPhoneNumberValidator(string collectionName)
{
RuleFor(x => x).Length(0, 35).OverridePropertyName(collectionName);
}
}
PhoneNumbers and FaxNumbers are declared as List.
My unit tests:
[TestMethod]
[TestCategory("ValidationTests")]
public void ShouldHaveErrorWhenPhoneNumberIsLongerThan35Charachters()
{
validator.ShouldHaveValidationErrorFor(x => x.PhoneNumbers, new List<string>() { "123456789012345678901234567890123456111" });
}
[TestMethod]
[TestCategory("ValidationTests")]
public void ShouldNotHaveErrorWhenPhoneNumberIsSmallerThan35Charachters()
{
validator.ShouldNotHaveValidationErrorFor(x => x.PhoneNumbers, new List<string>() { "0032486798563" });
}
The first test fails, the second one does not.
Also when I do a live test, it succeeds on a phone number which is larger than 35 charachters.
I've seen other questions about this: How do you validate against each string in a list using Fluent Validation?
But I really don't see what I'm doing wrong.
Check this example it might clarify all your doubts.
Validation classes:
using FluentValidation;
using System.Collections.Generic;
namespace Test.Validator
{
public class EmailCollection
{
public IEnumerable<string> email { get; set; }
}
public class EmailValidator: AbstractValidator<string>
{
public EmailValidator()
{
RuleFor(x => x).Length(0, 5);
}
}
public class EmailListValidator: AbstractValidator<EmailCollection>
{
public EmailListValidator()
{
RuleFor(x => x.email).SetCollectionValidator(new EmailValidator());
}
}
}
Try to use:
public class CustomerContactCommunicationValidator : AbstractValidator<CustomerCommunication>
{
public CustomerContactCommunicationValidator()
{
RuleForEach(x => x.PhoneNumbers).Length(0, 35);
RuleForEach(x => x.FaxNumbers).Length(0, 35);
}
}

map configuration or unsupported mapping

I have two types. One in the business layer:
namespace Business
{
public class Car
{
private int _id;
private string _make;
private string _model;
public int id
{
get { return _id; }
set { _id = value; }
}
public string make
{
get { return _make; }
set { _make = value; }
}
public string model
{
get { return _model; }
set { _model = value; }
}
}
}
and the other in the Data layer (Entity Framework):
namespace Data
{
using System;
using System.Collections.Generic;
public partial class Car
{
public Car()
{
this.facttables = new HashSet<facttable>();
}
public int id { get; set; }
public string make { get; set; }
public string model { get; set; }
public virtual ICollection<facttable> facttables { get; set; }
}
}
Here is the code I get from the service layer:
namespace Data
{
public class VehicleDAO : IVehicleDAO
{
private static readonly ILog log = LogManager.GetLogger(typeof(VehicleDAO));
MapperConfiguration config;
public VehicleDAO ()
{
Mapper.Initialize(cfg => cfg.CreateMap<Business.Car, Data.Car>());
config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Business.Car, Data.Car>()
.ForMember(dto => dto.facttables, opt => opt.Ignore());
//.ForMember(d => d.id, opt => opt.MapFrom(c => c.id))
//.ForMember(d => d.make, opt => opt.MapFrom(c => c.make))
//.ForMember(d => d.model, opt => opt.MapFrom(c => c.model));
});
config.AssertConfigurationIsValid();
}
public Data.Car Select(int id)
{
Data.Car car;
using (VehicleEntities VehicleDatabase = new VehicleEntities())
{
car = VehicleDatabase.Cars.Where(c => c.id == id).ToList().Single();
Business.Car cars = AutoMapper.Mapper.Map<Business.Car>(car);
}
return car;
}
The exception is: {"Missing type map configuration or unsupported mapping.\r\n\r\nMapping types:\r\nCar_70BD8401A87DAAD8F5F0EC35BCAE5C9E6EE2D6CB5A1AFCE296B313D8AD87D2E9 -> Car\r\nSystem.Data.Entity.DynamicProxies.Car_70BD8401A87DAAD8F5F0EC35BCAE5C9E6EE2D6CB5A1AFCE296B313D8AD87D2E9 -> Business.Car"}. What is wrong? I have marked the line that causes the exception (third from last line).
Automapper only maps in the direction you created the mapping in. CreateMap<Business.Car, Data.Car> creates a mapping from Business.Car to Data.Car. It looks like you are trying to map from Data.Car to Business.Car, which means you need to CreateMap<Data.Car, Business.Car>
Mapper.Initialize(cfg => cfg.CreateMap<Data.Car, Business.Car>());
config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Data.Car, Business.Car>();
});
config.AssertConfigurationIsValid();

How to invoke Action set in property of Moq input parameter

I have the following method in a ViewModel.
public void ViewModelMethod()
{
UserDialogs.Confirm(new ConfirmConfig
{
Message = "Dialog message",
OnConfirm = (result) =>
{
if (result)
{
AnotherService.Method();
}
}
});
}
In my tests I have the UserDialogsMock and AnotherServiceMock. I'm trying to setup the UserDialogsMock like below.
UserDialogsMock.Setup(s => s.Confirm(It.IsAny<ConfirmConfig>()))
.Callback((ConfirmConfig confirmConfig) => confirmConfig.OnConfirm(true));
How to verify that AnotherServiceMock.Method is invoked?
If AnotherServiceMock is injected, just verify it as normal:
AnotherServiceMock.Verify(s => s.Method(), Times.Once());
Here's a SSCCE that works for me:
namespace ConsoleApplication
{
using System;
using Moq;
using NUnit.Framework;
public class MoqCallbackTest
{
[Test]
public void TestMethod()
{
Mock<IAnotherService> mockAnotherService = new Mock<IAnotherService>();
Mock<IUserDialogs> mockUserDialogs = new Mock<IUserDialogs>();
mockUserDialogs.Setup(s => s.Confirm(It.IsAny<ConfirmConfig>()))
.Callback((ConfirmConfig confirmConfig) => confirmConfig.OnConfirm(true));
SystemUnderTest systemUnderTest = new SystemUnderTest(mockUserDialogs.Object,
mockAnotherService.Object);
systemUnderTest.ViewModelMethod();
mockAnotherService.Verify(p => p.Method(), Times.Never());
}
public interface IAnotherService
{
void Method();
}
public interface IUserDialogs
{
void Confirm(ConfirmConfig config);
}
public class ConfirmConfig
{
public Action<bool> OnConfirm { get; set; }
}
public class SystemUnderTest
{
readonly IAnotherService anotherService;
readonly IUserDialogs userDialogs;
public SystemUnderTest(IUserDialogs userDialogs, IAnotherService anotherService)
{
this.userDialogs = userDialogs;
this.anotherService = anotherService;
}
public void ViewModelMethod()
{
userDialogs.Confirm(new ConfirmConfig { OnConfirm = result =>
{
if (result)
anotherService.Method();
} });
}
}
}
}

Categories

Resources