AngularJS meet ASP.Net Server Validation
So. You're using AngularJS to build your front end with ASP.Net running on the server side. You're a trustworthy dev - you know that validation on the client will only get you so far. You need to validate on the server.
My particular scenario is where you have a form which you are saving. Angular serves you well when it comes to hooking in your own client side validation. But it doesn't really ship with anything that supports nicely presenting server side validation on the client. Invariably when you look around you find people duplicating their server side validation on the client and presenting all their server side validation in a <div> at the top of the screen.
This works but it's not as helpful to the user as it might be. It groups together all the validation from the server into one place. What I want is field level validation from the server that's presented on a field level basis on the screen.
Let us travel together to this promised land...
What do we need client side?
Well, let's start with a directive which I'll call serverError. This plants a validation message just after the element being validated which is displayed when that element is declared invalid by the server. (That is to say when the ngModel has a $error.server set.) When the element is changed then the $error.server is unset in order that validation can be hidden and the form can be revalidated against the server.
I'm using TypeScript with Angular so for my JavaScript examples I'll give you both the TypeScript which I originally wrote and the generated JavaScript as well.
TypeScript
JavaScript
If you look closely at this directive you'll see it is restricted to be used as an attribute and it depends on 2 things:
- The value that the server-error attribute is set to should be an object which will contain key / values where the keys represent fields that are being validated.
- The element being validated must have a name property (which will be used to look up the validation message in the server-error error "dictionary".
Totally not clear, right? Let's have an example. Here is my "sageEdit" screen which you saw the screenshot of earlier:
If you look closely at where server-error is used we have a name attribute set (eg "sage.email") and we're passing in something called vm.errors as the server-error attribute value. That's because we're using the "controller as" syntax and our controller is called vm.
On that controller we're going to have a dictionary style object called errors. If you wanted to you could put that object on the scope instead and omit the "vm." prefix. You could call it wrongThingsWhatISpottedWithYourModel or barry - whatever floats your boat really. You get my point; it's flexible.
Let's take a look at our sageEdit Angular controller:
TypeScript
JavaScript
Okay - this is a shedload of code and most of it isn't relevant to you. I share it as I like to see things in context. Let's focus in on the important bits that you should take away. Firstly, our controller has a property called errors.
Secondly, when we attempt to save our server sends back a JSON payload which, given a validation failure, looks something like this:
So let's pare back our save function to the bare necessities (those simple bare necessities, forget about your worries and your strife...):
TypeScript
JavaScript
At the point of save we wipe any server error messages that might be stored on the client. Then, if we receive back a payload with errors we store those errors and set the validity of the relevant form element to false. This will trigger the display of the message by our directive.
That's us done for the client side. You're no doubt now asking yourself this question:
How can I get ASP.Net to send me this information?
So glad you asked. We've a simple model that looks like this which has a number of data annotations:
When we save we post back to a Web API controller that looks like this:
As you can see, when ModelState is not valid we send back a dictionary of the ModelState error messages keyed by property name. We generate this with an extension method I wrote called ToErrorDictionary:
That's it - your solution front to back. It would be quite easy to hook other types of validation in server-side (database level checks etc). I hope you find this useful.
Discussion in the ATmosphere