Are Data Annotations a sign of bad design?

The .net Framework comes with a validation system, called Data Annotations. At first glance, these are pretty awesome and I have used them a lot. In a nutshell, it allows you to do stuff like this:

[Required]
[StringLength(64)]
public string Title { get; set; }

It is also absolutely trivial to write your own validators and to validate objects using these. In fact, ASP.net MVC will automatically verify these as part of its model binding.

However, they have their limits, and because of these limits I started questing them a while ago. Since they are attributes, they have to be constant – not a big deal, constraints should be constant anyway. There is some awkwardness around MVC model binding, for example this:

[Range(0,100)]
public byte Value { get; set; }

will actually blow up during model binding if a negative amount is passed since the binding of a negative value to a byte fails and we don't even get to this point - arguably, this is an ASP.net MVC issue and not directly related to Data Annotations.

The bigger problem with Data Annotations is that they can't validate the object completely and thus make it hard to adhere to DRY and SRP rules. For example, let's say I have a business rule that says that Titles must be unique.

Whose job is it to ensure this constraint? With Data Annotations, we put the responsibility in the hands of the business object, which turns it from a simple data structure into an object (Uncle Bob Martin has some good insight about the distinction in his book Clean Code) and thus may violate SRP.

Let’s say we would want to create a [NoDuplicateTitle] Attribute. Inside the attribute, we could use Dependency Injection to get a database connection (since MVC 3, there is a built in DependencyResolver static), then do the validation and bail out if required. Our Business Service and Repository layers could then just run validation on the business object itself to not duplicate their logic.

The obvious problem is that these attributes are not reusable outside of an ASP.net MVC application unless you bundle them with their own Dependency Injection mechanism (essentially just a Service Locator). The other problem is that you just added additional database calls to your application, in places that aren't immediately obvious.

Another issue I see: What about other dependencies that aren't directly visible from the database? Let's say you have a rule that says you can only create 10 items an hour. That rule may change over time (e.g., next week management decides that it should be 25/hour) or may be inconsistent accross users (HR personell can create an unlimited amount while IT staff can only create 5). These rules are not constants, they can change from one second to the other. Also, this rule doesn't describe the validity of an object but rather the validity of an action. The same object may be valid or invalid depending on the user and time of submission and thus can only reliably be verified in the service in the moment you actually try to save it.

Is it the responsibility of the business object to know if it can be saved given the current user and time? That seems like too much responsibility for something that's supposed to be a data structure. Is it the services responsibility? That sounds more like it, since ultimately the service enforces the business rule. But if the service enforces the "10 items/hour" rule, shouldn't it also enforce the "64 character max length" rule? Because if the title is too long, the save should fail. Should the business object care why it fails?

Arguably, the C# type system is causing us some grief here since there is no good way to create a type with constraints like "a number between 0 and 100" or "a non-empty string that is 64 characters or less" (I think that Haskell or F# support this), but we still have to solve it somehow.

One way is to create a IEnumerable<ValidationResult> Validate(MyBusinessObject bo) method in the service and have it make the calls. This satisfies SRP and DRY since it's now only the service that handles this. The downside is that we lose some cheap and easy ways to build HTML Forms, e.g. an Html.TextBoxFor call can no longer check for a StringLengthAttribute to set the maxlength on the generated textbox. Data Annotations still make sense on View Models given that, but now we violated DRY and have to deal with changes all over the place (someone decides that Titles can be 72 chars long. You now need to change it in all places, and since you're likely dealing with a hardcoded literal 64 you need to make sure you're not changing some other constraint that also was 64 and shouldn't change. If you want to use a public const int MaxTitleLength = 64 you need to ask yourself where to put it. On the service? Now your ViewModels have a compile-time dependency on the service. On the business object? Seems to violate SRP. In a Constants.cs class? Smells like a big ball of mud. It has to go somewhere and the correct answer will vary by project and by the underlying tech stack, but it's an interesting problem to think about nevertheless.

There is another option involving dynamic generation of attributes at compile time, but that seems like a lot of bandaid. I don't know what the right answer is, but I'll try a different approach without Data Annotations for my next project and see how that goes.