77

I have a viewmodel with an Id property

[Required]
public int Id { get; set; }

But I think this attribute is working only for string properties.

When no Id is set, Id has value 0 and the model is valid.

How can I enforce that if no value for a int property is set, the model will be invalid ?

user256034
  • 4,289
  • 5
  • 37
  • 44

5 Answers5

115

Use the Range Attribute.

Set minimum to 1 and maximum to int.MaxValue

[Range(1, int.MaxValue, ErrorMessage = "Value for {0} must be between {1} and {2}.")]
CodeNotFound
  • 22,153
  • 10
  • 68
  • 69
Lee Smith
  • 6,339
  • 6
  • 27
  • 34
  • I was also considering this option,but what if an Id is 0. Can happen :-) – user256034 Jul 12 '11 at 13:38
  • 4
    True, but it depends on your DB architecture. If you're using autoincremented fields starting at 1, then you're likely safe. Most database designs I've seen treat zero as a special value that can't occur. – ShadowChaser Jul 09 '12 at 16:16
  • 2
    Best answer, as things come with 0 from client-side, even if it was nullable it would still be valid int... – SparK Jul 14 '16 at 18:08
  • 3
    Nope, really bad practice... you are limiting the admitted values to be `> 0` which could be not viable in the present and/or in the furure of a project... really don't do it! – Shockwaver Jul 16 '19 at 11:31
  • 1
    @Shockwaver your comment is not relevant to the question asked. Also, are you suggesting not validating input because it might be valid it a future version of a project?? – Lee Smith Jul 17 '19 at 09:06
  • Expecially when a couple of minutes more worth of thinking would bring a solid fix in place of an easy/quick/incomplete/bugs-bringer way out! – Shockwaver Jul 17 '19 at 10:11
  • 1
    @Shockwaver please feel free to enlighten us by posting your superior answer. – Lee Smith Jul 17 '19 at 11:09
  • 2
    I'll lightly pass by your mocking (I know I can be a finger from time to time)... As I said in the comment above, don't limit the range upon which you can't make assumptions, but encapsulate your Entity in a ViewModel (either with inheritance or property item) and use a property `int?` (with related private field) whose setter is also writing into the requested field Id, hence leaving it primitive. Of course your DataAnnotation `[Required]` goes on this wrapper prop. The rest is just logic. Don't have time to write a full blown formal answer, but I'm confident you get the idea behind. – Shockwaver Jul 17 '19 at 11:42
  • entity passes zero for new values to tell the database to add it a new id. While a zero value is legal in the database, it will cause problems in the framework. – John Lord Feb 06 '20 at 23:53
53

Change the type to Nullable<int> (shortcut int?) to allow null values.

Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • 30
    But isn't this against the logic ? In first place a non nullable int should behave like it is required without any required attribute. And nullable int should behave like a non required value. – user256034 Jul 12 '11 at 10:57
  • 6
    @user256034 If zero isn't valid, then you need a nullable int because Required only enforces that a valid int value exists, of which 0 is valid. Make sense? – Jeff Reddy Mar 30 '12 at 14:34
  • 5
    It doesn't have to make sense, it just has to be true :-) –  Oct 28 '15 at 16:29
  • 1
    if property is nonnullable foreign key then its a nonsense to change it to nullable int and required attribute should fail to validate like it does in MVC, too bad it doesnt – Jaroslav Daníček Apr 01 '19 at 19:04
  • 2
    Guys that's what model layering is there for! When needed you encapsulate your DB entity in a ViewModel which exposes nullable wrappers for fields that can't (or shouldn't) actually be! You then properly work your entity out with getters and setters (or manually in the controller action) before commiting it to the DB. ;) – Shockwaver Jul 16 '19 at 11:28
4

This is similar to the answer from @Lee Smith, but enables 0 to be valid input, which might be useful in some scenarios.

What you can do is to init the int value to another value then 0, like this:

[Range(0, int.MaxValue)]
public int value{ get; set; } = -1;

It would even be possible to support all values except int.MinValue by doing it like this:

[Range(int.MinValue + 1, int.MaxValue)]
public int value{ get; set; } = int.MinValue;
David Berg
  • 1,958
  • 1
  • 21
  • 37
3

For .NET Core (and maybe earlier versions) you can also create a custom attribute to perform the range validation for ease of reuse:

public class Id : ValidationAttribute
{
    protected override ValidationResult IsValid(
        object value,
        ValidationContext validationContext)
    {
        return Convert.ToInt32(value) > 0 ?
            ValidationResult.Success :
            new ValidationResult($"{validationContext.DisplayName} must be an integer greater than 0.");
    }
}

Use the Id attribute like this in your model:

public class MessageForUpdate
{
    [Required, Id]
    public int UserId { get; set; }
    [Required]
    public string Text { get; set; }
    [Required, Id]
    public int ChannelId { get; set; }
}

When the Id is <= 0 this error message is returned:

UserId must be an integer greater than 0.

No need to verify that the value is less than int.MaxValue (although it is nice to display that in the message) because the API will return this error by default before it gets this far even if the value is int.MaxValue + 1:

The JSON value could not be converted to System.Int32

Dustin C
  • 215
  • 6
  • 15
0

If you are using a database, you should use the attributes [Key] and [DatabaseGenerated(DatabaseGenerated.Identity)] and Id shouldn't be NULLABLE.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91