Unzip all of these in 1 dedicated empty folder:
Introduction
RIA Services is a rather new kid on the block, and because of that it is pretty hard to find relevant information and code samples about how to set up a proper environment for building business applications around it (which is probably the main intent for the whole framework). Even more so if on your previous projects you got hooked on NHibernate and all the powerful things and tools around it and just HAVE to have all that in it. One of RIA's key points was it's data source agnostic approach, which basically meant you can hook up practically anything as a data source. Which is basically true. And you also get a nice top to bottom validation in the package. Which is also "basically" true. But how about some sample code, preferably with using Fluent nhibernate, orm validation, and some automapping just to add to the flavour? Well, until now, you'd be pretty much out of luck:).
The Validation Issue
One of the key issues I wanted to address is the validation pipeline. As some of you are aware, there's a rather nice support for top to bottom validation through using System.ComponentModel.DataAnnotations
namespace. It can be found both as a .NET and Silverlight assembly.
By using this assembly, we get nice validation on the client side and on the server side. And if we would be using Entity Framework or Linq2Sql, we would also get data layer validation. The only thing we need to do is decorate our business objects with correct attributes. As an example, we will use the Plate
class:
public class Plate
{
[Key]
public virtual int Id { get; set; }
public virtual string Name { get; set; }
[CustomValidation(typeof(PlateDescriptionRule),"Validate")]
public virtual string Description { get; set; }
[Required]
[Range(0, 10000)]
public virtual int? CalorieCount { get; set; }
public virtual int? RestaurantId { get; set; }
[Include]
[Association("RestaurantPlate", "RestaurantId", "Id", IsForeignKey=true)]
public virtual Restaurant Restaurant { get; set; }
}
The focus of the sample is on CalorieCount
and Description
properties. In the previous code snippet, they've been described using attributes. As previously stated, this works well for solutions based on Microsoft tools. But what if we wanted to do some validation inside data layer and use NHibernate instead? First thing we would have to do is choose the correct validation framework. The one that plays best with NHibernate is NHibernate Validator. It has very good integration with the ORM and I could pretty much do all sorts of things with it. NHibernate Validator has its own set of attributes, but putting another set of similar attributes on the same class would end up looking ridiculous:
public class Plate
{
[NotNull] [Required] [Range(0, 10000)]
public virtual int? CalorieCount { get; set; }
}
This is also one of the biggest reasons I don't like using attributes. In today's business application world, there's so many different layers and concerns that need to be addressed that just slapping attributes over model gets really old really fast, and is not very maintainable. So if you had 3 different systems which use attributes on the same business model, you'd have a complete mess about figuring out which goes where. Also, there's just not enough flexibility with using the model and often you have to resort to "magic strings". What's a magic string? A good example of one of those is the custom validator implemented over the description property:
[CustomValidation(typeof(PlateDescriptionRule),"Validate")]
public virtual string Description { get; set; }
So this attribute is telling us that we should use static
"Validate
" method from PlateDescriptionRule
class which also has to have 1 argument that has the same type as the Description
property. I know, it takes some time for it to settle down in your brain. Not very refactoring-proof also.
Luckily, RIA and NHV are both open for an alternative approach.
Combining the Validation
Some of you may ask why even go through the trouble of implementing NHV at all. There's validation on the server side and on the client side, so basically no data can enter the server without being validated first. Well, as you know, not all data goes straight to the database when communicating it to the server. There's often some preprocessing done before saving it, and during that preprocessing, some valid data can get to become invalid. This would usually be a design flaw, but it's still a flaw which needs to be addressed. Also, you could use some direct approach to database using the same model assembly and nhibernate classes, totally avoiding the Silverlight annotations and still reap the benefits of validation. In any case, I find it to be a good design decision for just a little extra work. Also, there are some cool gains in the whole thing in terms of type safety and refactoring which become obvious due to NHV's more robust model which overrides the RIAs.
The sample code itself is pretty much what everyone needs out of all of this, but I wanted to comment on a few design decisions. It's the basic example of using NHibernate and RIA together which you can find on RIA site, updated to latest versions of NHibernate and other tools (all off the trunk and compiled) and the same goes for NHibernate.
The cornerstone of the solution are fluent interfaces for RIA and NHV.
As you can see from the RIA sample, the Metadata
class is used as a vessel for associating attributes to properties. The great thing about it is that the class is not inheriting from any other class, i.e., it's a standalone. It's a very good job from Nikhil though I did some refactoring to the original concept to suit my way of coding (introduced an interface Imetadata
, few renamings, rearranging responsibilities and such....). NHibernate validator's Fluent interface is based on IValidationDef
class from which you have to inherit to define associations between properties and attributes. They are both generic, so the logical thing to do is to make Metadata
class inherit from IvalidationDef
. That way, both RIA and NHV can use the same class for validation definition and MetadataClass
holds attributes for both RIA and NHV. The only thing to do is somehow declare corresponding attributes, and for that purpose we use an IcombinedRule
interface:
public interface ICombinedRule
{
Attribute NhAttribute { get; } ValidationAttribute DNetAttribute { get; }
string ErrorMessage { get; }
}
So if we would look at the aforementioned example for „not null“ property validation, we would define it like this:
public class RequiredRule:ICombinedRule
{
public Attribute NhAttribute
{
get { return new NotNullAttribute(); }
}
public ValidationAttribute DNetAttribute
{
get { return new RequiredAttribute(); }
}
}
And consume it like this:
public class PlateMetaData:Metadata<Plate>
{
public PlateMetaData()
{
AddMetadata(new RequiredRule(),errorMessage);
this.Validation(x => x.CalorieCount).Required();
}
}
There's also some Fluent interface definition involved here, I showed both ways of how you can add a „not null“ validation. The point of the whole exercise is having one place of validation definition and multiple validations across all layers with corresponding messages.
Custom Validation
One of the bigger gains in introducing the merged validation model for me was the custom validation. The way RIA does it, as previously explained is pretty error prone. Though mistyping method name or defining it wrong will invoke a compile-time error, it's still not satisfactory because of refactoring. NHV's approach for me is much more reliable. It asks you to define your custom attribute, then define the validator you are going to use for that particular attribute, which has to implement a certain interface.
[ValidatorClass(typeof(PlateDescriptionRule))]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class PlateDescriptionAttribute : Attribute, IruleArgs
{
public string Message
{
get { return PlateDescriptionRule.Message; }
set
{
if (value == null) throw new ArgumentNullException("value");
}
}
}
And PlateDescriptionRule
class would be like this:
public class PlateDescriptionRule:Ivalidator
{
public static string Message = "Valid descriptions must have 5 or more words.";
public static ValidationResult Validate(string description)
{
if (description != null && description.Split().Length < 5)
{
var vr =
new ValidationResult(Message, new string[] { "Description" });
return vr;
}
return ValidationResult.Success;
}
public bool IsValid(object value,
IConstraintValidatorContext constraintValidatorContext)
{
return Validate((string)value) == ValidationResult.Success;
}
}
And to apply the whole thing, we would just do the following:
public PlateMetaData()
{
this.Validation(x => x.Description).Custom<PlateDescriptionAttribute>();
}
So what we actually did here is used NHV's custom validation mechanics to provide validation for RIA services! Well, not really:) but with some clever usage of conventions and composition the solution becomes much more manageable. RIA's way of handling custom validation was to just provide the class and the name of a static
method we want to use for validation. Which could be used to have 1 big class and lots of static
validation methods in it, which all have to be defined in string
s. This way, we are enforcing a creation of separate validation classes for every custom validation we want to define. The only convention we have to uphold is to have a static
„Validate
“ method inside every such class for RIA to use.
I'm a big proponent of enforcing good practices through code, i.e., not giving an alternative approach to the best one. This way the developer is FORCED to use the type safe way of defining a custom validation, and to uphold to certain given conventions. This is where, in my opinion, Microsoft has a lot to learn from community solutions.
Sweetening the Deal
To top it off, I added Fluent configuration of the whole thing, which you can find in RestaurantRepository
class, Automapping with conventions instead of those ugly xmls,
NHibernate 3.0 Alpha2, compiled Fluent NHIbernate, compiled NHibernate Validator and a bunch of other stuff. I'm not a big proponent of automapping (we use code generation instead of it for mapping), but it's a nice example of how to set it up for smaller projects. What's more, validation through using attributes still works if you choose, for some reason, to use attributes on some models and fluent on others.
All in all, there's a lot to see in this project even if you're not that interested in this sort of validation and choose just to stick with the Microsoft way of doing things.
The Caveats
Ok, there are some problems with the approach, the most obvious one being the „Nhibernate-Silverlight“ project. What's with that? Well, for client –side custom validation, you also have to share the validator code, which means you have to have all the interfaces defined which validators are implementing. Basically that project does nothing except make the whole thing compile without errors. Those problems could be avoided by introducing some additional layers of abstraction, but I decided to leave it at that so you're aware of what could be the potential problem. Realistically, I never talk directly to NH or NHV, I always wrap them in custom classes for serious projects, plus there's also some dependency injection involved to avoid some problems I faced in this project.
Conclusion
RIA services and Silverlight really bring a lot to the table for business software developers, abstracting away a lot of the plumbing and problems you would have to otherwise overcome in a really painful way. But what's even better is that though it does a lot, it doesn't restrict you from doing a lot of stuff differently and on your own terms, validation being the prime example. Nhibernate 3.0 is coming with LINQ provider integrated out-of-box, which is a critical feature for a seamless integration with Ria services, and it's growing in power with every day. The great thing about it, you can run your project practically off the Nhibernate trunk without giving it too much thought. This would be just the tip of the iceberg of how you can adjust Ria and Nhibernate to suite your needs in full, and I'm also in the process of discovering the potential myself.
It's my first article, so any comments, criticisms or suggestions are more than welcome,
hope you enjoy it:)
History
- v1.0 Just to get it out there:)