I have a class which has various child objects:
public class ApplicationPayload
{
public Quote Quote { get; set; }
public IApplication Application { get; set; }
public DeliveryPreferences DeliveryPreferences { get; set; }
}
I have an api controller method which accepts this model:
public async Task<IActionResult> LtdCompanyPost([FromBody] ApplicationPayload payload)
{
}
When submitting to the controller method, properties within classes that implement IApplication are not being validated (validation seems to be being ignored), however, the other objects (Quote / DeliveryPreferences) are being validated as expected.
Is it possible to have my objects implementing IApplication validatable, or is this structure simply not going to work for me?
(I tested the objects implementing IApplication by having them at the same level as Quote/DeliveryPreferences, having removed the interface implementation, and the validation worked as expected, so the validation rules themselves are not the issue).
Any advice? I can give more examples if necessary.
My guess is you are falling foul of the below check in the ComplexModelBinder, an interface has no constructor.
But more broadly, how would it know what implementation of the interface to instantiate?
if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null)
{
var metadata = bindingContext.ModelMetadata;
switch (metadata.MetadataKind)
{
case ModelMetadataKind.Parameter:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(
modelTypeInfo.FullName,
metadata.ParameterName));
case ModelMetadataKind.Property:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(
modelTypeInfo.FullName,
metadata.PropertyName,
bindingContext.ModelMetadata.ContainerType.FullName));
case ModelMetadataKind.Type:
throw new InvalidOperationException(
Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(
modelTypeInfo.FullName));
}
}
https://github.com/aspnet/Mvc/blob/24eaa740f5b1736700d8d91053f60d690f4fc17e/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs#L366,
Related
I am having trouble getting FluentValidation to work with a collection of objects. My controller POST action takes in an IEnumerable of objects like below. When I post to an action that takes a single EventInputDto, with an incorrectly formatted Url property, my validation occurs successfully. When I post to a collection of EventInputDto, it does not work and does no validation.
If I use regular MVC Attributes (i.e. required / email), they work with collections as well as single objects. How do I get this to work with FluentValidation? I am not working with inner collections so I'm not sure why it does not work as intended.
public async Task<IActionResult> CreateEventCollection([FromBody] IEnumerable<EventInputDto> events)
{
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState); //does not work
}
}
My validators are setup using generics because I am using separate models for inputs and updates.
public class EventManipulationValidator<T> : AbstractValidator<T> where T : EventManipulationDto
{
public EventManipulationValidator()
{
RuleFor(manipulationDto => manipulationDto.Title).NotNull().WithMessage("Title cannot be blank")
.Length(1, 50);
RuleFor(manipulationDto => manipulationDto.Message).NotNull().WithMessage("Message cannot be blank")
.Length(1, 1000);
RuleFor(manipulationDto => manipulationDto.ScheduledTime).NotNull().WithMessage("Scheduled Time cannot be blank");
RuleFor(inputDto => inputDto.Url).Matches(#"https://.*windows\.net.*").WithMessage("The url must be valid and stored on Azure");
}
}
As my CreateEventCollection action takes in an IEnumerable of EventInputDto, my validator for EventInputDto is setup as below:
public class EventInputValidator : EventManipulationValidator<EventInputDto>
{
public EventInputValidator()
{
//all property validators are inherited from EventManipulationValidator
}
}
public class EventInputCollectionValidator : AbstractValidator<IEnumerable<EventInputDto>>
{
public EventInputCollectionValidator()
{
RuleForEach(p => p).SetValidator(new EventManipulationValidator<EventInputDto>());
}
}
Below are my models for reference:
EventManipulationDto
public abstract class EventManipulationDto
{
public string Title { get; set; }
public string Message { get; set; }
public string Url { get; set; }
public DateTime? ScheduledTime { get; set; }
}
EventInputDto
public class EventInputDto : EventManipulationDto
{
//all properties inherited from base class
}
After going through the list of open/closed issues on the project GitHub, it seems that not all of my approach is required. There is no need for my `EventInputCollectionValidator. FluentValidation no longer requires explicitly defining an IEnumerable validator like I defined above.
It's enough to define a base AbstractValidator or as in my case an inherited validator from a parent class.
The only change needed to get it to work was in my startup.cs when registering fluentvalidation. I needed to explicitly add ImplicitlyValidateChildProperties = true. Didn't realize this was required as I thought this was for validating child property collections and not the parent collection objects. Works perfectly now.
.AddFluentValidation(fv => {
fv.RunDefaultMvcValidationAfterFluentValidationExecutes = true;
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
fv.ImplicitlyValidateChildProperties = true;
});
Preface: This code is used within a windows desktop application, client / server application, where the server sends and receives messages to/from the client using SMessage based classes
I have the following interface
public interface IMessage
{
string ID { get; }
string R_ID { get; set; }
DateTime Send { get; }
}
Here is the concrete implementation of this interface:
[Serializable]
public class SMessage : IMessage
{
public string ID { get; set; }
public string R_ID { get; set; }
public DateTime Send{ get; set;}
public SMessage()
{
R_ID = "";
ID = Guid.NewGuid().ToString();
Send = DateTime.UtcNow;
}
public SMessage(SMessage msg)
{
ID = msg.ID;
Send = msg.UTCSend;
R_ID = msg.R_ID;
}
}
I have released software to the world using the above interface and now I need to add a piece of additional data to this interface "Where"
public interface IMessage
{
string ID { get; }
string R_ID { get; set; }
DateTime Send { get; }
string Where { get; }
}
My question : Will adding this piece of data break existing clients in the field?
If so, how I can I update the interface / concrete classes so existing clients don't break?
Thanks
Additional info:
The SMessage is the base class for other messages that are sent within the application:
public class InstallMessage : SMessage
{
}
public class ChangeState : SMessage
{
}
How can I keep from breaking existing clients?
So, if I do this:
public interface IMessage2 : IMessage
{
string Where { get; }
}
And this:
public class SMessage : IMessage2
{
// the correct implementation for IMessage2 is added and omitted here for brevity
}
So what I am unsure about is how do I handle the case where I don't know if the message is from IMessage2 or not? ( NOTE: this code is in the client and server applications )
EXISTING CODE IN THE FIELD:
public void ReceiveChange( ChangeState msg )
{
string x = msg.ID.ToString();
}
NEW CODE THAT WILL BE SENT OUT WITH NEXT VERSION:
public void ReceiveChange( ChangeState msg )
{
string x = msg.ID.ToString();
// do I need to do some converting to keep from breaking ?
IMessage2 iMsg = msg as IMessage2;
if( iMsg2 != null )
{
string y = iMsg2.Where;
}
}
Thanks
Your interface's consumers won't complain, but the implementations will.
If you want to avoid this, then create a new interface that extends from the old one:
public interface INewMessage : IMessage
{
string Where { get; set; }
}
If in an WebAPI scenario.
It will only break existing clients if you have a dependency upon the newly added fields in a method that accepts IMessage as a parameter.
public void ServiceMethod(IMessage message) {
if (message.Where == null)
throw new ArgumentException("message.Where is null");
}
you can add things to interfaces and as long as you have code to properly handle the missing information existing clients will be fine.
The proper way to handle this though is to 'version' your services and data contracts. I find namespace versioning the easiest to maintain. You would define a new namespace (say v2) and redefine everything that actually changes, methods, data contracts, etc. And then in your routing, route the v2 messages (http://acme.com/api/v2/messages) to the new namespace or if not specially routed (http://acme.com/api/messages) route it to the old namespace.
If in a directly referenced library.
Then yes - it will break existing clients. Unless your factory that produces concrete implementations can determine which the client wants. Something similar to the WebAPI routing - but for directly referenced libraries. But this is extremely difficult.
Yes, it will break the existing clients if they implmented their own classes that use IMessage that do not derive from SMessage. This is the reason why Microsoft has not updated interfaces in the .NET framework between versions to add new features. For example in .NET 4.5 DbDataReader got new async methods that returned tasks but they could not update IDataReader because that would have broken anyone who implemented IDataReader without deriving from DbDataReader.
If you don't want to break the code of people who created classes with IMessage but without using SMessage you must either create a new derived interface that has the additional field (For example this is what COM objects do, you will often see ISomeInterface, ISomeInterface2, ISomeInterface3 etc.) or not update the interface at all and only update concrete implementations that other people may have derived from.
I'm writing a client and service using WCF, however I suspect that there are multiple issues at the moment.
As you can see from the code below, the process is as follows: The client asks for some data, which the service generates and returns in a DTO-object. The client then attempts to do a look-up in a returned dictionary, but this throws a KeyNotFoundException.
In addition, a test on the server fails before this (if left uncommented) because the input parameter list allBranches no longer contains Branch currentBranch, which it did on the client side of the method call.
Can someone enlighten me as to what happens in this code, and why it blows up first on the server side and later on the client side?
Shared class definitions
------------------------
[DataContract(IsReference = true)]
public class Branch
{
public Branch(int branchId, string name)
{
BranchId = branchId;
Name = name;
}
[DataMember]
public int BranchId { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Department
{
public string Name { get; set; }
// a few other properties, both primitives and complex objects
}
[DataContract]
public class MyDto
{
[DataMember]
public IDictionary<Branch, List<Department>> DepartmentsByBranch { get; set; }
[DataMember]
public Branch CurrentBranch { get; set; }
// lots of other properties, both primitives and complex objects
}
Server-side
--------------------------------
public CreateData(List<Branch> allBranches, Branch currentBranch)
{
// BOOM: On the server side, currentBranch is no longer contained in allBranches (presumably due to serialization and deserialization)
if (!branches.Contains(branchToOpen))
{
throw new ArgumentException("allBranches no longer contain currentBranch!");
}
// Therefore, I should probably not do the following, expecting to use currentBranch as a key in departmentsByBranch later on
var departmentsByBranch = branches.ToDictionary(branch => branch, branch => new List<Department>());
return new MyDto
{
DepartmentsByBranch = departmentsByBranch,
CurrentBranch = departmentsByBranch,
};
}
Client-side (relevant code only)
--------------------------------
var service = new ServiceProxy(); // using a binding defined in app.config
var allBranches = new List<Branch>
{
new Branch(0, "First branch"),
new Branch(1, "Second branch"),
// etc...
};
var currentBranch = allBranches[0];
MyDto dto = service.CreateData(allBranches, currentBranch);
var currentDepartments = dto.DepartmentsByBranch[currentBranch]; // BOOM: Generates KeyNotFoundException
EDIT: I followed Jon's excellent answer below and did the following (which fixed all problems):
Made Branch immutable by giving every property a private setter.
Every class used as a key in a dictionary should be immutable, or at least have its hash code computed from immutable properties.
Implemented IEquatable + overrides of Object.Equals and GetHashCode, the latter as per this SO-answer (link)
Implementing IEquatable is done simply by testing for equal property values,
public bool Equals(Branch other)
{
return other != null && ((BranchId == other.BranchId) && (Name == other.Name));
}
The reason your code fails is that you have two separate Branch instances in your client: the one you create locally (currentBranch) and the one that gets received from the server and created implicitly by WCF (inside dto.DepartmentsByBranch). You have not specified that these two instances are "the same thing", so as far as the dictionary is concerned it has never seen that currentBranch you are talking about.
You need to give Branch a proper implementation of IEquatable<Branch> -- and the same goes for all classes you use as dictionary keys.
Note that "proper implementation" means
If you implement IEquatable<T>, you should also override the base
class implementations of Object.Equals(Object) and GetHashCode so that
their behavior is consistent with that of the IEquatable<T>.Equals
method.
I have a small problem with the WebApi.
Problem:
If I want to post a model using JSON, I can add as many members I want, as long as the members defined in model are present.
Question:
How can I trigger an exception, if an undefined member is present in my Json object. Is this achievable without a custom JsonConverter?
What I'm looking for is a generic solution, not a convertion for every different model.
Example:
Model:
public class Person
{
[Required]
public string Name { get; set; }
}
Api Controller:
public class PersonController : ApiController
{
public HttpResponseMessage Post(Person person)
{
if (person != null)
{
if (ModelState.IsValid)
{
//do some stuff
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
}
Json posts (body)
{"Name":"Joe"} --> valid
{"Name":"Joe","InvalidMember","test","Name","John"} --> also valid. In this case I want to trigger an Exception. Because if you look at it, it doesn't match my modeldefinition exactly.
One thing you could try is playing around with this setting:
config.Formatters.JsonFormatter.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Error;
It should give you an invalid model state when there are extra properties that aren't recognized in the JSON.
I have a FluentValidation validator that I want to use to validate a booking. On a booking you must choose a room type that exists as an available room type on the tour that you are choosing. I need to get the available room types from a service, passing in the code for the tour. What is the best way to handle getting the tour code where it needs to be?
What I've got so far:
public class BookingValidator : AbstractValidator<Booking>, IBookingValidator
public BookingValidator()
{
RuleFor(booking => booking.Rooms).SetValidator(new RoomValidator())
}
public class RoomValidator : AbstractValidator<Room>
public RoomValidator()
{
//validate that room.Type (eg. TWIN) exists in availableRoomTypes (eg List<string> {'SINGLE','TWIN'}
}
Some hack at the problem:
public class BookingValidator : AbstractValidator<Booking>
//should/can i pass in arguments here when IoC container is wiring up IBookingValidator to BookingValidator? Seems awkward
public BookingValidator(string tourCode)
{
//if so, use argument to get available room types, pass to RoomValidator
var availableRooms = RoomTypeService.GetAvailableRoomTypesForTour(tourCode);
RuleFor(booking => booking.Rooms).SetValidator(new RoomValidator(availableRooms))
//alternatively, tourCode is available from booking - is there some way to pass it to RoomValidator?
RuleFor(booking => booking.Rooms).SetValidator(new RoomValidator(),booking => booking.TourCode);
//Or is there some way I should be using .Must() or Custom()??
}
So the main problem is how or where to get tour code into the validator...?
I would suggest creating a service that has dependencies on IRoomTypeService and IBookingValidator. It gets the available room types from the IRoomTypeService dependency and passes them to the validator via a property. See the following code by way of example:
public class BookingValidationService : IBookingValidationService
{
public IRoomTypeService RoomTypeService { get; set; }
public IBookingValidator BookingValidator { get; set; }
public ValidationResult ValidateBooking(Booking booking, string tourCode)
{
BookingValidator.AvailableRooms = RoomTypeService.GetAvailableRoomTypesForTour(tourCode);
return BookingValidator.Validate(booking);
}
}