One of the many challenging things in building n-tier applications is designing a validation system that allows running rules on both the client and the server and sending messages and displaying them back on the client. I’ve built a couple application frameworks in my time and so I know how tricky this can be. I’ve been spending time digging into the validation framework for LightSwitch and I have to say I’m impressed. LightSwitch makes it easy to write business rules in one place and run them in the appropriate tiers. Prem wrote a great article detailing the validation framework that he posted on the LightSwitch Team Blog yesterday that I highly recommend you read first:
Most validation rules you write are rules that you want to run on both the client and the server (middle-tier) and LightSwitch does a great job of handling that for you. For instance when you put a validation rule on an entity property this rule will run first on the client. If there is an error the data must be corrected before it can be saved to the middle-tier. This gives the user an immediate response but also makes the application scale better because you aren’t unnecessarily bothering the middle-tier. Once the validation passes on the client, it is run again on the middle-tier. This is best practice when building middle-tiers – don’t ever assume data coming in is valid.
Validating sets (or collections) of data can get tricky. You usually want to validate the set on the client first but then you have to do it again on the middle-tier, not only because you don’t trust the client, but also because the set of data can change in a multi-user environment. You need to take the change set of data coming in from the client, merge it with the set of data stored in the database, and then proceed with validation. Dealing with change sets and merging yourself can get pretty tricky sometimes. What I didn’t realize at first is that LightSwitch also handles this for you.
Example – Preventing Duplicates
Let’s take an example that I was working on this week. I have the canonical OrderHeader –< OrderDetails >—Product data model. I want a rule that makes sure no duplicate products are chosen across OrderDetail line items on any given order. So if a user enters the same product twice on an order, validation should fail. Here I have an orders screen that lets me edit all the orders for a selected customer. For each order I should not be allowed to enter the same product more than once:
Where Do the Rules Go?
You can write rules in xxx_Validate methods for entity properties (fields) and the entity itself. From the Entity Designer select the property name then click the arrow next to the “Write Code” button to drop down the list of available methods. The property methods will display for the selected property. The entity methods are under “General Methods”. In my example if you select the Product property and drop down the list of methods you see two validation methods Product_Validate and OrderDetails_Validate.
The Property Methods change as you select an entity property (field) in the designer but the General Methods are always displayed for the entity you are working with. Property _Validate methods run both on the client and then again on the middle-tier. Entity _Validate methods run on the server, these are called DataService validations.
In my order entry scenario I was first tempted to write code in the DataService on the OrderHeader entity and check the collection of OrderDetails there. When I select the OrderHeader entity in the Entity Designer, click the arrow next to the “Write Code” button, and select OrderHeaders_Validate, a method stub is generated for me in the ApplicationDataService class. This is where I was thinking I could validate my set of OrderDetails and return an error if there were duplicates.
Public Class ApplicationDataService Private Sub OrderHeaders_Validate(ByVal entity As OrderHeader, ByVal results As EntitySetValidationResultsBuilder) Dim isValid = False 'Write code to validate entity.OrderDetails collection '.... If Not isValid Then results.AddPropertyError("There are duplicated products on the order") End If End Sub End Class
However I quickly realized that this wouldn’t work because the OrderHeader entity would need to be changed for this validation to fire. If a user is editing a current order’s line items (OrderDetails) then only the validation for the OrderDetail would fire, not OrderHeader. Another issue with putting my rule in the ApplicationDataService class is the user would have to click save before the rule would fire and we’d have an unnecessary round-trip to the middle-tier. We want to be able to check this set for problems on the client first. Another issue is if I found an error then only a general validation message on the order would be presented to the user. They would have to stare at the screen to figure out the problem.
I think the reason why I went this route in the first place is because I was thinking I needed to merge the change set coming from the client with the set of data in the database and then validate that. It turns out that LightSwitch handles this for you. When you are validating a set of data (entity collection) on the client, you are validating what is on the user’s screen. When the validation runs on the server you are validating the merged set of data. NICE!
(Note that you can still access the change set via the DataWorkspace object but we’ll dive into that in a future post. )
The Right Way to Write this Rule
Since LightSwitch is doing all the heavy-lifting for me this rule gets a whole lot easier to implement. Since we’re checking duplicate products on each OrderDetail we need to put the code in the Product_Validate method of the OrderDetail entity (see screenshot above). Now we can write a simple LINQ query to check for duplicates.
Public Class OrderDetail Private Sub Product_Validate(ByVal results As EntityValidationResultsBuilder) If Me.Product IsNot Nothing Then 'Look at all the OrderDetails that: ' 1) have a product specified (detail.Product IsNot Nothing) ' 2) have the same product ID as this entity (detail.Product.Id = Me.Product.Id) ' 3) is not this entity (detail IsNot Me) Dim dupes = From detail In Me.OrderHeader.OrderDetails Where detail.Product IsNot Nothing AndAlso detail.Product.Id = Me.Product.Id AndAlso detail IsNot Me 'If Count is greater than zero then we found a duplicate If dupes.Count > 0 Then results.AddPropertyError(Me.Product.ProductName + " is a duplicate product") End If End If End Sub End Class
This validation will fire for every line item we add or update on the order. It will first fire on the client and Me.OrderHeader.OrderDetails will be the collection of line items being displayed on the screen. If this rule passes validation on the client then it will fire on the middle-tier and the Me.OrderHeader.OrderDetails will be the collection of line items that were sent from the client merged with the data on the server. This means that if another user has modified the line items on the order we can still validate this set of data properly. Also notice when we specify the error message, it is attached to the Product property on the OrderDetail entity so when the user clicks the message in the validation summary at the top of the screen, the proper row in the grid is highlighted for them.
Stay tuned for more How Do I videos on writing business rules.
UPDATE: Here’s a video I did on writing business rules: How Do I: Write business rules for validation and calculated fields in a LightSwitch Application?