Action Chaining is using the output of action A as input for action B. Applying this pattern to ASP.NET MVC projects is not trivial. This post is meant as a quick start.
Suppose you have the following scenario
- client places order in browser
- system redirects client to ~/Order/ThankYou
- system sends email to client with summary about order
- system shows summary of order to client
There are other usecases in which Action Chaining would be a valid pattern, for example in cases where you want to show the output of one action as PDF or as HTML depending on the required format. Apache Cocoon is based on this pattern.
The way to execute an action and capture the output in ASP.NET MVC involves some hijacking of the Response Stream and the RouteValues.
The Response Stream can messed with using a ResponseFilter. A response filter is a class that inherits from stream and takes a stream as constructor argument. The stream in the constructor is the original Response stream (or another filter, they can be chained). If you set the filter, the filter stream gets the writes and is supposed to pass the writes to the wrapped stream. Which is exactly what we won't do: we jealously keep the bytes to ourselves in a memorystream:
class BufferingMemoryStreamFilter : MemoryStreamControllers react on the routevalues to find the right action and view. Because we will be executing another action (perhaps on another controller also), the routevalues should be changed for the occasion and restored afterwards.
public BufferingMemoryStreamFilter(Stream wrappedStream)
// ignore the wrapped stream
The method that execute and capture an arbitrary action on an arbitrary controller is posted below.
string GetActionOutput(string controller, string action)Include this in a (base) controller or in a class that has access to the controller to be able to use it.
// hijack the response stream
var orgResponseFilter = HttpContext.Response.Filter;
var memoryStreamFilter = new BufferingMemoryStreamFilter(
HttpContext.Response.Filter = memoryStreamFilter;
// hijack routeData
var routeData = ControllerContext.RequestContext.RouteData;
var orgAction = routeData.Values["action"];
var orgController = routeData.Values["controller"];
routeData.Values["action"] = action;
routeData.Values["controller"] = controller;
var c = ControllerBuilder.Current
memoryStreamFilter.Position = 0;
using (var r = new StreamReader(memoryStreamFilter))
result = r.ReadToEnd();
HttpContext.Response.Filter = orgResponseFilter;
routeData.Values["action"] = orgAction;
routeData.Values["controller"] = orgController;
The code in this post is licensed under the Apache 2.0 license, which in practice means you have permission to use it.