I have the following model:
public class ViewDataItem
{
public string viewName { get; set; }
public UpdateIndicator updateIndicator { get; set; }
}
With the following enum:
public enum UpdateIndicator
{
Original,
Update,
Delete
}
And the following Validator:
public class ViewValidator : AbstractValidator<ViewDataItem>
{
public ViewValidator()
{
RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
RuleFor(x => x.updateIndicator).SetValidator(new UpdateIndicatorEnumValidator<UpdateIndicator>());
}
}
public class UpdateIndicatorEnumValidator<T> : PropertyValidator
{
public UpdateIndicatorEnumValidator() : base("Invalid update indicator") {}
protected override bool IsValid(PropertyValidatorContext context)
{
UpdateIndicator enumVal = (UpdateIndicator)Enum.Parse(typeof(UpdateIndicator), context.PropertyValue.ToString());
if (!Enum.IsDefined(typeof(UpdateIndicator), enumVal))
return false;
return true;
}
}
The code is in a WebAPI that receives data via JSON, deserialize it to an object and then validates, but for some reason I can send whatever I please in the updateIndicator, so long as I don't put in an integer value larger than the max index in the enum (i.e 1,2 or 3 works fine, but 7 will generate an error).
How can I get this to validate the input of the data I receive to see if that value is actually in the Enum?
Try the built-in IsInEnum()
RuleFor(x => x.updateIndicator).IsInEnum();
This checks if the provided enum value is within the range of your enum, if not, the validation will fail:
"'updateIndicator' has a range of values which does not include '7'."
The problem arises from the fact that the API model builder will convert what is sent to an enum. If a value isn't found, it doesn't populate it, and the default value is used (as it would be with any other property data type that isn't populated).
In order to easily tell if the value sent is a valid enum value, you should make your property nullable. That way, if a value isn't able to be parsed, it will be set to null. If you want to ensure that the property is set, just have your validator not allow null values for it.
public class ViewDataItem
{
public string viewName { get; set; }
public UpdateIndicator? updateIndicator { get; set; }
}
public class ViewValidator : AbstractValidator<ViewDataItem>
{
public ViewValidator()
{
RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
RuleFor(x => x.updateIndicator).NotNull();
}
}
Without setting the property to null, your model will always have a valid value when you have it. Alternatively, you could have the first value of your enum be a dummy value, but that would be a code smell. A null model property makes far more sense.
If you want to find out what the actual value that was sent to the API endpoint was, you'll need to look at creating an HTTP Handler, which is beyond the scope of this question.
Related
Let's say I have a test class like this:
public class TestClass
{
public Properties[] TestProperties { get; set; }
public Guid Id { get; set; }
public TestClass(Properties[] testProperties)
{
Id = Guid.NewGuid();
TestProperties = testProperties;
}
}
And a Properties class as follows:
public class Properties
{
public Guid Id { get; set; }
public string Name { get; set; }
public Properties(string name)
{
Name = name;
Id = Guid.NewGuid();
}
}
I need to validate that none of my properties Name at the TestProperties array is null, like this:
public class TestValidator : AbstractValidator<TestClass>
{
public TestValidator()
{
RuleForEach(x => x.TestProperties)
.Must(y => y.Name != string.Empty && y.Name != null)
.WithMessage("TestPropertie at {CollectionIndex}, can't be null or empty");
}
}
But instead of returning the position of the failing property, at the validation message, I would like to return it's Id, how can I do so?
Yes, using the default validators it's possible to inject other property values from the objects into the message.
This can be done by using the overload of WithMessage that takes a
lambda expression, and then passing the values to string.Format or by
using string interpolation.
Source
There are a couple of ways you can do it. Firstly, as per your current implementation using Must:
public class TestClassValidator : AbstractValidator<TestClass>
{
public TestClassValidator()
{
RuleForEach(x => x.TestProperties)
.Must(y => !string.IsNullOrEmpty(y.Name))
.WithMessage((testClass, testProperty) => $"TestProperty {testProperty.Id} name can't be null or empty");
}
}
I try to avoid using Must when possible, if you stick to using the built-in validators you stand a better chance of client-side validation working out of the box (if you're using it in a web app). Using ChildRules allows you to use the built-in validators and also get the benefit of using the fluent interface:
public class TestClassValidator : AbstractValidator<TestClass>
{
public TestClassValidator()
{
RuleForEach(x => x.TestProperties)
.ChildRules(testProperties =>
{
testProperties.RuleFor(testProperty => testProperty.Name)
.NotNull()
.NotEmpty()
.WithMessage(testProperty => $"TestProperty {testProperty.Id} name can't be null or empty");
});
}
}
ChildRules doco
I've included the NotNull() validator for verbosity/alignment with the custom error message, however it's not needed as NotEmpty() will cover the null or empty case.
Finally if it was me I'd probably create a separate validator for the Properties type (should this be Property?) and use SetValidator to include it. Splits up the validation concerns, defines the validation for a type once and makes the rules reusable, and makes the validators easier to test. I'm not going to cover that here as that feels beyond the scope of this question but the links below give examples on how to do it.
Child validator doco (SetValidator usage) here and here
Working samples of the above including tests can be found here.
I approached this a little differently, because I wanted a more reusable solution. (I'm validating many different classes in similar ways). Putting the message identification inside the extension with Must<> ties you to the type and could lead to copy&paste. Instead, I pass as an argument to the validation, a Func that returns an identifying string and lets the caller decide how to identify the object being validated.
public static IRuleBuilderOptions<T, string> IsValidStringEnumAllowNullable<T>(this IRuleBuilder<T, string> ruleBuilder, IList<string> validValues, Func<T,string> identifierLookup)
{
return ruleBuilder.Must((rootObject, testValue, context) =>
{
context.MessageFormatter.AppendArgument("AllowableValues", string.Join(", ", validValues));
context.MessageFormatter.AppendArgument("Identifier", identifierLookup(rootObject));
return string.IsNullOrEmpty(testValue) || validValues.Contains(testValue, StringComparer.Ordinal);
}).WithMessage("{Identifier}{PropertyName} with value {PropertyValue} must be one of the allowable values: {AllowableValues}, or null or empty string");
}
And then the calling code where I tell the specific validation message 'how' to identify the object for messaging:
base.RuleForEach(rq => rq.Thingies).ChildRules(x =>
{
x.RuleFor(f => f.MyProperty).IsValidStringEnumAllowNullable(ValidationStrings.AnArrayOfAllowedValues, f => $"Thing[{f.Id}] ");
});
The result of this code is
Thing[1234] My Property with value asdf must be one of the allowable values: Value1, ValidValue2, Somethingelse, or null or empty string
This is the spiritual successor to my previous question Web API attribute routing and validation - possible?, which I think was too general to answer. Most of those issues are solved, but the default value question remains.
Basically I have solved many pieces of the puzzle. I have this:
[HttpGet]
[Route("test/{id}"]
public IHttpActionResult RunTest([FromUri]TestRequest request)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
return Ok();
}
My TestRequest class:
public class TestRequest
{
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; }
}
The problem is that if no parameter is in the query string for something, the model is "valid" and yet something is null.
If I specify a blank value for something (i.e. GET test/123?something=), then the default value comes into play, and the model is valid again.
Why is this? How can I get a default value into my model here? As a bonus, why is it when a parameter is not specified, the default value is not used, but when a blank string is explicitly specific, the default value is used?
(I've been trawling through the ASP.NET stack source code and am knee-deep in model binders and binding contexts. But my best guess can't be right - it looks like the DefaultValueAttribute is used only if the parameter value is null. But that's not the case here)
You need to initialize the default value in the constructor for your Model:
public class TestRequest
{
public TestRequest()
{
this.something = "SomethingDefault";
}
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; }
}
Update:
With C# 6, you don't need to initialize it in the constructor anymore. You can assign the default value to the property directly:
public class TestRequest
{
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; } = "SomethingDefault";
}
As documentation of the DefaultValueAttribute states:
Note
A DefaultValueAttribute will not cause a member to be
automatically initialized with the attribute's value. You must set the
initial value in your code.
In the case where you're providing no value for your something property, the property is initialized and the ModelBinder doesn't have a value to assign to it and thus the property defaults to its default value.
Specifying the default in the constructor works for when no parameter is specified at all, but when a blank string is specified, null is put into the field instead.
As such, adding [DefaultValue("")] actually worked the best - when a blank string was specified, a blank string was passed in. Then the constructor can specify default values for when the parameter is missing.
To get around this, I've created PreserveBlankStringAttribute, derives from DefaultValueAttribute which is equivalent to [DefaultValue("")].
I would very much welcome a better answer than this, please.
Long Description: I'm writing a basic search by filter function from an entity, so I can do something like this:
public Entity GetEntityBy(Entity filter)
{ }
public IList<Entity> GetEntitiesBy(Entity filter)
{ }
The problem is with non nullable types (int, float, etc), and I don't want to "force" all properties to be written as nullables. I want to avoid any kinds of rules (such as applying attributes or implementing my own get/set) so I can write the entity just as usual and simply use this filter function.
The code looks like this:
public class Entity
{
public int EntityID { get; set; }
public string Name { get; set; }
public DateTime RegisterDate { get; set; }
//Other properties
}
public IList<Entity> GetEntitiesBy(Entity filter)
{
if (filter != null)
{
if (filter.EntityID > 0)
{
//Add criteria to filter by ID
//In this case it works because there shouldn't have any IDs with 0
}
//this won't work because DateTime can't be null
//I can't check the default value as well because there are some searchs using the default value and I don't want to ignore that
if (RegisterDate != null)
{
}
}
}
It's supposed to be a simple equal filter depending on the values found in the filter parameter, but as it is now I don't know when I should ignore the default values or not.
I already have a SCRUD manager sort of class, so I want to add a function call to the class that it belongs so I can check when a property has been read/written to.
Short Description: How do I add a function call before a property's get or set acessor is called on a dynamic class? Is this even possible?
My model has an property whcih I assigned a ReadOnly tag. My intention is to set a propery to readonly true or false depending of a condition like
class Test {
static bool test() { // It is my model
// My logical response ...
return true;
}
[ReadOnly(test)]
datetime prop {get; set;}
}
using this model I get the error message:
Error 7 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter
Could you, pleaee, get me an idea for this?
=====================================================================================
Solution like answer 3:
Inside Template:
cshtml:
...
#if (Model.condition) {
<td>#Html.EditorFor(m => m.prop)</td>
} else {
<td>#Html.DisplayFor(m => m.prop)</td>
}
...
It will be inside the template.
Inside Model in the copmnstructor I set the condition of the property condition:
class XX {
public condition { get; set; } // not necessary readonly, I do the logical inside the template.
public datetime prop {get; set; }
public XX (bool _condition) {
condition = _condition;
}
}
You cannot use something other than described in the error message as the parameter for attributes.
It is a sad true, but still a true.
Only this:
[ReadOnly(5)]
[ReadOnly("string")] // Or other type (int/long/byte/etc..) which can be used with const keyword.
[ReadOnly(Enums.SomeValue)]
[ReadOnly(typeof(SomeType))]
[ReadOnly(new string[] { "array", "of", "strings"} )]
So unfortunately, you wont succeed making custom parameter type:
class ReadOnlyAttribute { ReadOnlyAttribute(MyClass foo) { ... } }
One alternative would be to do it within the get/set, something like:
class test
{
static bool test() {
...
}
private datetime prop;
public datetime Prop
{
get { return prop; }
set
{
if (test)
prop = value;
else
// Ignore, throw exception, etc.
}
}
}
The metadata for the model (which includes your IsReadOnly) is created by the Model Metadata providers. This providers only have information about data types, and property names, but not about the concrete values of the properties of an instance of the model. So the metadata can not depend on the value of a property or method of the model class. (So implementing a Custom ModelMetada Provider wouldn't solve your problem).
Then, you have to find an alternative, hacky, way to do it:
Create a view model with two properties, the original, without the readonly attribute and an additional readonly property, decorated with the readonly attribute.
In the view, decide which of the two to show.
public class MyModel
{
public DateTime MyProperty { get; set;}
[ReadOnly]
public DateTime MyPropertyRo { get; set;}
}
If you want to recover the posted values, the editable version should use the original property in the Telerik control. The non-editable version should use the readonly property in the Telerik control, and the original property in a hidden-field, so that you can recover it in the post.
#if (Model.MyPropertyIsReadOnly)
{
#Html.HiddenFor(m => m.Property)
#Html.TelerikEditorFor(m => m.PropertyRo ...)
}
else
{
#Html.TelerikEditorFor(m => m.Property ...)
}
If you have to do this in many different views, you can create an Html helper (extension method for Html), which receives the 3 properties and includes the last sample code.
Finally, it would be even better to make a custom Editor template, but that's much harder to do if you don't have experience.
There is still another option: contact telerik, and ask them to implement a version of their control which receives a readonly parameter, and does this automatically for you. I think it must be really easy for them to implement it. So, if you're lucky enough...
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.