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
-
Grab the validation nugets
-
Register the fubu validator
-
Conventions
-
Implement & register the validation Interceptor
-
Display errors in the view
-
Show a working exampleLet’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
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.