Custom validation - Required only when method is put - c#

I have two API endpoints, post and put:
[HttpPost]
[Route("projects")]
public IHttpActionResult Create([FromBody] ProjectDTO projectDto)
{
if (ModelState.IsValid)
{
var project = MappingConfig.Map<ProjectDTO, Project>(projectDto);
_projectService.Create(project);
return Ok("Project successfully created.");
}
else
{
return BadRequest(ModelState);
}
}
[HttpPut]
[Route("projects")]
public IHttpActionResult Edit([FromBody] ProjectDTO projectDto)
{
if (ModelState.IsValid)
{
var project = _projectService.GetById(projectDto.ProjectId);
if (project == null)
return NotFound();
project = Mapper.Map(projectDto, project);
_projectService.Update(project);
return Ok("Project successfully edited.");
}
else
{
return BadRequest(ModelState);
}
}
DTO looks like this:
public class ProjectDTO
{
public int ProjectId { get; set; }
[Required(ErrorMessage = "Name field is required.")]
public string Name { get; set; }
[Required(ErrorMessage = "IsInternal field is required.")]
public bool IsInternal { get; set; }
}
I'm trying to validate field ProjectId. ProjectId field should be required only in HttpPut method when I'm editing my entity.
Is it possible to make custom validation RequiredIfPut or something like that where that field will be required only when editing, but not when creating?

Here is what you can do using custom validation attribute:
public class RequiredWhenPutAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (System.Web.HttpContext.Current.Request.HttpMethod == "PUT")
{
var obj = (ProjectDTO)validationContext.ObjectInstance;
if (obj.ProjectId == null)
{
return new ValidationResult("Project Id is Required");
}
}
else
{
return ValidationResult.Success;
}
}
}
public class ProjectDTO
{
[RequiredWhenPut]
public int? ProjectId { get; set; }
}
Update:
In response to your comment, in order to make the solution more general, you can add a ParentDto class from which other classes are inherited and the shared property needs to be in the ParentDto class, as the following:
public class RequiredWhenPutAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (HttpContext.Current.Request.HttpMethod == "PUT")
{
var obj = (ParentDto)validationContext.ObjectInstance;
if (obj.Id == null)
{
return new ValidationResult(ErrorMessage);
}
}
else
{
return ValidationResult.Success;
}
}
}
public class ParentDto
{
[RequiredWhenPut(ErrorMessage = "Id is required")]
public int? Id { get; set; }
}
public class UserDTO : ParentDto
{
// properties
}
public class ProjectTypeDTO : ParentDto
{
// properties
}
public class ProjectDTO : ParentDto
{
// properties
}

That's one of the reasons, why I use different classes for both case (e.g. ProjectUpdateRequestDto and ProjectCreateRequestDto). Maybe both can be derived from a common base class, but even if not it makes it a lot easier to distinguish between both scenarios.
Also security could be a problem, cause if you use the same class it could be possible that the create requests already contains an id and if your create method simply maps the DTO to an database entity you could overwrite existing data. This means you have to be careful and think about such scenarios. If your create DTO class doesn't have that property it can't be set from the mapper and can't be malused.

Related

Require not null collection elements in when validating model in aspnet core

I have example mode class:
public class Data
{
[Required]
[MinLength(1)]
public List<Foo> Foos{ get; set; }
}
which is passed to controller:
[ApiController]
public class FooController : ControllerBase
{
public async Task<IActionResult> Post([FromBody] Data data)
{
// Do stuff
}
}
I have nullable annotations enabled in my project configuration.
How can I force model validator to reject request with null foos array? For example following request is accepted by controller:
{
foos: [null]
}
I would like such request to return bad request status code.
When using Data Annotations the child classes are not validated. I recommend Fluent Validation for the use case presented.
The use is simple, just define a rule
public class DataValidator : AbstractValidator<Data>
{
public DataValidator()
{
// RuleFor(r => r.Foos).NotEmpty(); // <-- not needed as Nullable Annotations are enabled
RuleForEach(r => r.Foos).NotNull();
}
}
And register FluentValidation in configure services method
services.AddControllers()
.AddFluentValidation(configuration => configuration.RegisterValidatorsFromAssemblyContaining<Startup>());
You could custom ValidationAttribute to custom the rule:
Custom ValidationAttribute:
public class ValidArray : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
var model = (Data)validationContext.ObjectInstance;
if(Extension.IsNullOrEmpty<Foo>(model.Foos))
{
return new ValidationResult
("Array cannot be null");
}
else
{
return ValidationResult.Success;
}
}
}
Custom IsNullOrEmpty:
public static class Extension
{
public static bool IsNullOrEmpty<T>(List<T> array) where T : class
{
if (array == null || array.Count == 0)
return true;
else
return array.All(item => item == null);
}
}
Model:
public class Data
{
//[Required]
[MinLength(1)]
[ValidArray]
public List<Foo> Foos { get; set; }
}
public class Foo
{
public int Id { get; set; }
public int Count { get; set; }
}
Result:

Is there a way to validate a model which is created inside web api controller?

I have a controller where my PUT method uses multipart/form-data as content type and so I am getting the JSON and the mapped class thereby inside the controller.
Is there a way I could validate this model with respect to the annotations I have written in the model class while inside the controller?
public class AbcController : ApiController
{
public HttpResponseMessage Put()
{
var fileForm = HttpContext.Current.Request.Form;
var fileKey = HttpContext.Current.Request.Form.Keys[0];
MyModel model = new MyModel();
string[] jsonformat = fileForm.GetValues(fileKey);
model = JsonConvert.DeserializeObject<MyModel>(jsonformat[0]);
}
}
I need to validate "model" inside the controller.
FYI, I have added required annotations to MyModel().
Manual model validation:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
class ModelValidator
{
public static IEnumerable<ValidationResult> Validate<T>(T model) where T : class, new()
{
model = model ?? new T();
var validationContext = new ValidationContext(model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(model, validationContext, validationResults, true);
return validationResults;
}
}
Suppose you have defined models in Product class like :
namespace MyApi.Models
{
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public decimal Price { get; set; }
}
}
and then inside controller just write:
public class ProductsController : ApiController
{
public HttpResponseMessage Post(Product product)
{
if (ModelState.IsValid)
{
return new HttpResponseMessage(HttpStatusCode.OK);
}
}
}
You can also use Fluent Validation to do this. https://docs.fluentvalidation.net/en/latest/aspnet.html
Your Model Class:
public class Customer
{
public int Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public decimal Discount { get; set; }
public string Address { get; set; }
}
You would define a set of validation rules for this class by inheriting from AbstractValidator<Customer>:
using FluentValidation;
public class CustomerValidator : AbstractValidator<Customer>
{
RuleFor(customer => customer.Surname).NotNull();
. . . etc;
}
To run the validator, instantiate the validator object and call the Validate method, passing in the object to validate.
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult result = validator.Validate(customer);
The Validate method returns a ValidationResult object. This contains two properties:
IsValid - a boolean that says whether the validation succeeded.
Errors - a collection of ValidationFailure objects containing details about any validation failures.
The following code would write any validation failures to the console from your controller or even from your service or repository:
using FluentValidation.Results;
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
if(! results.IsValid)
{
foreach(var failure in results.Errors)
{
Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage);
}
}
You can also inject the validator inside Program.cs , read here : https://docs.fluentvalidation.net/en/latest/di.html

Custom validation attribute does not work when editing a record

I have an entity called Doctor, in the create doctor form I added custom validation logic as follows:
public class UniqueDoctorNameAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string name = value.ToString();
HospitalEntities db = new HospitalEntities();
int count = db.Doctors.Where(d => d.DoctorName == name).ToList().Count;
if (count != 0)
return new ValidationResult("A doctor already exists with that name");
return ValidationResult.Success;
}
}
and then in the Doctor model class:
public class Doctor
{
[Required]
[Display(Name = "Name")]
[UniqueDoctorName]
public string DoctorName { get; set; }
}
and it works as expected when creating doctors but it also shows up in the edit form of Doctor, I know one way to remedy this is to use a viewmodel in the create form and do the validation there but that would require alot of debugging on my part as I've written alot of code depending on it being passed a Doctor model, so how do I fix that ?
You can update your custom validation attribute to accept your Id property so that you can use that when you do your check against your db.
public class UniqueDoctorNameAttribute : ValidationAttribute
{
private readonly string _IdPropertyName;
public UniqueDoctorNameAttribute(string IdPropertyName)
{
_IdPropertyName = IdPropertyName;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
string name = value.ToString();
var property = validationContext.ObjectType.GetProperty(_IdPropertyName);
if (property != null)
{
var idValue = property.GetValue(validationContext.ObjectInstance, null);
var db = new HospitalEntities();
var exists = db.Doctors.Any(d => d.DoctorName == name && d.Id!=idValue);
if (exists )
return new ValidationResult("A doctor already exists with that name");
return ValidationResult.Success;
}
return ValidationResult.Success;
}
}
When user creates a new record, the value of DoctorId will be 0 and when editing it will be a valid doctorId value.
Now in your view model,
public class Doctor
{
public int DoctorId { set; get; }
[Required]
[Display(Name = "Name")]
[UniqueDoctorName(nameof(DoctorId))]
public string DoctorName { get; set; }
}
nameof will return a string "DoctorId"(name of that property). If your c# version does not support this keyword, simply use the string "DoctorId" as the constructor parameter.

Asp.net Web Api nested model validation

I'm running in to a bit of a problem in asp.net web api's model binding and validation (via data annotations).
It seems like if i have a model with property such as
Dictionary<string, childObject> obj { get; set; }
the childObject's validations don't seem to trigger. The data is bound from json with Json.Net serializer.
Is there some workaround or fix to this? Or have I misunderstood something else related to this?
I can't help but wonder why this doesn't result in errors:
public class Child
{
[Required]
[StringLength(10)]
public string name;
[Required]
[StringLength(10)]
public string desc;
}
//elsewhere
Child foo = new Child();
foo.name = "hellowrodlasdasdaosdkasodasasdasdasd";
List<ValidationResult> results = new List<ValidationResult>();
Validator.TryValidateObject(foo, new ValidationContext(foo), results, true);
// results.length == 0 here.
Oh god. I had forgotten to declare properties instead of fields.
There are 2 ways you can setup validation of the Dictionary Values. If you don't care about getting all the errors but just the first one encountered you can use a custom validation attribute.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
[ValidateDictionary]
public Dictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidateDictionaryAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!IsDictionary(value)) return ValidationResult.Success;
var results = new List<ValidationResult>();
var values = (IEnumerable)value.GetType().GetProperty("Values").GetValue(value, null);
values.OfType<object>().ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
Validator.TryValidateObject(value, new ValidationContext(value, null, validationContext.Items), results);
return results.FirstOrDefault() ?? ValidationResult.Success;
}
protected bool IsDictionary(object value)
{
if (value == null) return false;
var valueType = value.GetType();
return valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof (Dictionary<,>);
}
}
The other way is to create your own Dictionary as an IValidatableObject and do the validation in that. This solution gives you the ability to return all the errors.
public class Foo
{
[Required]
public string RequiredProperty { get; set; }
public ValidatableDictionary<string, Bar> BarInstance { get; set; }
}
public class Bar
{
[Required]
public string BarRequiredProperty { get; set; }
}
public class ValidatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IValidatableObject
{
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
Values.ToList().ForEach(item => Validator.TryValidateObject(item, new ValidationContext(item, null, validationContext.Items), results));
return results;
}
}
Validation always passes on fields because attributes can only be applied to properties. You need to change the fields name and desc into properties using auto implemented getter and setters.
These should then look something like
public string name { get; set; }

DataAnnotations "NotRequired" attribute

I've a model kind of complicated.
I have my UserViewModel which has several properties and two of them are HomePhone and WorkPhone. Both of type PhoneViewModel. In PhoneViewModel I have CountryCode, AreaCode and Number all strings. I want to make the CountryCode optional but AreaCode and Number mandatory.
This works great. My problem is that in the UserViewModel WorkPhone is mandatory, and HomePhone is not.
Is there anyway I can dissable Require attributs in PhoneViewModel by setting any attributes in HomeWork property?
I've tried this:
[ValidateInput(false)]
but it is only for classes and methods.
Code:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public PhoneViewModel HomePhone { get; set; }
[Required]
public PhoneViewModel WorkPhone { get; set; }
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[Required]
public string Number { get; set; }
}
[UPDATED on 5/24/2012 to make the idea more clear]
I'm not sure this is the right approach but I think you can extend the concept and can create a more generic / reusable approach.
In ASP.NET MVC the validation happens at the binding stage. When you are posting a form to the server the DefaultModelBinder is the one that creates model instances from the request information and add the validation errors to the ModelStateDictionary.
In your case, as long as the binding happens with the HomePhone the validations will fire up and I think we can't do much about this by creating custom validation attributes or similar kind.
All I'm thinking is not to create model instance at all for HomePhone property when there are no values available in the form (the areacode, countrycode and number or empty), when we control the binding we control the validation, for that, we have to create a custom model binder.
In the custom model binder we are checking if the property is HomePhone and if the form contains any values for it's properties and if not we don't bind the property and the validations won't happen for HomePhone. Simply, the value of HomePhone will be null in the UserViewModel.
public class CustomModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name == "HomePhone")
{
var form = controllerContext.HttpContext.Request.Form;
var countryCode = form["HomePhone.CountryCode"];
var areaCode = form["HomePhone.AreaCode"];
var number = form["HomePhone.Number"];
if (string.IsNullOrEmpty(countryCode) && string.IsNullOrEmpty(areaCode) && string.IsNullOrEmpty(number))
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
Finally you have to register the custom model binder in global.asax.cs.
ModelBinders.Binders.Add(typeof(UserViewModel), new CustomModelBinder());
So now of you have an action that takes UserViewModel as parameter,
[HttpPost]
public Action Post(UserViewModel userViewModel)
{
}
Our custom model binder come into play and of form doesn't post any values for the areacode, countrycode and number for HomePhone, there won't be any validation errors and the userViewModel.HomePhone is null. If the form posts atleast any one of the value for those properties then the validation will happen for HomePhone as expected.
I've been using this amazing nuget that does dynamic annotations: ExpressiveAnnotations
It allows you to do things that weren't possible before such as
[AssertThat("ReturnDate >= Today()")]
public DateTime? ReturnDate { get; set; }
or even
public bool GoAbroad { get; set; }
[RequiredIf("GoAbroad == true")]
public string PassportNumber { get; set; }
Update: Compile annotations in a unit test to ensure no errors exist
As mentioned by #diego this might be intimidating to write code in a string, but the following is what I use to Unit Test all validations looking for compilation errors.
namespace UnitTest
{
public static class ExpressiveAnnotationTestHelpers
{
public static IEnumerable<ExpressiveAttribute> CompileExpressiveAttributes(this Type type)
{
var properties = type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(ExpressiveAttribute)));
var attributes = new List<ExpressiveAttribute>();
foreach (var prop in properties)
{
var attribs = prop.GetCustomAttributes<ExpressiveAttribute>().ToList();
attribs.ForEach(x => x.Compile(prop.DeclaringType));
attributes.AddRange(attribs);
}
return attributes;
}
}
[TestClass]
public class ExpressiveAnnotationTests
{
[TestMethod]
public void CompileAnnotationsTest()
{
// ... or for all assemblies within current domain:
var compiled = Assembly.Load("NamespaceOfEntitiesWithExpressiveAnnotations").GetTypes()
.SelectMany(t => t.CompileExpressiveAttributes()).ToList();
Console.WriteLine($"Total entities using Expressive Annotations: {compiled.Count}");
foreach (var compileItem in compiled)
{
Console.WriteLine($"Expression: {compileItem.Expression}");
}
Assert.IsTrue(compiled.Count > 0);
}
}
}
I wouldn't go with the modelBinder; I'd use a custom ValidationAttribute:
public class UserViewModel
{
[Required]
public string Name { get; set; }
public HomePhoneViewModel HomePhone { get; set; }
public WorkPhoneViewModel WorkPhone { get; set; }
}
public class HomePhoneViewModel : PhoneViewModel
{
}
public class WorkPhoneViewModel : PhoneViewModel
{
}
public class PhoneViewModel
{
public string CountryCode { get; set; }
public string AreaCode { get; set; }
[CustomRequiredPhone]
public string Number { get; set; }
}
And then:
[AttributeUsage(AttributeTargets.Property]
public class CustomRequiredPhone : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = null;
// Check if Model is WorkphoneViewModel, if so, activate validation
if (validationContext.ObjectInstance.GetType() == typeof(WorkPhoneViewModel)
&& string.IsNullOrWhiteSpace((string)value) == true)
{
this.ErrorMessage = "Phone is required";
validationResult = new ValidationResult(this.ErrorMessage);
}
else
{
validationResult = ValidationResult.Success;
}
return validationResult;
}
}
If it is not clear, I'll provide an explanation but I think it's pretty self-explanatory.
Just some observation: the following code couse a problem if the binding is more than simple filed. I you have a case that in object have nested object it going to skip it and caouse that some filed not been binded in nested object.
Possible solution is
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (!propertyDescriptor.Attributes.OfType<RequiredAttribute>().Any())
{
var form = controllerContext.HttpContext.Request.Form;
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).Count() > 0)
{
if (form.AllKeys.Where(k => k.StartsWith(string.Format(propertyDescriptor.Name, "."))).All(
k => string.IsNullOrWhiteSpace(form[k])))
return;
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
much thanks to Altaf Khatri

Categories