Multiple views in OpenRasta, and delving into the pipeline
This entry may be outdated. For the latest updates on openrasta, see www.openrasta.com.
I just had an idea to clean-up my resource definitions. I could’ve gone straight in the OpenRasta code and add just this small additional feature I really want, but OpenRasta is all about extensibility and composition, and I’m a few days away from the beta 1 release. So I thought I’d put to test the API from the outside, and tell you a story about how to define multiple views on a resource with the WebForms Codec. I may move this entry over to the documentation part of the project later on.
--
In OpenRasta, you need to define your resources individually. A resource in OpenRasta is simply a class you create that handlers will deal with. Each resource may have any number of URIs, and any numbers of codecs. A codec is a piece of code responsible for rendering a codec, and on this project I use the WebForms codec.
ResourceSpace.Has.ResourcesOfType<UserRegistration>()
.AtUri("/registration")
.HandledBy<UserHandler>()
.AndRendededByAspx("~/Views/User/New.aspx");
You can see here the registration for my UserRegistration resource. It defines where to find it, what handler is using it, and a page to render it.
Whenever a user has successfully registered, it’s quite usual to return them to a page welcoming them to the world of splendor that you’ll provide them, now that you have all their previous personal details. And it usually contains the exact same information that you collected previously. And that’s where it can become a bit hairy.
The initial (and in many scenario sane) reaction is to create a separate resource / handler / view couple to render our Thanks page. This is indeed accurate: the thing that I serve to the client is indeed a document with a “Thank you” message, complete with blink tags.
Another point of view, and the one I usually favour, is to consider those various elements as different views on the same resource. From a ReST point of view, each of those views will be a resource in its own right, but from a programming perspective OpenRasta will treat them as being the same entity to operate against.
So how do we solve the conundrum? The first piece of the puzzle is to use the capacity baked in the WebForms view engine to define multiple views. We can change the registration ever-so-slightly by using the generic notation for registering the view.
ResourceSpace.Has.ResourcesOfType<UserRegistration>()
.AtUri("/registration")
.HandledBy<UserHandler>()
.AndTranscodedBy<WebFormsCodec>(new
{
index = "~/Views/User/New.aspx",
thanks= "~/Views/User/Thanks.aspx"
});
The registration looks very similar. The AndRenderedByAspx method you saw earlier is a shorthand for the full notation using the AndTranscodedBy<T> which lets you plug-in many different codecs per-resource.
The anonymous type itself defines view names that will be passed to the WebForms codec. The question is, how do we get the framework to choose those views?
By default, that codec always uses a view named index (or default or get) for the default view to select. Another approach is to use uri path segments. If I typed the URI http://localhost/registration;thanks the thanks bit is called a path segment, and is always separated by a semi-column. If you go and try that now on your OpenRasta website, it won’t work (something about not enabling features don’t want). You need to go and enable it by adding a UriDecorator.
ResourceSpace.Has.UriDecorator<PathSegmentAsRendererUriDecorator>();
URI decorators in OpenRasta are modules that lets you manipulate uris before a request is processed. It’s used for integrating various features, such as file extensions, localized URIs, and whatever else you may think of. And the reason the PathSegmentAsRenderer is called the way it is is historical, and before this blog entry I never realized that it should really be updated to PathSegmentAsCodecParameter. Expect to see that change in the trunk over the weekend. (Codecs used to be called Renderers when I was working on the codebase that preceded OpenRasta).
So, all is good, we can now go to /registration;thanks and see the Thanks.aspx page rendering a resource. But what if you didn’t want to use path segments, and define two different URIs yourself?
.AtUri("/registration")
.AndAt("/registration/{emailAddress}/complete").Named("thanks")
.HandledBy<UserHandler>()
.AndTranscodedBy<WebFormsCodec>(new
{
index = "~/Views/User/New.aspx",
thanks= "~/Views/User/Thanks.aspx"
});
We’re going to need to leverage a few other features of the configuration API.
First is the support for named URIs. If one resource can have multiple name, that feature lets you give it a friendly name. It is mostly used to decorate a handler method to help select which overload of a method gets executed, but it can also serve our purpose here quite well.
We’re going to plug in the OpenRasta pipeline, and do the same thing the PathSegmentAsRendererUriDecorator type does: add a string that will be sent to the codec to help it choose. To do this, we’re going to implement a class implementing IPipelineContributor. You can find the code at the end, as an example.
This exposes a few specificities of OpenRasta. The first one is the pipeline model itself. Whenever you want to integrate deep within the framework, and modify the way things are processed, you need to tell OpenRasta which components you depend on in the execution pipeline. That’s what the ExecuteBefore and ExecuteAfter methods do. Out of the box, there’s 18 contributos to choose from. Each component does a tiny bit of the request processing, and enrich the data until it’s been turned back into a byte stream. The execution order is non-deterministic: OpenRasta will always honour your requests to be before or after someone else, but doesn’t guarantee anything beyond that.
Another aspect is the fact that pipeline contributors are the only components in the system to be singletons. They will be loaded one and never be discarded. That’s why they take a dependency on the ICommunicationContext god object. It’s the only ever Context object you’ll find in OpenRasta. Most of the interfaces in the system have flat methods with a well-known number of parameters. Just the minimum amount of information needed for a component to execute, everything else is handled by dependency injection.
Overall, when you’re an IPipelineContributor in OpenRasta, your’re like god, you run at the kernel of stuff. And because it’s a pipeline model, any of your actions *will* have consequences on everyone else. There’s many other less low-level extensibility points in OpenRasta, so when you can, use them!
The code
public class UriNameAsCodecParameter : IPipelineContributor
{
public void Initialize(IPipeline pipelineRunner)
{
pipelineRunner.ExecuteBefore<ResponseEntityWriter>(AddUriNameToCodecParameters);
}
public PipelineContinuation AddUriNameToCodecParameters(ICommunicationContext context)
{
if (context.PipelineData.SelectedResource.UriName != null)
{
var newCodecParameters = new List<string> { context.PipelineData.SelectedResource.UriName };
if (context.Request.CodecUriParameters != null)
newCodecParameters.AddRange(context.Request.CodecUriParameters);
context.Request.CodecUriParameters = newCodecParameters.ToArray();
}
return PipelineContinuation.Continue;
}
}
The registration (until I’ve revisited that part of the code, cause I sure don’t like it. It’s an outstanding task)
DependencyManager.GetService<ITypeRepository<IPipelineContributor>>().Types.Add(typeof(UriNameAsCodecParameter));