I have a custom class (to be simple) :
using System;
using System.ComponentModel.DataAnnotations;
public class MyClass {
[Required]
public string Title { get; set;}
[Required]
public string Description { get; set;}
}
I want to validate this object, and get an exception with everything not correct.
If I do :
void Validate() {
var objectToValidate = new MyClass { }; // Both properties are null at this time
var ctx = new ValidationContext(objectToValidate, null, null);
Validator.ValidateObject(objectToValidate, ctx, true);
}
A ValidationException is thrown but it shows only the first error, even I specify true to the validateAllProperties parameter.
if I refactor a bit my code :
void Validate() {
var objectToValidate = new MyClass { }; // Both properties are null at this time
var ctx = new ValidationContext(objectToValidate, null, null);
var errors = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(objectToValidate, ctx, errors, true);
if(!isValid) {
throw new AggregateException(
errors.Select((e)=>new ValidationException(e.ErrorMessage)
);
}
}
I can actually have all my errors.
Why does the first code snippet works as expected ? Did I do something wrong?
Validator.ValidateObject goes through all validation attributes and throws ValidationException for the first one that fails. I don't think one can get the list of all properties that failed this way.
The MSDN documentation is a bit confusing here, it says:
The ValidateObject method evaluates each ValidationAttribute attribute that is associated with the object type. If validateAllProperties is set to true, the method validates the property values of the object.
TryValidateObject method manual continues:
It also checks whether each property that is marked with RequiredAttribute is provided.
I confirm that it always checks all the Required attributes, regardless of validateAllProperties. But if you have, for example, a Range validation attribute it will only check it when validateAllProperties is true (because this is when it validates the property value). A bit confusing, but as you noticed, TryValidateObject shows all the failed properties and this is what I'm using for my project as well.
Related
I have the following minimal C# code.
Notice that my source class From has a nullable property called Original. My destination To is a record.
When I run this code, I get a NullReferenceException.
If the property is called anything other than Original, the code works.
If the destination is a class instead of a record, the code works
Why does it only throw when the property is called Original and the destination is a record?
using AutoMapper;
var mapper = new MapperConfiguration(conf =>
{
conf.CreateMap<From, To>();
}).CreateMapper();
var from = new From();
var to = mapper.Map<To>(from);
public class From
{
public string? Original { get; set; }
public bool BoolProp { get; set; }
}
public record To
{
public bool BoolProp { get; init; }
}
From my own investigation, it seems that the autogenerated class for the To record actually has two constructors. One takes an existing To and the other is parameterless. Sharplab:
protected To(To original)
{
<BoolProp>k__BackingField = original.<BoolProp>k__BackingField;
}
public To()
{
}
A call to mapper.ConfigurationProvider.AssertConfigurationIsValid(); states:
Unhandled exception. System.AggregateException: One or more errors occurred. (The following member on To cannot be mapped:
To.Void .ctor(To).parameter original
It seems that the constructor parameter of To being named original and your property being named as original is causing AutoMapper to choose an incompatible constructor from the record.
It seems that this is probably an edge case bug that you've discovered. You should probably report this to the maintainers via GitHub.
As for a workaround, you could change your property name away from Original or explicitly tell AutoMapper how to construct a To: conf.CreateMap<From, To>().ConstructUsing(_ => new To());.
Either approach solves the problem and satisfies mapper.ConfigurationProvider.AssertConfigurationIsValid();.
.Net Core allows you to decorate a property with [DisallowNull] to tell the compiler that code is not allowed to set the property to null, even if the property itself is declared to allow it. For example:
public sealed class Test
{
[DisallowNull] public string? Text { get; set; }
}
This works fine when you try to explicitly set the property to null:
var test = new Test();
test.Text = null; // Warning: "Cannot convert null literal to non-nullable reference type".
However, it doesn't work if you use an object initializer:
var test = new Test
{
Text = null // No warning. I want one.
};
Is there any way to make the code above cause a compiler warning/error?
Until the fix is released, you can work around this by flipping it around:
public sealed class Test
{
[MaybeNull] public string Text { get; set; } = null!;
}
This warns that Text has a default value of null (hence the need to suppress that with = null!), but otherwise seems to do what you want.
SharpLab
When validating data annotations, why does it only seem to validate (correctly) when using a get accessor on the property you want to validate?
Given the example below, the Name property will always be considered valid, even though it is unassigned, however the Address property will only be considered valid if it assigned a non-empty string value, all because it's using a get accessor. Why is this?
Surely TryValidateObject will basically just use test.Address to access the value whether it's through a get accessor or not.
class Program
{
static void Main(string[] args)
{
var test = new Test();
var _errors = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(
test, new ValidationContext(test), _errors, true);
}
}
public class Test
{
[Required]
public string Name; // Returns true even when unassigned / null / empty string
[Required]
public string Address { get; } // Only returns true when assigned a non-empty string
}
The reason is Validator.TryValidateObject will only check properties and class-level annotations, but your Name is a field, not a property (see here that documentation of Validator class does not mention fields). If you apply any other attribute (like Range) to a field - it also won't work with Validator.TryValidateObject. The reason why RequiredAttribute can be applied to a field is because RequiredAttribute is an entity on it's own and is not related\tied to Validator class in any way. Any other validation mechanism can validate public fields, it's just specific Validator class which does not.
I have a WebApi method, like this one:
public string Get([FromUri] SampleInput input)
{
//do stuff with the input...
return "ok";
}
The input is defined like this:
public class SampleInput
{
// ...other fields
public bool IsAwesome { get; set; }
}
As it is, it works OK: if I pass &isAwesome=true in the query string, the parameter is initializes with the value true.
My problem is that I'd like to accept both &isAwesome=true and &isAwesome=1 as true values. Currently, the second version will result in IsAwesome being false in the input model.
What I tried, after reading the various blog posts on the subject, was to define an HttpParameterBinding:
public class BooleanNumericParameterBinding : HttpParameterBinding
{
private static readonly HashSet<string> TrueValues =
new HashSet<string>(new[] { "true", "1" }, StringComparer.InvariantCultureIgnoreCase);
public BooleanNumericParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor)
{
}
public override Task ExecuteBindingAsync(
ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken)
{
var routeValues = actionContext.ControllerContext.RouteData.Values;
var value = (routeValues[Descriptor.ParameterName] ?? 0).ToString();
return Task.FromResult(TrueValues.Contains(value));
}
}
... and register it in Global.asax.cs, using:
var pb = GlobalConfiguration.Configuration.ParameterBindingRules;
pb.Add(typeof(bool), p => new BooleanNumericParameterBinding(p));
and
var pb = GlobalConfiguration.Configuration.ParameterBindingRules;
pb.Insert(0, typeof(bool), p => new BooleanNumericParameterBinding(p));
None of these worked. My custom HttpParameterBinding is not being called and I still get the value 1 translated to false.
How can I configure WebAPI to accept the value 1 as true for Booleans?
Edit: The example I presented is intentionally simplified. I have a lot of input models in my application and they contain many boolean fields that I would like to be handled in the manner described above. If there was just this one field, I would not have resorted to such complex mechanisms.
Looks like decorating the parameter with the FromUriAttribute just skips the parameter binding rules altogether. I made a simple test replacing the SampleInput input parameter with a simple bool:
public string Get([FromUri] bool IsAwesome)
{
//do stuff with the input...
return "ok";
}
and the boolean rule is still not getting called (IsAwesome is coming as null when you call &isAwesome=1).
As soon as you remove the FromUri attribute:
public string Get(bool IsAwesome)
{
//do stuff with the input...
return "ok";
}
the rule gets called and the parameter correctly bound.
The FromUriAttribute class is sealed, so I think you're pretty much screwed - well, you can always reimplement it and include your alternate boolean binding logic ^_^.
I know testing non public members is considered bad, but still why this doesn't work?
It throws ArgumentException with message: Member Connect doesn't exists
public class FtpHelper : IFtpHelper
{
public FtpHelper(ISignalRLogger signalRLogger, IDirectoryWrap directoryWrap = null) {
... some code
}
protected virtual IFtpClient Connect(string ftpPath) {
... some code
}
}
public class TestFtpHelper
{
[Fact]
public void Calls_connect(){
var signalrMq = new Mock<ISignalRLogger>();
var clientMq = new Mock<FtpHelper>(
MockBehavior.Strict, new { signalRLogger = signalrMq.Object });
clientMq.Protected().Setup<IFtpClient>("Connect",new { ftpPath = ""})
.Returns(It.IsAny<IFtpClient>()); // That doesn't work
}
}
UPD: oh I noticed that even before the last line when it's initializing clientMq and I try to use clientMq.Object it throws Exception with this message: A matching constructor for the given arguments was not found on the mocked type. I guess it has something to do with that strange constructor with default value
You need following addtional setup
1. Add using Moq.Protected() at the top
2. Make types visible to Moq using InternalsVisibleTo attribute.
[assembly: InternalsVisibleTo(RhinoMocks.NormalName)]
[assembly: InternalsVisibleTo(RhinoMocks.StrongName)]
From the link
I see what's wrong...
this:
var clientMq = new Mock<FtpHelper>(
MockBehavior.Strict, new { signalRLogger = signalrMq.Object });
should be:
var clientMq = new Mock<FtpHelper>(
MockBehavior.Strict, signalrMq.Object);
signature is params object[]
but now it throws NullReferenceException
Upd: the members that you're trying to mock not only should be virtual but also internal'. Making them 'protected simply not enough. Proxies can't be identified through Refection.Emit() and Moq uses that/