FluentValidation not working on collection of outer model objects - c#

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

Related

WebAPI deserialization of a protected property is null

My solution has a WebAPI project (.net core 3.1, Microsoft.AspNetCore.Mvc) and a (.Net Standard 2.1) class library that defines the data structures.
My Controller takes a post with a single parameter that deserializes mostly correctly
public class apiRequest
{
public RequestData TheData { get; set; }
public Options Options { get; set; }
public apiRequest() { }
}
The RequestData and child objects are defined i a .Net Standard 2.1 class library and added via a nuget package
public class RequestData : IRequestData
{
public int Datum{ get; set; }
...
public List<ComplexItem> ComplexItems { get; set; }
...
}
public class ComplexItem: ItemBase, IComplexItem
{
public ComplexItem() : base() { }
public ComplexItem(Pricing defaultPricing) : base(defaultPricing) { }
[JsonConstructor]
public ComplexItem(Pricing defaultPricing, Pricing selectedPricing) : base(defaultPricing, selectedPricing) { }
}
The problem I am running into is with the defaultPricing is always null when it gets to the controller
public class ItemBase : IItemBase
{
public ItemBase () { }
public ItemBase (Pricing defaultPricing)
{
DefaultPricing = defaultPricing;
}
[JsonConstructor]
public ItemBase (Pricing defaultPricing, Pricing selectedPricing)
{
DefaultPricing = defaultPricing;
SelectedPricing = selectedPricing;
}
#region Pricing
[JsonProperty]
protected Pricing DefaultPricing { get; set; }
public Pricing SelectedPricing { get; set; }
[JsonIgnore]
protected Pricing CurrentPricing
{
get { return SelectedPricing ?? DefaultPricing; }
set { SelectedPricing = value; }
}
[JsonIgnore]
public decimal Cost { get => CurrentPricing?.Cost ?? 0; }
[JsonIgnore]
public decimal Price { get => CurrentPricing?.Price ?? 0; }
#endregion
}
I've tried using [DataContract] and [DataMember] attributes, JsonObject, JsonConstructor, JsonProperty attributes and [Serializable] attribute. (Is there a current best practice on what to use?)
If I read the Json from a file and use Newtonsoft.Json.JsonConvert.DeserializeObject it deserializes correctly with the Json attributes added, but still null in the controller.
It also deserializes in the API properly if I make it public, so it doesn't seem like a problem in the Pricing class itself
After posting I found this Question about making Newtonsoft the default and using MikeBeaton's accepted solution there with Microsoft.AspNetCore.Mvc.NewtonsoftJson package worked so I'll put this as one potential answer for anyone else with this issue. Would still like to know if there is a more correct solution available.
System.Text.Json Serializes Public Properties
As the documentation implies (emphasis mine):
By default, all (read: only) public properties are serialized. You can specify properties to exclude.
I would guess that this was the design chosen because serializing an object is allowing that object to cross barriers of scope and the public scope is the only one that can reliably be assumed.
If you think about it, it makes sense. Lets say, you define a protected property and serialize the object. Then a client picks it up and deserializates that text representation into a public property. What you have designed to be an implementation detail of/to derived types is now accessible outside the scope defined by the modifier.
Apart from simply pointing you to your own answer where Newtonsoft allows this protected property to be serialized, I would suggest you look more intently at your design and why those properties are protected in the first place. It makes sense within the context of your API implementation, but the client can't (shouldn't) be assumed to follow your same inheritance structure (or support inheritance at all). It seems like you might want to define a true DTO to act as the "shape" of your API response and find the right place to transition from your internal types using protected scope to control access and the DTO that can cross the border of the API.

Model bound complex types must not be abstract or value types and must have a parameterless constructor

I have the following problem, I created an application to add game categories and the games themselves to the database. I created a relationship and unfortunately when I add to the database I get an error.
Model bound complex types must not be abstract or value types and must
have a parameterless constructor.
Game Category Model :
using System.Collections.Generic;
namespace relationship.Models
{
public class GameCategory
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Game> Game { get; set; }
}
}
Game Model :
namespace relationship.Models
{
public class Game
{
public int GameId { get; set; }
public string Name { get; set; }
public GameCategory Category { get; set; }
public int CategoryId { get; set; }
}
}
ViewModel :
using relationship.Models;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace relationship.ViewModels
{
public class AddGameViewModel
{
[Required]
public string GameName { get; set; }
public int CategoryID { get; set; }
public List<SelectListItem> Categories { get; set; }
public AddGameViewModel(IEnumerable<GameCategory> categories)
{
Categories = new List<SelectListItem>();
foreach (var catData in categories)
{
Categories.Add(new SelectListItem { Text = catData.Name.ToString(), Value = catData.Id.ToString() });
}
return;
}
}
}
GameRepository :
using System.Collections.Generic;
using System.Linq;
namespace relationship.Models
{
public class GameRepository : IGameRepository
{
private readonly AppDbContext appDbContext;
public GameRepository(AppDbContext dbContext)
{
appDbContext = dbContext;
}
public void AddGame(Game game)
{
appDbContext.Games.Add(game);
appDbContext.SaveChanges();
}
public IEnumerable<Game> Games()
{
return appDbContext.Games.ToList();
}
}
}
and last is GameController :
using Microsoft.AspNetCore.Mvc;
using relationship.Models;
using relationship.ViewModels;
namespace relationship.Controllers
{
public class GameController : Controller
{
private readonly IGameRepository gameRepository;
private readonly ICategoryRepository categoryRepository;
public GameController(IGameRepository gameRepo, ICategoryRepository catRepo)
{
gameRepository = gameRepo;
categoryRepository = catRepo;
}
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult Add()
{
var addGameViewModel = new AddGameViewModel(categoryRepository.GameCategory());
return View(addGameViewModel);
}
[HttpPost]
public IActionResult Add(AddGameViewModel addGameViewModel)
{
if (ModelState.IsValid)
{
GameCategory gameCategory = categoryRepository.GetDetails(addGameViewModel.CategoryID);
if(gameCategory == null)
{
return NotFound();
}
Game game = new Game
{
Name = addGameViewModel.GameName,
Category = gameCategory
};
gameRepository.AddGame(game);
return RedirectToAction("Index");
}
return View(addGameViewModel);
}
}
}
I don't have any idea what is wrong.
My error screen :
Could not create an instance of relationship.ViewModels.AddGameViewModel. Model bound complex types must not be abstract or value types and must have a parameterless constructor.
Let's try and break this error down.
Could not create an instance of relationship.ViewModels.AddGameViewModel.
Pretty self-explanatory: the model-binding components are trying to create an instance of your type, but failed.
Model bound complex types
"Model bound" refers to that they're being bound by the ASP.NET pipeline. "complex types" are basically any types which aren't "basic" like string or int. Your model classes are complex types.
must not be abstract
The model-binding system is going to want to be able to create instances of the class, so it cannot be abstract; it must be concrete. All of the types you've show are concrete so this isn't the problem.
or value types
You can't use struct types with model-binding; it's just one of its limitations. Fortunately your types are all classes, so you can ignore this.
and must have a parameterless constructor.
ASP.NET doesn't know how to supply parameters to model constructors. It can only do the equivalent of new T(), so all your model types must define a constructor which has zero parameters. This is the reason you're seeing the error; your AddGameViewModel class only defines this constructor:
public AddGameViewModel(IEnumerable<GameCategory> categories)
One of the C# language features is that when you don't specify a constructor manually, it adds a default one for you. When you define a constructor in your code, this default constructor is not added.
In all of your other models, you aren't defining any constructors so the compiler is adding the default one for you. In the case of AddGameViewModel you have added a constructor, so to fix the problem you must also add the default constructor:
public AddGameViewModel()
{
}
you need add [FromBody] to the parameter so that asp.net core know how to bind the model.
[HttpPost]
public IActionResult Add([FromBody] AddGameViewModel addGameViewModel)
As of this writing, I experienced this issue in an Asp.NET Core 2.2 Controller where the type was injected on one of the methods. Moving the type to the Controller's constructor worked around it. Since this wasn't really acceptable we eventually refactored the offending class out of the Controller and into the processing layer where singletons are already used extensively. Adding one more at that point cleared up our problem.
Note this is the OOB IoC container that is built-in to Asp.Net Core. Other IoC providers may be better able to handle injecting properties on methods.
Lamar might be an alternative.
Using a model binder might also have worked since the binder could probably use the singleton and/or support constructor injection more cleanly.
In my case, I was naively binding a complex object (a complex object without a no-args constructor):
Edit.cshtml.cs:
namespace MyNamespace.Pages.CSDA
{
public class EditModel : PageModel
{
...
[BindProperty]
public MyComplexClass WorkflowItem { get; set; }
...
I got this runtime error when I clicked "Save":
System.InvalidOperationException: Could not create an instance of type 'MyNamespace.MyComplexClass'.
Model bound complex types must not be abstract or value types and must have a parameterless constructor.
Alternatively, set the 'WorkflowItem' property to a
non-null value in the 'MyNamespace.EditModel' constructor. at
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext
bindingContext) at
Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.BindModelCoreAsync(ModelBindingContext
bindingContext, Int32 propertyData)
I needed the object (it had information I wanted to display to the user), but I didn't need to "update" it (at least not in this edit menu).
SOLUTION:
Simply removing [BindProperty] eliminated the error.
I had this same error. Constructor was internal, I returned it back as public, and the model was passed normally.
Adding [ApiController] at the top of my Controller's class fixed it for me:
[Route("api/[controller]")]
[ApiController]
public class ProductController : Controller
{
...
}

Why is my response being serialized without navigation properties?

Today I ran into a small problem with my code. I have an HttpGet method similar to the following:
[HttpGet]
public IEnumerable<SomeEntity> Get()
{
return db.SomeEntity.ToList();
}
where SomeEntity could be represented as
public class SomeEntity
{
#region DatabaseColumns
[Key]
public int SomeEntityID { get; set; }
public string SomeEntityName { get; set; }
#endregion
#region Navigation Properties
public virtual ICollection<SomeChildEntity> SomeChildEntity { get; set; }
#endregion
}
I noticed that return db.SomeEntity.ToList(); only returned the top level members of the object (not the navigation properties).
This made sense to me considering I was not calling Include. Out of curiosity, I attempted the following:
[HttpGet]
public IEnumerable<SomeEntity> Get()
{
var enumeratedEntity = db.SomeEntity.ToList();
return enumeratedEntity;
}
To my surprise, it returned the entire entity along with its navigation properties.
I also noticed that an HttpGet along these lines also returned the entire object
[HttpGet]
public SomeEntity Get(int id)
{
return db.SomeEntity.Find(id);
}
Can someone please explain, or point me to resources that will explain, why these methods return the entire object without using the Includes method while the first one doesn't?
I'm not 100% on this, so feel free to test and then up or downvote me. When you return an IEnumerable<SomeEntity> you don't define a concrete type. You're returning some generic IEnumerable which the HTTP pipeline then strips the virtual properties out of.
When you call var enumeratedEntity = db.SomeEntity.ToList(); you create a List. That concrete object then gets the virtual properties immediately instantiated. The whole concrete object is then sent down the pipeline including the virtual properties.
You could test this by changing var enumeratedEntity = db.SomeEntity.ToList(); to IEnumerable<SomeEntity> enumeratedEntity = db.SomeEntity.ToList(); and List<SomeEntity> enumeratedEntity = db.SomeEntity.ToList();. You'd then expect to see the 2 behaviors you currently see based on which generic container you use.

Specify a unique identifier attribute for an object across webapi Models

In a POST call to a WebApi I am trying to return a Created(newobject) thing. But there is no signature for Created in ApiController that can only take the object and do the rest.
It works fine if I return something like:
return Created(newobject.blahid.ToString(), newobject);
or if I do a
return CreatedAtRoute("DefaultApi", new { controller = ControllerContext.ControllerDescriptor.ControllerName, id = newobject.blahid.ToString()}, newobject);
I want to simplify this to:
return Created(newobject);
I would need to implement a method in a BaseController
public class BaseController : ApiController
{
protected new CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var id = GetId(content);//need help here
return base.Created(id, content);
}
}
I don't want to worry about the Unique Identifier for an object being called differently in different models e.g. myobjguid, someblahguid etc. I would just want to find it out and mark it as "id".
say if my model is
public class Model_A
{
public List<Model_A> ChildModels { get; set; }
[LookForThisAttribute]//I want something like this
public Guid Model_AGuid { set; get; }
public Guid ? ParentGuid { set; get; }
public List<SomeOtherObject> OtherObjects { set; get; }
}
Is there an attribute([LookForThisAttribute]) or something I can set on all my models to specify that this is the guy to be assumed as THE unique identifier if I ever look for it.
Just like the [Key] attribute in Entity Framework. No matter what you call it, Entity Framework know its going to be the primary key.
So the GetId(T content) method can take the object and return the value of the property that has a [LookForThisAttribute] set?
I ended up writing my own Attribute and then looking up for it in the BaseController.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class UniqueIdAttribute: Attribute
{
}
And in the BaseController Created method:
protected CreatedNegotiatedContentResult<T> Created<T>(T content)
{
var props =typeof(T).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UniqueIdAttribute)));
if (props.Count() == 0)
{
//log this
return base.Created(Request.RequestUri.ToString(), content);
}
var id = props.FirstOrDefault().GetValue(content).ToString();
return base.Created(new Uri(Request.RequestUri + id), content);
}
Mark Gravell's post here helped me with getting the value of the property that has my custom attribute:
How to get a list of properties with a given attribute?
Along with a corresponding unit test for the controllers works fine for me.
Now I can just call Created(anyobject); from all ApiControllers without bothering about the different names people put for their IDs as long as they decorate it with my custom attribute.

FluentValidation Validator using arguments

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

Categories

Resources