FubuValidation in Open Rasta
Open Rasta is a popular
.NET on OSS web framework that lets you choose your own validation mechanism. Fubu Validation is a stand-alone
OSS validation framework that lets you “have validation your way”. I think you
can see where this is going….
In this post I’ll show you how to get a basic, conventional
validation strategy setup in Open Rasta. To keep the post short I’ll keep
things to a bare minimum — but enough to be useful. I’ll hopefully provide more
detailed information in another post.
Outline
1. Grab
the validation nugets
2. Register
the fubu validator
3. Conventions
4. Implement
& register the validation Interceptor
5. Display
errors in the view
6. Show
a working example
Let’s go
Grab the nugets
Assuming you have an open Open Rasta project,
“install-package fubuvalidation” to your web project.
Conventions
Validation should occur when the http method is post and the
object being bound to’s type name ends with “inputmodel”. If validation fails
we want to go back to the get request so the user can correct their errors.
We will also apply the one model –in-one-model-out pattern –
so there will only ever be one model to validate.
If you have other conventions, don’t worry. I’m just using
these for this example.
Register the Fubu Validator
We now need to register a validator which can be injected
into our upcoming validation component. In the most basic fashion, add the
following to your IConfigurationSource implementation. Use namespaces from the
fubu package:
ResourceSpace.Uses.Resolver.AddDependencyInstance<IValidator>(Validator.BasicValidator());
Implement & Register the Validation Interceptor
Think of an interceptor as akin to an action filter in ASP.NET
MVC — an AOP-style hook. We’re now going to add one that is called just before
the method in a handler is executed. Here it is:
public class ValidationInterceptor : OperationInterceptor{private readonly IValidator validator;private readonly ICommunicationContext context;public ValidationInterceptor(IValidator validator, ICommunicationContext context){this.validator = validator;this.context = context;}public override bool BeforeExecute(IOperation operation){var input = operation.Inputs.FirstOrDefault();if (!ShouldValidate(input)) return true;var model = input.Binder.BuildObject().Instance;var validationResult = validator.Validate(model);if (validationResult.IsValid()) return true;context.PipelineData.Add("Validation", validationResult.ToValidationErrors());context.OperationResult = new OperationResult.BadRequest{ResponseResource = Activator.CreateInstance(GetClassThatInherits(model.GetType()))};return false;}private bool ShouldValidate(InputMember input){return input != null&& input.Member.Type.Name.ToLower().EndsWith("inputmodel")&& context.Request.HttpMethod.ToLower() == "post";}private Type GetClassThatInherits(Type type){return Assembly.GetAssembly(type).GetTypes().Where(t => type.IsAssignableFrom(t)).Where(t => t != type).Single();}}
Firstly notice I’ve inherited from the default operation
interceptor. Secondly check out the IValidator in the constructor — dependency
injection will take care of that
Now look in BeforeExecute() — the “input” variable refers to
the model we are binding to. We then check whether we should validate model
inside ShouldValidate() — which applies the rules for the conventions mentioned
earlier.
The model is then validated with any errors being put into
the context’s data (which fubuvalidation provides a nice way of doing).
Finally, if validation did fail — we need to adhere to the
convention of returning to the corresponding get action. Open Rasta will
redirect to the get if we give it an OperationResult whose ResponseResource
property is an instance of the model returned by the get action.
How can we get one of those though? Well, the model for get inherits
the model that is posted (input model always inherits view model). So we just
need to reflect over the model and find the only class that inherits from it.
I’ve done this as you can see in GetClassThatInherits().
You can then register the interceptor like so:
ResourceSpace.Uses.CustomDependency<IOperationInterceptor, ValidationInterceptor>(DependencyLifetime.Transient);
Display Errors on the Page
As minimal as I can get:
<%var c = Resolver.Resolve(typeof(ICommunicationContext)) as ICommunicationContext;var errors = c.PipelineData["Validation"] as ValidationError[];%><% if (errors != null && errors.Any()) { %><div id="errors"><ul><% foreach (var e in errors) { %><li><%:e.field + ": " + e.message%></li><% } %> </ul></div> <% } %>
Grab those error messages back out of the communication
context — and display them if there are any. I insist you do this boilerplate
code in every page just for the love of the web forms view engine’s gator tags.
Working Example
Here’s one I made earlier:
Model
public class ReleaseInputModel{[Required]public int ArtistId { get; set; }[Required]public int? Version { get; set; }[Required]public string Type { get; set; }[Required]public string Title { get; set; }[Required]public string ImageUrl { get; set; }}
Notice the required attributes that live in FubuValidation
(above)
Handler Method
[HttpOperation(HttpMethod.POST)]public OperationResult Add(ReleaseInputModel model){var release = new Release{ArtistId = model.ArtistId,ImageUrl = model.ImageUrl,Title = model.Title,Type = model.Type,Version = model.Version.Value};session.Store(release);return new OperationResult.Created {ResponseResource = release};}
When Validation Fails…….
Here’s what happens when I leave all the fields empty and
submit the form:
Next Time…..
I kept this post minimal, but before I could use this
validation strategy I would need to:
·
Make the conventions pluggable
·
Improve error displays
o
Reusable helper method
o
In-line field errors
·
Apply the posted values back onto the page when validation fails
·
Better/custom/conventional error messages
·
Ajax example
·
Client rules
All of these things are well within reach — when I get
chance to implement them I’ll be sure to blog them too.