Since the beginning of the RIA Services project, a primary tenet was to propagate validation rules defined on the server up to the client, integrating the validation directly into the Silverlight UI. This came to fruition with RIA Services v1.0 and you can in fact apply an attribute to your model on the server and automatically get instant client-side validation of data entry on even the simplest of Textbox controls. In fact, some validation rules are inferred from your data model directly. This is a feat that many other frameworks have attempted to accomplish, but I believe RIA Services has one of the most successful implementations of the concept. In this post, we’ll examine how attributes declared on your model are propagated to the client. By understanding what is going on behind-the-scenes, you’ll be able to better leverage validation capabilities.
Code Generation Overview
The entire RIA Services product is centered around its code generation capabilities. You create DomainService classes on the server and RIA Services generates Entity classes within your Silverlight project that mimic your model on the server. You might not ever see the generated code, or even recognize that it’s happening, but it is. Every time you build your solution, RIA Services generates the classes in your Silverlight project that give you the ability to create, read, update, and delete data, and invoke custom operations within your domain services.
I learn best about these types of topics by examining the results—let’s do just that. I encourage you to walk along with me as we take a brief tour through RIA Service’s generated code.
- In Visual Studio 2010, create a New Project and select the Silverlight Application template from the Silverlight category. I will be illustrating C#, but if you primarily use VB, feel free to choose that language for your project. After entering your project name (“RudeValidation” for me), click OK and you will get the “New Silverlight Application” dialog.
- In order to utilize WCF RIA Services in your Silverlight application, you’ll need to choose to host the Silverlight application in an ASP.NET Web Application Project, Silverlight 4, and Enable WCF RIA Services. After setting those options, click OK and two projects will be created in your solution, one with the name you specified and another that is suffixed with .Web.
- We need to create a model on the server. This could be done through Entity Framework, Linq to SQL, NHibernate, or any other data access library. In our case, we will just create a Plain Old CLR Object (POCO) by creating a new class in our project. Right-click on your Web project and choose Add –> Class. Name the file “Meeting.cs” and click Add.
- Our class will be quite simple, but it will require a reference to an additional assembly: System.ComponentModel.DataAnnotations. After adding that reference, we can create our model with the following code:
using System;
using System.ComponentModel.DataAnnotations;
namespace RudeValidation.Web
{
public class Meeting
{
[Key]
public int MeetingId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public string Title { get; set; }
public string Details { get; set; }
public string Location { get; set; }
public int MinimumAttendees { get; set; }
public int MaximumAttendees { get; set; }
}
}
- Our next step is to create a Domain Service Class that will expose this Meeting entity to the client, thus triggering code generation to occur. Right-click on the Web project and choose Add –> New Item… again. This time select “Domain Service Class” from the Web templates. After naming the class “MeetingService.cs” and clicking Add, you will get the “Add New Domain Service Class” wizard. We’re creating an empty Domain Service class, so just click OK.
- By adding a single method to our MeetingService, we can gain access to our Meeting entity in our Silverlight project, and this will allow us to better understand the results of the code generation and continue on to our exploration of validation attribute propagation. Set your MeetingService.cs file to have the following code, and then build your solution:
namespace RudeValidation.Web
{
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
[EnableClientAccess]
public class MeetingService : DomainService
{
public IEnumerable<Meeting> GetMeetings()
{
return Enumerable.Empty<Meeting>();
}
}
}
At this point, we have a new Silverlight project, an entity class defined on the server, a DomainService class that exposes it to the client (thanks to the [EnableClientAccess] attribute), and our solution is building. It isn’t obvious, but RIA Services is now generating code within the Silverlight project each time you build. The generated code is not automatically visible within the Silverlight project, but we can navigate to it easily.
- Select the Silverlight project in the Solution Explorer;
- Click on the “Show All Files” button, or as Scott Hanselman calls it, the “Don’t Lie To Me” button.
- You’ll see a .Web.g.cs file in the folder – this is what RIA Services has generated for you. Let’s crack this bad-boy open and see what we have!
You will notice that the generated code includes a handful of types: WebContext, Meeting, MeetingContext, MeetingContext.IMeetingServiceContract, and MeetingContextEntityContainer. Our focus will be on the Meeting class that is generated. There are a few notable details for this class:
- It’s a partial class, which means you can create additional files that augment this class beyond what was generated. You don’t want to edit this .g.cs file, as the next build would overwrite your changes with the re-generated code. If you expand the “Extensibility Method Definitions” region, you’ll see that there is a series of partial methods that you can implement in your partial class file.
- The class derives from Entity, which is a framework class in RIA Services, and it provides loads of functionality that makes RIA Services tick.
- Every property setter has significant logic, such as:
/// <summary>
/// Gets or sets the 'Title' value.
/// </summary>
[DataMember()]
public string Title
{
get
{
return this._title;
}
set
{
if ((this._title != value))
{
this.OnTitleChanging(value);
this.RaiseDataMemberChanging("Title");
this.ValidateProperty("Title", value);
this._title = value;
this.RaiseDataMemberChanged("Title");
this.OnTitleChanged();
}
}
}
We will examine this property setter logic in a subsequent post. But at this point, we can see how properties created on our model on the server translate into Entity classes in our Silverlight project, and how every property declared on the server is represented on the client. Our next step is to apply some standard validators to our model and review the effects this has on our generated code.
Validation Attribute Propagation
Now that we’ve seen that RIA Services is performing code generation for our entities, we recognize that validation attributes applied on the server must somehow get propagated to the client, in the Silverlight project’s rendition of the entity class. Let’s again study this by looking at the results of the code generation. Revisiting the post on Standard Validators, let’s apply the same validators we’d used in there. And in addition to the validation attributes, we’ll also use the [Display] attribute.
public class Meeting
{
[Key]
public int MeetingId { get; set; }
[Required]
public DateTime Start { get; set; }
[Required]
public DateTime End { get; set; }
[Required]
[StringLength(80, MinimumLength = 5)]
public string Title { get; set; }
public string Details { get; set; }
[Required]
[RegularExpression(@"\d{1,3}/\d{4}",
ErrorMessage = "{0} must be in the format of 'Building/Room'")]
public string Location { get; set; }
[Range(2, 100)]
[Display(Name = "Minimum Attendees")]
public int MinimumAttendees { get; set; }
[Range(2, 100)]
[Display(Name = "Maximum Attendees")]
public int MaximumAttendees { get; set; }
}
The only difference between this code and what was used in the Standard Validators post is that we’re no longer using the ErrorMessageResourceType/ErrorMessageResourceName properties on the StringLength validator. We’ll cover how to use resources for error messages in a later article.
/// <summary>
/// Gets or sets the 'Title' value.
/// </summary>
[DataMember()]
[Required()]
[StringLength(80, MinimumLength=5)]
public string Title
{
get
{
return this._title;
}
set
{
if ((this._title != value))
{
this.OnTitleChanging(value);
this.RaiseDataMemberChanging("Title");
this.ValidateProperty("Title", value);
this._title = value;
this.RaiseDataMemberChanged("Title");
this.OnTitleChanged();
}
}
}
That’s pretty straight-forward, isn’t it? The attributes we applied on the server have been discovered and generated on the client, in a 1:1 manner. Here’s what’s happening:
- RIA Services finds all classes in your Web project that derive from DomainService;
- Your entity types are then discovered by convention, recognizing that GetMeetings() returns an IEnumerable<Meeting>, which implies that you want to be able to work with your Meeting class on the client;
- The Meeting class is then reflected upon, and all properties are catalogued. As long as the property is a type that can be serialized by RIA Services, and it doesn’t have the [Exclude] attribute on it, it will be included in the Silverlight version of the class.
- For each property, all of the attributes are enumerated. For every attribute, the attribute type is sought in the Silverlight project. It can be found a couple different ways:
- The attribute class is defined within a .shared.cs file in the Web project, which then triggers RIA Services to automatically copy this file to the Silverlight project (within the Generated_Code folder) – This is called a Shared Type.
- The attribute class is defined within a class library that is referenced by the Web project, and the Silverlight project references a Silverlight class library that also defines the class (in the same namespace). Note that this can be achieved either by implementing the class in both class libraries, or by using a WCF RIA Services Class Library and utilizing the .shared.cs file name convention within the Web class library.
- For each attribute that is found to exist on both the server and the client, the attribute is verified to meet construction requirements:
- There must be a public constructor defined within the class (the base constructor is not sufficient) that has the same signature as the constructor used on the Web project;
- Every public property on the Web version of the attribute must have a corresponding property in Silverlight with an accessible setter.
In other words, so long as the exact same attribute declaration can be used on both the server and client, the attribute will be propagated. The biggest exception to this is that the Silverlight version of the attribute cannot exist as a standalone class within the main Silverlight project—it must be either referenced or included through the .shared.cs file name convention.
Custom Validation
When using the [CustomValidation] attribute, there are some additional constraints that you must abide by. Because your attribute will be propagated to the client as-is, the ValidatorType and Method specified for the custom validation must also be available on the client. This is most often achieved using the .shared.cs file name convention, as mentioned in the Custom Validation Methods post. However, this can also be done using the class library approach.
If you have a [CustomValidation] attribute that does not work properly on the client, you’ll either get a build error stating that the type cannot be found, or you will get a runtime error saying the validation method cannot be found. It should be pretty apparent either way, so if you hit those scenarios, be sure that your validator type and method are discoverable in your Silverlight project.
Validation Execution
Now that your validation attributes are propagated to the Silverlight project, the client UI can now execute your validators without having to go back to the server. RIA Services and Silverlight use the static Validator class in the System.ComponentModel.DataAnnotations assembly and namespace to perform this validation. You can read about how the Validator class works in my post from last year. For specifics on when and how this validation occurs, you’ll have to keep following along, as we’ll cover that topic in my next post!
RIA Services Validation Recap:
This post is part of an ongoing series on RIA Services Validation. We have now learned how the attributes you declare on the server get propagated to your client code through RIA Services code generation. Hopefully, you now feel empowered to explore new ways to apply validators to your model. In fact, I implore you to look through the generated code for the other validators we applied, along with the [Display] attribute that we included in our model. Then, try deriving from ValidationAttribute in both a .shared.cs file as well as in a WCF RIA Services Class Library. As you make changes, study the effects on the generated code.
Here are the other posts in this series:
[9/6/2011] The source code for everything shown during the series is available on GitHub.
Digging Deeper
As mentioned, my next post will go into details on when and how RIA Services performs validation. We’ll then continue to explore more details of the RIA Services validation features over many other posts that will cover how to perform entity-level validation, how to leverage ValidationContext, and more.