The question title might be a bit difficult to understand, but I'll try to explain what I am asking here.
I have this sort of controller method in an ASP.NET MVC controller:
public IActionResult DoStuff([FromBody] RequestObject reqObj)
{
// do stuff
ReplyObject repObj = ProcessObject(reqObj);
// do more stuff
return Json(repObj);
}
where RequestObject looks like this:
public class RequestObject
{
public string Field { get; set; }
}
I am calling it from an Angular service like this:
this.http.post("/StuffController/DoStuff", { field: "field value goes here" })
Now this works OK. But now I need DoStuff() to handle different subclasses of RequestObj, for example:
public class RequestObjectA : RequestObject
{
public string FieldA { get; set; }
}
public class RequestObjectB : RequestObject
{
public string FieldB { get; set; }
}
and have DoStuff() somehow figure out whether it was given a RequestObjectA or a RequestObjectB.
Is this kind of thing possible? I have a feeling it is not as the JSON request transmits only the contents of the object, not its class, but I'd like to know for sure.
The short answer is that it is not possible.
The long answer is: your contract for the endpoint parameter is RequestObject class so nor additional parameter members will be respected during deserialization as nothing is known about them.
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;
});
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.
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 strongly-typed view which has a DropDownListFor attribute on it.
Each item in the dropdown list is represented by a GUID.
What I'm after is a way to validate if a user selects an item from the dropdown list. At present i don't see anyway of doing this using Data Annotations.
Is there anyway of achieving this using Data Annotations so client and server side validation would work.
I'm guessing i need to make a custom method to do this but was wondering if anything already existed.
Actually, you can't use Required attribute with GUIDs (without the method I mention below) because they inherit from struct, and as such their default value is actually an instance of Guid.Empty, which will satisfy the requirements of the Required attribute. Now that being said, it is possible to get what you want you just need to make your property nullable, take this for example...
public class Person
{
[Required] //Only works because the Guid is nullable
public Guid? PersonId { get; set;}
public string FirstName { get; set;}
public string LastName { get; set;}
}
By marking the GUID nullable (using the ?, or Nullable if you prefer the long way) you let it stay as null when binding against what the browser sent. In your case, just make sure the value of the default option of the dropdown uses an empty string as it's value.
EDIT: The only caveat to this method is you end up having to use something like Person.GetValueOfDefault() everywhere and potentially testing for Guid.Empty. I got tired of doing this and ended up creating my own validation attribute to help simplify validating Guids (and any other types that have default values I want to treat as invalid such as int, DateTime, etc). However I don't have client side validation to go along with this yet, so validation only happens on the server. This can be combined with [Required] (designed to not duplicate functionality of [Required]) if you're ok with using nullable types. This would mean you still have to use GetValueOrDefault(), but at least then you don't have to test for Guid.Empty anymore. The Gist link has some XMLDocs with examples, I left them out here for brevity. I'm currently using it with ASP.NET Core.
EDIT: Updated to fix a bug with Nullable<>, and a bug with treating null as invalid. Added supporting classes to handle client side validation. See Gist for full code.
Gist: RequireNonDefaultAttribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public class RequireNonDefaultAttribute : ValidationAttribute
{
public RequireNonDefaultAttribute()
: base("The {0} field requires a non-default value.")
{
}
public override bool IsValid(object value)
{
if (value is null)
return true; //You can flip this if you want. I wanted leave the responsability of null to RequiredAttribute
var type = value.GetType();
return !Equals(value, Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type));
}
}
Edited Answer
Upon re-reading your question, it sounds like you just want to know if a value is selected. If that's the case then just apply the RequiredAttribute to the Guid property and make it nullable on the model
public class GuidModel
{
[Required]
public Guid? Guid { get; set; }
public IEnumerable<Guid> Guids { get; set; }
}
then in the strongly typed View (with #model GuidModel)
#Html.ValidationMessageFor(m => m.Guid)
#Html.DropDownListFor(
m => m.Guid,
Model.Guids.Select(g => new SelectListItem {Text = g.ToString(), Value = g.ToString()}),
"-- Select Guid --")
Add the client validation JavaScript script references for client-side validation.
The controller looks like
public class GuidsController : Controller
{
public GuidRepository GuidRepo { get; private set; }
public GuidsController(GuidRepository guidRepo)
{
GuidRepo = guidRepo;
}
[HttpGet]
public ActionResult Edit(int id)
{
var guid = GuidRepo.GetForId(id);
var guids - GuidRepo.All();
return View(new GuidModel { Guid = guid, Guids = guids });
}
[HttpPost]
public ActionResult Edit(GuidModel model)
{
if (!ModelState.IsValid)
{
model.Guids = GuidRepo.All();
return View(model);
}
/* update db */
return RedirectToAction("Edit");
}
}
This will ensure that the Guid property is required for a model-bound GuidModel.
Original Answer
I don't believe that there is a ready made Data Annotation Validation attribute that is capable of doing this. I wrote a blog post about one way to achieve this; the post is using an IoC container but you could take the hard coded dependency if you're wanting to get something working.
Something like
public class ValidGuidAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "'{0}' does not contain a valid guid";
public ValidGuidAttribute() : base(DefaultErrorMessage)
{
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var input = Convert.ToString(value, CultureInfo.CurrentCulture);
// let the Required attribute take care of this validation
if (string.IsNullOrWhiteSpace(input))
{
return null;
}
// get all of your guids (assume a repo is being used)
var guids = new GuidRepository().AllGuids();
Guid guid;
if (!Guid.TryParse(input, out guid))
{
// not a validstring representation of a guid
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
// is the passed guid one we know about?
return guids.Any(g => g == guid) ?
new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : null;
}
}
and then on the model you send into the controller action
public class GuidModel
{
[ValidGuid]
public Guid guid { get; set; }
}
This gives you server side validation. You could write client side validation to do this as well, perhaps using RemoteAttribute but I don't see a lot of value in this case as the only people that are going to see this client side validation are people that are messing with values in the DOM; it would be of no benefit to your normal user.
I know this is an old question now, but if anyone else is interested I managed to get around this by creating an [IsNotEmpty] annotation (making the Guid nullable wasn't an option in my case).
This uses reflection to work out whether there's an implementation of Empty on the property, and if so compares it.
public class IsNotEmptyAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null) return false;
var valueType = value.GetType();
var emptyField = valueType.GetField("Empty");
if (emptyField == null) return true;
var emptyValue = emptyField.GetValue(null);
return !value.Equals(emptyValue);
}
}
Regex actually does work (if you use the right one!)
[Required]
[RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*$", ErrorMessage = "Cannot use default Guid")]
public Guid Id { get; set; }
Non Empty Guid Validator
prevents 00000000-0000-0000-0000-000000000000
Attribute:
using System.ComponentModel.DataAnnotations;
[AttributeUsage(AttributeTargets.Property)]
internal class NonEmptyGuidAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if ((value is Guid) && Guid.Empty == (Guid)value)
{
return new ValidationResult("Guid cannot be empty.");
}
return null;
}
}
Model:
using System.ComponentModel.DataAnnotations;
public class Material
{
[Required]
[NonEmptyGuid]
public Guid Guid { get; set; }
}
If the custom validation doesn't require a high reuse in your system (i.e. without the need for a custom validation attribute), there's another way to add custom validation to a ViewModel / Posted data model, viz by using IValidatableObject.
Each error can be bound to one or more model properties, so this approach still works with e.g. Unobtrusive validation in MVC Razor.
Here's how to check a Guid for default (C# 7.1):
public class MyModel : IValidatableObject // Implement IValidatableObject
{
[Required]
public string Name {get; set;}
public Guid SomeGuid {get; set;}
... other properties here
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (SomeGuid == default)
{
yield return new ValidationResult(
"SomeGuid must be provided",
new[] { nameof(SomeGuid) });
}
}
}
More on IValidatableObject here
You can validate the Guid if it contains default values - "00000000-0000-0000-0000-000000000000".
if (model.Id == Guid.Empty)
{
// TODO: handle the error or do something else
}
You can create a custom validator for that.
using System;
using System.ComponentModel.DataAnnotations;
namespace {{Your_App_Name}}.Pages
{
public class NotEmptyGuidAttribute: ValidationAttribute
{
protected override ValidationResult IsValid(object guidValue, ValidationContext validationContext)
{
var emptyGuid = new Guid();
var guid = new Guid(guidValue.ToString());
if (guid != emptyGuid){
return null;
}
return new ValidationResult(ErrorMessage, new[] {validationContext.MemberName});
}
}
}
You can use it like this
[EmptyGuidValidator(ErrorMessage = "Role is required.")]
public Guid MyGuid{ get; set; }
This worked for me.