I have a regular Integer (Not nullable) in my model:
[Required]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
In my WebApi action, I accept an object that has that propery.
public IHttpActionResult Create([FromBody] Person person)
{
if (!ModelState.IsValid)
{
return BadRequest("Some error message.");
}
//Do some stuff with person...
}
Now, altough there is a Required attribute on PersonId, when a person is posted to this action, the ModelState.IsValid property is true.
I guess this is because Person is created with default value, which is 0, I want to throw an error if there is no PersonId field in the incoming JSON / query string request.
I can set PersonId to be Nullable, but that doesn't make sense.
Is there any easy way to validate the field exists and the integer is larger than 0 ? (without custom validators for that simple requirement)
Setting the [Required] attribute doesn't do anything on an int, as far as I know. All [Required] does is make sure the value is not null.
You can set [Range(1, Int32.MaxValue)] to make sure that a correct value is added.
If you don't already do this, it might be a good idea to make a different model for your view and make the data annotations on this model. I use view models to make sure I don't pollute my "real" models with stuff that is not relevant to the whole domain. This way your PersonId can be nullable in your view model only, where it makes sense.
BindRequiredAttribute can be used to
Quoting from this nice blog post about [Required] and [BindRequired]
It works the same way as RequiredAttribute, except it mandates that
the value comes from the request – so it not only rejects null values,
but also default (or “unbound”) values.
So this would reject unbound integer values:
[BindRequired]
[Range(0, Int32.MaxValue - 1)]
public int PersonId
{
get;
set;
}
I tend to use int? (nullable int) in this case and then mark those as required. I then use myInt.Value throughout the code and assume it's safe to use because it wouldn't have passed validation otherwise.
and like #andreas said, I do make sure to use "view models" in times like this so I'm not polluting my view model as a business or data layer model.
Actually for missing not nullable integer parameters model validation doesn't work. There is JSON parsing exception which is thrown by Newtonsoft.Json.
You can have a following workaround to parse and include exceptions in model validations.
Create the custom validation attribute as following and register in WebApiConfig.cs.
public class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
// Check if model state is valid
if (actionContext.ModelState.IsValid == false) {
// Return model validations object
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest,
new ValidationResultModel(100001, actionContext.ModelState));
}
}
public class ValidationError {
public string Field { get; }
public string Message { get; }
public ValidationError(string field, string message) {
Field = field != string.Empty ? field : null;
Message = message;
}
}
public class ValidationResultModel {
public int Code { get; set; }
public string Message { get; }
public IDictionary<string, IEnumerable<string>> ModelState { get; private set; }
public ValidationResultModel(int messageCode, ModelStateDictionary modelState) {
Code = messageCode;
Message = "Validation Failed";
ModelState = new Dictionary<string, IEnumerable<string>>();
foreach (var keyModelStatePair in modelState) {
var key = string.Empty;
key = keyModelStatePair.Key;
var errors = keyModelStatePair.Value.Errors;
var errorsToAdd = new List<string>();
if (errors != null && errors.Count > 0) {
foreach (var error in errors) {
string errorMessageToAdd = error.ErrorMessage;
if (string.IsNullOrEmpty(error.ErrorMessage)) {
if (key == "model") {
Match match = Regex.Match(error.Exception.Message, #"'([^']*)");
if (match.Success)
key = key + "." + match.Groups[1].Value;
errorMessageToAdd = error.Exception.Message;
} else {
errorMessageToAdd = error.Exception.Message;
}
}
errorsToAdd.Add(errorMessageToAdd);
}
ModelState.Add(key, errorsToAdd);
}
}
}
}
}
//Register in WebApiConfig.cs
// Model validation
config.Filters.Add(new ValidateModelAttribute());
Related
I have a model which is posted to my backend from a cshtml view. This model is also a representation of a Database-Table.
This code looks for the HTTP POST like the following:
[HttpPost]
public IActionResult CreateAKG(Conversation akg)
{
if (ModelState.IsValid && ValideD3OrD4Values(akg))
{
akg.RDPflichfelderBefuellt = true;
}
else
{
akg.RDPflichfelderBefuellt = false;
}
if (akg.Kommentare == null)
{
akg.Kommentare = new List<Kommentar>();
}
if (akg.AuftragsklaerungsgespraechId == 0)
{
this.MyDatabase.Conversation.Add(akg);
}
else
{
this.MyDatabase.Conversation.Update(akg);
}
this.MyDatabase.SaveChanges();
return RedirectToAction("Index");
}
The class which represents the model is called Conversation. There are some properties which are annotated by Validation Annotations. The annotation should only be used by the Controller / ModelState.IsValid and not for the Database-Table.
Here is the code sample:
public class Conversation
{
public int ConversationId { get; set; }
[Required(ErrorMessage = "This field is required.")]
public DateTime? DatumAKG { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungFertigungskosten { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungQVP { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungLayoutkosten { get; set; }
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungBeschaffung { get; set; }
public bool RDPflichfelderBefuellt { get; set; }
}
The ModelState.IsValid is only used to validate if a boolean is true or false. Other validation is not needed.
My problem now is, that in reason of the data-annotation a string which could normally be NULL in a Database is now in the database-design configured as NOT NULL.
If i try to store a new conversation in the database, a error is throw that some string values could not be null.
What i want to do is:
Validation on Controller
No Validation Annotation which changes the Database-Design
Since properties and requirements are different you should have 2 different objects
// this is you database object
public class Conversation {
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
public string KontierungQVP { get; set; }
}
// this your Data Transfer Object
public class ConversationDTO {
[MaxLength(256, ErrorMessage = "This field cannot be more than 256 characters")]
[Required]
public string KontierungQVP { get; set; }
}
Your EF models should always represent the database. If you want to add extra validations or conditions you should do it with another object and simply transfer between those objects. Having 2 different types of objects gives you more modularity.
EDIT:
There is a way to do what you ask but it's not a recommended approach and it might cause issues. You want to use 2 contexts and configure the required properties using FluentAPI and not data annotations.
// Call this method in your context.
protected override void OnModelCreating(your_builder){
modelBuilder.Entity<Conversation>()
.Property(p => p.KontierungQVP)
.IsRequired();
}
Generally you want to have 2 different contexts. One where you will initialize your database. And another one where you will have your required attributes defined by the FluentAPI (not data annotations).
So to recap one DbContext for your DB creation and one other for your operations. Of course this will lead to disparencies and it is easy to forget a validation on the database and so on.
I have created a very simple OData v4 controller. The controller basically contains Entity Framework-backed CRUD methods for the following Pet entity:
public class Pet
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int Age { get; set; }
}
An important thing here is that Pet.Age is the non-nullable required property.
Here is the controller itself (only Post method is shown):
public class PetController : ODataController
{
private DatabaseContext db = new DatabaseContext();
// POST: odata/Pet
public IHttpActionResult Post(Pet pet)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Pet.Add(pet);
db.SaveChanges();
return Created(pet);
}
// Other controller methods go here...
}
And this is my WebApiConfig controller configuration:
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Pet>("Pet");
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
Now if I want to create a new Pet in my database, I issue a POST request like this:
POST http://localhost:8080/odata/Pet
Content-type: application/json
{ Name: "Cat", Age: 5 }
However, I can simply omit the Age property in JSON request payload, so JSON deserializer will use a default value of 0, while I want a 400 Bad Request status to be returned instead. This problem is called under-posting.
It can be easily solved when using regular WebApi controllers (the solution is described here). You just create a PetViewModel and make your controller to accept a PetViewModel instead of an actual Pet entity:
public class PetViewModel
{
// Make the property nullable and set the Required attribute
// to distinguish between "zero" and "not set"
[Required]
public int? Age { get; set; }
// Other properties go here...
}
Then in your controller you just convert PetViewModel to Pet entity and save it to the database as usual.
Unfortunately, this approach does not work with OData controllers: if I change my Post method to accept PetViewModel instead of Pet, I receive the following error:
System.Net.Http.UnsupportedMediaTypeException: No MediaTypeFormatter is available to read an object of type 'PetViewModel' from content with media type 'application/json'.
at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable'1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
So, is there any way to prevent under-posting when using OData controllers?
After some investigation I have solved this issue. Not sure if it is an "official" or preferred way of solving underposting problem in OData, but at least it works fine for me. So, for the lack of the official information, here is my recipe:
First, create a corresponding validation ViewModel for your OData entity:
public class PetViewModel
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
// Make the property nullable and set the Required attribute
// to distinguish between "zero" and "not set"
[Required]
public new int? Age { get; set; }
}
Then, add your own ODataUnderpostingValidationAttribute. My implementation looks like this:
public class ODataUnderpostingValidationAttribute: ActionFilterAttribute
{
public ODataUnderpostingValidationAttribute(Type viewModelType)
{
ViewModelType = viewModelType;
}
public Type ViewModelType { get; set; }
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
// Rewind requestStream so it can be read again.
var requestStream = await actionContext.Request.Content.ReadAsStreamAsync();
if (requestStream.CanSeek)
{
requestStream.Position = 0;
}
// Read the actual JSON payload.
var json = await actionContext.Request.Content.ReadAsStringAsync();
// Deserialize JSON to corresponding validation ViewModel.
var viewModel = JsonConvert.DeserializeObject(json, ViewModelType);
var context = new ValidationContext(viewModel);
var results = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(viewModel, context, results);
if (!isValid)
{
// Throw HttpResponseException instead of setting actionContext.Response, so the exception will be logged by the ExceptionLogger.
var responseMessage = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage)));
throw new HttpResponseException(responseMessage);
}
await base.OnActionExecutingAsync(actionContext, cancellationToken);
}
}
After that, apply this custom filter to your ODataController:
[ODataUnderpostingValidation(typeof(PetViewModel))]
public class PetController : ODataController
{ /* Implementation here */ }
Voila! Now you have everything in place. Underposting validation works fine.
You've got a couple options as I see it:
First In your controller you can check the integer value and if its below a certain value return 404.
if (Age <= 0)
return NotFound();
This could be labor intensive and if you're doing it for every controller method it's not very DRY.
Second in your Pet class you can use the DataAnnotations Attribute Range e.g.
[Range(0, 80, ErrorMessage = "Value for {0} must be between {1} and {2}")]
public int Age { get; set; }
Where Age can be a maximum of 80.
https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.rangeattribute(v=vs.110).aspx
Lastly I think your a more permanent solution for you would be to create your own validation:
public class AgeValidation : ValidationAttribute {
public override bool IsValid(object value) {
if (Object.Equals(value, null)) {
return false;
}
int getage;
if (int.TryParse(value.ToString(), out getage)) {
if (getage == 0)
return false;
if (getage > 0)
return true;
}
return false;
}
}
Then in your Pet class add:
[AgeValidation(ErrorMessage = "Age is wack")]
public int Age { get; set; }
Borrowed from How to do Integer model validation in asp.net mvc 2
What is the best way to handle the queries[search] and sorts[Driver1] parameters in the following querystring in asp.net?
?queries[search]=greenock&id=20&sorts[Driver1]=1
I tried using this model but only id was bound:
public class ICRSRequestModel
{
public int ID { get; set; }
public ICollection<string> Sorts { get; set; }
public ICollection<string> Queries { get; set; }
}
I don't have the option of changing the requesting application unfortunately, and the string contained inside [] could be any unknown value.
If the property is
public ICollection<string> Queries { get; set; }
Then the query string would need to be
?queries[0]=greenock
you would need to change the property to
public Dictionary<string, string> Queries { get; set; }
so that the query string could be
?queries[search]=greenock
The key will be "search" and the value will be "greenock"
Note this will only work for a single queries value. ?queries[search]=greenock?queries[anotherKey]=anotherValue will not work
Sorry, not got 50 rep points yet else I would have commented with this probably useless comment, but here goes..
I'm sure there's a better way using MVC bindings but if all else fails it might be worth taking the QueryString from the Server.Request object and splitting the string up to extract the information you want. You can get them in to a keyvaluepair collection using the code I had lying around in a project, I'm sure it can be manipulated for your needs.
Dictionary<string, string> dictionary = new Dictionary<string, string>();
foreach (string part in queryString.Split(new char[] { '&' }))
{
if (!string.IsNullOrEmpty(part))
{
string[] strArray = part.Split(new char[] { '=' });
if (strArray.Length == 2)
{
dictionary[strArray[0]] = strArray[1];
}
else
{
dictionary[part] = null;
}
}
}
Django has a very handy test client/dummy web browser that one can use in test cases to verify the correctness of HTTP responses (e.g., status codes, context/model data). It does not require you to have the web server running, as it deals directly with the framework to simulate the calls.
I'd really love an nUnit (or similar) equivalent that we can slip right into our test suites. We're working in MVC3 and 4, and want to check things like successful 301 redirects, that model validation is correct, and that ViewModel data is correct in the views.
What's the best solution for this?
ViewModel Data should be easy to check with the following:
public T GetViewModelFromResult<T>(ActionResult result) where T : class
{
Assert.IsInstanceOf<ViewResult>(result);
var model = ((ViewResult)result).Model;
Assert.IsInstanceOf<T>(model);
return model as T;
}
[Test]
public void TheModelHasTheOrder()
{
var controller = new MyController();
var result = controller.MyActionMethod();
var model = GetViewModelFromResult<MyModel>();
Assert.That(model, Is.SameAs(???));
}
As for the model validation, if you are using the out of the box .net property attributes like [Required] etc, you can be pretty sure they will work fine, and won't need testing.
To explicitly test the [Required] etc attributes on your object you will have extract the built in .net validation into another class. Then use that class in your controllers to validate your objects, instead of the Model.IsValid property on your controller.
The model validator class:
public class ModelValidator : IModelValidator
{
public bool IsValid(object entity)
{
return Validate(entity, new List<ValidationResult>());
}
public IEnumerable<ValidationResult> Validate(object entity)
{
var validationResults = new List<ValidationResult>();
Validate(entity, validationResults);
return validationResults;
}
private static bool Validate(object entity, ICollection<ValidationResult> validationResults)
{
if (entity != null)
{
var validationContext = new ValidationContext(entity, null, null);
return Validator.TryValidateObject(entity, validationContext, validationResults);
}
return false;
}
}
This could be verifiable in unit tests with the following:
public class MySampleEntity
{
[Required]
public string X { get; set; }
[Required]
public int Y { get; set; }
}
[TestFixture]
public class ModelValidatorTests
{
[Test]
public void GivenThePropertiesArePopulatedTheModelIsValid()
{
// arrange
var _validator = new ModelValidator();
var _entity = new MySampleEntity { X = "ABC", Y = 50 };
// act
var _result = _validator.IsValid(_entity);
// assert
Assert.That(_result, Is.True);
}
}
I'm currently trying to work through MVC validation, and am coming up against some problems where a field is required depending on the value of another field. An example is below (that I haven't figured out yet) - If the PaymentMethod == "Cheque", then the ChequeName should be required, otherwise it can be let through.
[Required(ErrorMessage = "Payment Method must be selected")]
public override string PaymentMethod
{ get; set; }
[Required(ErrorMessage = "ChequeName is required")]
public override string ChequeName
{ get; set; }
I'm using the System.ComponentModel.DataAnnotations for the [Required], and have also extended a ValidationAttribute to try and get this working, but I can't pass a variable through to do the validation (extension below)
public class JEPaymentDetailRequired : ValidationAttribute
{
public string PaymentSelected { get; set; }
public string PaymentType { get; set; }
public override bool IsValid(object value)
{
if (PaymentSelected != PaymentType)
return true;
var stringDetail = (string) value;
if (stringDetail.Length == 0)
return false;
return true;
}
}
Implementation:
[JEPaymentDetailRequired(PaymentSelected = PaymentMethod, PaymentType = "Cheque", ErrorMessage = "Cheque name must be completed when payment type of cheque")]
Has anyone had experience with this sort of validation? Would it just be better to write it into the controller?
Thanks for your help.
If you want client side validation in addition to model validation on the server, I think the best way to go is a custom validation attribute (like Jaroslaw suggested). I'm including the source here of the one I use.
Custom attribute:
public class RequiredIfAttribute : DependentPropertyAttribute
{
private readonly RequiredAttribute innerAttribute = new RequiredAttribute();
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue) : base(dependentProperty)
{
TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get a reference to the property this validation depends upon
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(DependentProperty);
if (field != null)
{
// get the value of the dependent property
var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);
// compare the value against the target value
if ((dependentvalue == null && TargetValue == null) ||
(dependentvalue != null && dependentvalue.Equals(TargetValue)))
{
// match => means we should try validating this field
if (!innerAttribute.IsValid(value))
// validation failed - return an error
return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
}
}
return ValidationResult.Success;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredif"
};
var depProp = BuildDependentPropertyId(DependentProperty, metadata, context as ViewContext);
// find the value on the control we depend on;
// if it's a bool, format it javascript style
// (the default is True or False!)
var targetValue = (TargetValue ?? "").ToString();
if (TargetValue != null)
if (TargetValue is bool)
targetValue = targetValue.ToLower();
rule.ValidationParameters.Add("dependentproperty", depProp);
rule.ValidationParameters.Add("targetvalue", targetValue);
yield return rule;
}
}
Jquery validation extension:
$.validator.unobtrusive.adapters.add('requiredif', ['dependentproperty', 'targetvalue'], function (options) {
options.rules['requiredif'] = {
dependentproperty: options.params['dependentproperty'],
targetvalue: options.params['targetvalue']
};
options.messages['requiredif'] = options.message;
});
$.validator.addMethod('requiredif',
function (value, element, parameters) {
var id = '#' + parameters['dependentproperty'];
// get the target value (as a string,
// as that's what actual value will be)
var targetvalue = parameters['targetvalue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString();
// get the actual value of the target control
var actualvalue = getControlValue(id);
// if the condition is true, reuse the existing
// required field validator functionality
if (targetvalue === actualvalue) {
return $.validator.methods.required.call(this, value, element, parameters);
}
return true;
}
);
Decorating a property with the attribute:
[Required]
public bool IsEmailGiftCertificate { get; set; }
[RequiredIf("IsEmailGiftCertificate", true, ErrorMessage = "Please provide Your Email.")]
public string YourEmail { get; set; }
Just use the Foolproof validation library that is available on Codeplex:
https://foolproof.codeplex.com/
It supports the following "requiredif" validation attributes / decorations:
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
To get started is easy:
Download the package from the provided link
Add a reference to the included .dll file
Import the included javascript files
Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.
I would write the validation logic in the model, not the controller. The controller should only handle interaction between the view and the model. Since it's the model that requires validation, I think it's widely regarded as the place for validation logic.
For validation that depends on the value of another property or field, I (unfortunately) don't see how to completely avoid writing some code for that in the model, such as shown in the Wrox ASP.NET MVC book, sort of like:
public bool IsValid
{
get
{
SetRuleViolations();
return (RuleViolations.Count == 0);
}
}
public void SetRuleViolations()
{
if (this.PaymentMethod == "Cheque" && String.IsNullOrEmpty(this.ChequeName))
{
RuleViolations.Add("Cheque name is required", "ChequeName");
}
}
Doing all validation declaratively would be great. I'm sure you could make a RequiredDependentAttribute, but that would only handle this one type of logic. Stuff that is even slightly more complex would require yet another pretty specific attribute, etc. which gets crazy quickly.
Your problem can be solved relatively simply by the usage of conditional validation attribute e.g.
[RequiredIf("PaymentMethod == 'Cheque'")]
public string ChequeName { get; set; }