Handling Entity Framework Validations in WCF Data Services
I’m writing this column on the heels of the Microsoft BUILD conference. The core of all of the excitement at BUILD was, of course, the new Metro UI for Windows 8 that sits on top of the new Windows Runtime (WinRT). If you’re a data geek, you might’ve already looked to see what options exist for providing data to “Metro style” apps. In this early preview, you can provide data from file storage or from the Web. If you want to interact with relational data, Web-based options include XML or JSON over HTTP, sockets and services. On the services front, Metro-style apps will provide client libraries for consuming OData, which means that any experience you have today working with OData through the Microsoft .NET Framework, Silverlight or other client libraries will give you a big advantage when you’re ready to consume OData in your Metro-style applications.
With that in mind, I’ll devote this column to working with OData. The Entity Framework (EF) release that contains Code First and the DbContext introduced a new Validation API. I’ll show you how to take advantage of built-in server-side validation when your EF Code First model is being exposed as OData through WCF Data Services.
Validation API Basics
You might already be familiar with configuring attributes such as Required or MaxLength to class properties using Data Annotations or the Fluent API. These attributes can be checked automatically by the new Validation API. “Entity Framework 4.1 Validation,” an article in the MSDN Data Developer Center (msdn.microsoft.com/data/gg193959), demonstrates this, as well as how to apply rules with the IValidatableObject interface and the ValidateEntity method. While you might already be validating Data Annotations and IValidatableObject on the client side, their rules can also be checked on the server side along with any ValidateEntity logic that you’ve added. Alternatively, you can also choose to trigger validation on demand in your server code.
Here, for example, is a simple Person class that uses two Data Annotations (the first specifies that the LastName property is required and the other sets a maximum length for the IdentityCard string field):
By default, EF will perform validation when SaveChanges is called. If either of these rules fails, EF will throw a System.Data.Entity.DbEntityValidationException—which has an interesting structure. Each validation error is described in a DbValidationError, and DbValidationErrors are grouped by object instance into sets of EntityValidationErrors.
For example, Figure 1 shows a DbEntityValidationException that would be thrown if EF detected validation problems with two different Person instances. The first EntityValidationErrors object contains a set of DbValidationErrors for a single Person instance where there were two errors: no LastName and the IdentityCard had too many characters. The second Person instance had a single problem; therefore, there’s only one DbValidationError in the second EntityValidationErrors object.
Figure 1 DbEntityValidationException Contains Grouped Sets of Errors
In the MSDN Data Developer Center article I mentioned, I showed the exception being passed back to a Model-View-Controller (MVC) application that knew how to discover and display the specific errors.
In a distributed application, however, the errors might not make it back to the client side to be used and reported so easily. While the top-level exception may be returned, the client application may have no idea how to drill into a DbEntityValidationException to find the errors. With many apps, you may not even have access to the System.Data.Entity namespace and therefore no knowledge of the DbEntityValidationException.
More problematic is how WCF Data Services transmits exceptions by default. On the client side, you only get a message telling you “An error occurred while processing this request.” But the critical phrase here is “by default.” You can customize your WCF Data Services to parse DbEntityValidationExceptions and return useful error information to the client. This is what I’ll focus on for the rest of this column.
WCF Data Service Results Hide Validation Errors by Default
My model is hosted in a DbContext data layer I’ve called PersonModelContext:
I have a simple data service that exposes the Person type from this context for reading and writing:
Because I’m using Code First, I would have to do some tweaks to get WCF Data Services to work with it. Instead of tweaking, I’ve replaced the Microsoft .NET Framework 4 System.Data.Services and System.Data.ClientServices with the Microsoft.Data.Services and Microsoft.Data.ClientServices libraries from the March 2011 WCF Data Services CTP (see bit.ly/mTI69m), which has those tweaks built in. That’s why the DataServiceProtocolVersion is set to V3.
Finally, I’m consuming the service with a simple console app that uses the following method to insert a Person:
Notice I’ve neglected to set the LastName property. Because LastName is configured to be required, the EF will throw an exception to the data service, but the console app will just receive a DataServiceRequestException with the message described earlier (“An error occurred while processing this request.”). If you drill into the inner exception, you’ll find that it contains the same message and no additional details.
WCF Data Services does have a setting to let you send back exception messages with more details by adding the following to the InitializeService method:
Now the inner message (contained in the XML response from the service) tells you: “Validation failed for one or more entities. See ‘EntityValidationErrors’ property for more details.” But unfortunately, the EntityValidationErrors do not get passed back with the exception. So you know that the Validation API found one or more problems, but you can’t discover anything more about the error. Note that I wrapped UseVerboseErrors in a compiler directive. UseVerboseErrors should only be used for debugging—you don’t want it in your production code.
Overriding the HandleException Method
WCF Data Services exposes a virtual (Overrideable) method called HandleException. This lets you capture any exception that happens in the service, analyze it and construct your own DataServiceException to return to the caller. It is in this method that you can parse out any Validation errors and return more meaningful information to the calling application. The signature of the method is:
The HandleExceptionArgs type has a number of properties: Exception, ResponseContentType, esponseStatusCode, ResponseWritten, UseVerboseErrors
Of interest to me is the Exception property. This is where you can capture and identify exceptions thrown by the Validation API—DbEntityValidationException. You can also handle any other types of errors here, but I will focus on looking for and parsing the validation exceptions. I’ve got the System.Data.Entity.Validation namespace in my using statements at the top of the class so that I don’t have to strongly type the exception.
I’ll start out with the presumption that only a single entity is being validated, which is why I’m only querying for the first EntityValidationErrors contained in the exception, as shown in Figure 2. If you want the service to validate multiple objects, be sure to use the SaveChangesOptions.Batch parameter when you call SaveChanges. Otherwise, only one object will be saved and validated at a time and once you hit an error, no more objects will be saved or validated.
Figure 2 Building a More Useful Exception Message
What’s happening in this method is that I first check to see if the exception is the type thrown by the Validation API. If it is, I pull the exception into the variable “ex.” Next, I query for a list of all of the DbValidationErrors contained in the first set of EntityValidationErrors in the exception. Then I build up a new error string using the ErrorMessage property of each EntityValidationError and pass that string back to the calling application in a new DataServiceException. EntityValidationError has other properties, but it builds up a complete error message using the name of the property and the validation problem into the ErrorMessage. With Code First you can specify a custom error message, but I’m happy with the defaults for the purposes of this demonstration. In this example, the message is “The LastName field is required.” Note that the constructor for DataServiceException has a number of overloads. I’m keeping it simple by just providing the “internal server error” 500 code and a string with the message I want to relay.
Parsing the New Exception on the Client
Now, on the client side, you’ll still get an exception that says “An error occurred while processing this request,” but this time the inner exception contains the message “The LastName field is required.”
But it’s not a simple string. The message of a DataServiceRequestException is formatted in an HTTP Response because the request is made over HTTP:
One of the overloads for the DataServiceException I constructed in the service allows you to insert custom error codes. If I had used that, the custom code would show up in the <code> element of the error. If you’re calling the service from a Web app, you may be able to display the HTTP response directly in your UI. Otherwise, you’ll probably want to parse it so that you can handle the exception using whatever patterns you’re using in your application for dealing with errors.
I’m using LINQ to XML to extract the message and then I can display it in my console application. I call SaveChanges in a try/catch block, parsing and displaying the error message (see Figure 3). Figure 4 shows the results of the client-side exception.
Figure 3 Parsing and Displaying the Error Message Returned from the Service
Figure 4 Parsed Error Message Displayed in the Client
Now I’ll throw another wrench into the InsertPerson method. In addition to neglecting the LastName property, I’ll put too many characters into the IdentityCard property. Remember that this property was configured to have a MaxLength of 10:
Now the HandleException method will find two DataValidationErrors for the Person instance that the service attempted to update. The StringBuilder will contain a two-line message—one describing the problem with the LastName property and another to explain the problem with the IdentityCard property.
In the console application, this will be seen as a single message in the exception:
The LINQ to XML parser will then relay the message to the console, as shown in Figure 5.
Figure 5 Console App Displaying Multiple Errors for a Single Entity
Benefit from Validation Even When Disconnected
You’ve now seen how, using a simple set of requirements applied using Code First Data Annotations, you can capture and parse EF validation exceptions, return them to a client and, on the client side, parse the exception returned through HTTP. Whether you’re working with validation rules applied through property configurations or more complex rules that you can specify with IValidationObject or by overriding the ValidateEntity method, the EF will always return DbEntityValidationExceptions. You now know how to parse through those and can expand the logic to accommodate multiple objects, provide error messages containing more details, and handle them on the server or on the client as required by your application.
Because WCF Data Services returns OData, you can consume these services and leverage the validation today and practice so that you can be ready to do the same with future Metro-style technologies.
Julie Lerman is a Microsoft MVP, .NET mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other Microsoft .NET topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of the highly acclaimed book, “Programming Entity Framework” (O’Reilly Media, 2010). Follow her on Twitter at twitter.com/julielerman.
Thanks to the following technical expert for reviewing this article: Mike Flasko