Skip to content

Redirect Enhancements

thofrey edited this page Apr 1, 2014 · 8 revisions

Table of Contents

  1. Overview
  2. Defining the Problem
  3. Solution
  4. Additional Usage Examples
  5. Redirect to a route
  6. Comments

This is a working document and is subject to change.

Comments regarding this M2SFP are welcome. Please leave your comments by using the form at the bottom of this entry.

This M2SFP feature has been filed under ticket (trac-wiki) #30 "enhancement: Add redirectEvent for filters/plugins (closed: fixed)").

Overview

Serving as the Controller layer in MVC Web applications written in CFML, Mach-II plays the role of a "traffic cop" in managing application flow and resources. Specifically, related to application flow, Mach-II offers a number of possible built-in solutions. Currently, Mach-II developers manage the flow of their applications in many ways, including:

  1. Issuing announceEvent() method calls in listeners, filters, and plugins
  2. Issuing redirect commands in the mach-ii.xml configuration file
  3. Manipulating the EventContext object from within Filters and Plugins

Defining the Problem

While these Mach-II constructs offer the right solution in many cases by allowing the developer to manipulate the event queue, the only one which actually issues a new http request is the redirect command. This hard redirect functionality can be quite important in many cases when developing Web applications. One example would be preventing duplicate form submissions by the application in the event of an unanticipated user refresh.

In order to solve this situation currently, many Mach-II developers have resorted to building event-handlers specifically for the purpose of redirecting the user to a new event. For example:

    // in the listener
    if(not result.hasErrors())
    {
        announceEvent("processSaveUser_redirect");
    }
    <!-- in mach-ii.xml config -->
    <event-handler event="processSaveUser_redirect">
        <redirect event="manageUsers" persist="true" />
    </event-handler>

This form of redirect does the job of forcing a completely new http request so that, if the user were to click refresh, the application will not attempt to process the save operation for a second time. On the downside, however, this can lead to the creation of many event-handlers which serve no purpose other than redirecting the user to a new event.

One solution to limit the amount of redirect only event-handlers which need to be added to the config file is to create a custom redirect filter which redirects to any number of possible events by searching for a relevant event argument and immediately thereafter issuing a <cflocation> command which points to the new event. While this solution does limit the number of redirect specific event-handlers in the mach-ii.xml config file, and force a completely new http request (thus eliminating duplicate form submissions via user refreshes), it does have other limitations.

Most notably, the solution mentioned above eliminates one of the most powerful features of redirects in Mach-II: persisting complex arguments across redirects. Persisting complex arguments across redirects is currently available when using the redirect command within the mach-ii.xml config file, but not easily accomplished when using a redirect filter with <cflocation>.

Solution

The proposed solution, slated for inclusion in Mach-II 1.8, falls in line with the theme of the release, which is "Simplicity." The solution would make a new method, redirectEvent() available to Mach-II developers within Plugins, Filters, and Listeners. This way, just as Mach-II developers have the ability to announce a new event by calling the announceEvent() and announceEventInModule() methods, they will also be able to perform a hard redirect (new http request) to a new event by calling redirectEvent() and redirectEventInModule(). This solution will eliminate the need for creating event-handlers whose only purpose is to redirect the user from one event-handler to another. Additionally, this solution will also allow the developer to handle data persistence across the redirect. The specifics of how Mach-II handles the data persistence will be provided via a series of arguments to the redirectEvent() and redirectEventInModule() methods

Note

Both redirectEvent() and redirectEventInModule() methods will obey any set event-mappings that have been defined so the flow of your application can still be seen from the configuration file.

For example:

    if(not result.hasErrors())
    {
        redirectEvent("manageUsers", "", true);
    }

The method signature for the new redirectEvent method:

Name Type Required Description
eventName string true the name of the target event for the redirect
args any false
(default: "")
You can pass in either a struct of arguments or a list of event args names from the current event to place in the url.
persist boolean false
(default: false)
whether or not to persist event data across the redirect
persistArgs any false
(default: "")
You can pass in either a struct of items or a list of event args to persist. If you pass in empty string all event args will be persisted.
statusType string (permanent, temporary, prg) false the HTTP status code to that is sent on the redirect (permanent = 301, temporary = 302 [default], prg [post-redirect-get] = 303)

The method signature for the new redirectEventInModule method:

Name Type Required Description
moduleName string false the name of the module to redirect to
eventName string true the name of the target event for the redirect
args any false
(default: "")
You can pass in either a struct of arguments or a list of event args names from the current event to place in the url.
persist boolean false
(default: false)
whether or not to persist event data across the redirect
persistArgs any false
(default: "")
You can pass in either a struct of items or a list of event args to persist. If you pass in empty string all event args will be persisted.
statusType string (permanent, temporary, prg) false the HTTP status code to that is sent on the redirect (permanent = 301, temporary = 302 [default], prg [post-redirect-get] = 303)

Warning - Immediate Ending of Requests

Redirects in Mach-II are no different than <cflocation>. When a redirect is encountered, the browser is immediately sent headers to request a new URL. Any events that are in the Mach-II event queue will not be processed because the CFML engine aborts the request after <cflocation>. This applies to the redirectEvent(), redirectEventInModule() and the <redirect> command.

Additional Usage Examples

    // in a filter

    var isLoggedIn = getSessionFacade().isLoggedIn();
    var proceed = false;

    if(isLoggedIn)
    {
        proceed = true;
    }
    else
    {
        arguments.event.setArg("message", "You need to login to access this feature!");
        redirectEventInModule("MachBlog", "showLoginForm", "", true, "message");
    }

Redirect to a route

If you are using routes for SES URL's, you can redirect to a route in the same way as event by using redirectRoute().

For example:

    if(not result.hasErrors())
    {
        redirectRoute("manageUsers");
    }

The method signature for the new redirectRoute method:

Name Type Required Description
routeName string true the name of the target route for the redirect
args any false
(default: "")
You can pass in either a struct of arguments or a list of event args names from the current event to place in the url.
persist boolean false
(default: false)
whether or not to persist event data across the redirect
persistArgs any false
(default: "")
You can pass in either a struct of items or a list of event args to persist. If you pass in empty string all event args will be persisted.
statusType string (permanent, temporary, prg) false the HTTP status code to that is sent on the redirect (permanent = 301, temporary = 302 [default], prg [post-redirect-get] = 303)

If you prefer to defined the flow of your application in the configuration file, you can do so by setting generic mapping using event-args. For example:

    <!-- in mach-ii.xml config -->
    <event-handler event="processSaveUser_redirect">
        <event-arg name="errorRoute" value="userEdit" />
        <event-arg name="successRoute" value="userProfile" />
    </event-handler>
    // in the listener
    if(not result.hasErrors())
    {
        redirectRoute(event.getArg("successRoute"));
    } else {
        redirectRoute(event.getArg("errorRoute"),event.getArgs());
    }

event args will not be persisted across redirects if MACHII_CONFIG_MODE is set to 1 in your Application.cfc

Comments

Comment by kurtwiersma on Sun 15 Feb 2009 10:37:42 AM EST

In order to stay consistent with the redirect command in 1.8 when used from the Mach II xml file, the new redirect method should allow expressions in their args and persistArgs arguments.

Comment by anonymous on Thu 19 Feb 2009 05:42:12 AM EST

This is the best. I've wanted this since 1.0!

Comment by anonymous on Mon 23 Feb 2009 09:10:01 PM EST

To be honest, I personally try to keep all of my "flow" defined inside of my mach-ii.xml, so my listeners would never announce new flow events. I use event mappings to define this flow, and my listeners only ever announce generic "flag" type events like "success" or "fail".

For instance, say I have a ProcessTaskComplete event. The listener for this even would itself announce "success" or "fail" depending on the result of the operation, and an event-mapping in the event-handler would handle the flow: <event-mapping event="success" mapping="showHome_redirect">, along with a separate "showHome_redirect" event-handler to handle the redirection.

My problem with your proposed solution is that it still does not solve the problem for a user like me. My listeners never announces flow events and thus I can not make use of a redirectEvent() method as it bypasses my event-mappings, who do the actual flow control. I love being able to keep flow out of my listeners and it would be fantastic if your solution also covered my situation.

Off the top of my head, the solution in my case would be an upgrade to the <event-mapping> command:

<event-mapping event="success" mapping="showHome" redirect="true" persistArgs...>

Now that would be cool. Any thoughts?

Comment by anonymous on Mon 23 Feb 2009 11:25:35 PM EST

I think this would also be something to consider adding. This way, for some developers, the flow of the application is still described within the xml. For other developers it also provides the ability choose to do their redirects from within their listeners.

Comment by peterfarrell on Tue 24 Feb 2009 09:41:37 AM EST

@anonymous on Mon 23 Feb 2009, Just like announceEvent() obeys event-mappings, the new redirectEvent() would obey event-mappings as well. Remember that announceEvent() can announce what I like to generic event names (such as pass or fail) that have been set via event-mappings or actually names of events that correspond to event-handlers defined in the XML. I think your argument falls on the assumption that event-mappings are not obeyed when a redirectEvent() method and that is a short coming of this document (which I will shortly fix) doesn't explicitly state that event-mappings would be obeyed.

For example this will work the same for you as you can do by having a concrete redirect event by using event-mappings and this new feature.

Event-Handler XML:

    <event-handler name="processTask" access="public">
        <event-mapping event="success" mapping="showHome" />
        <event-mapping event="fail" mapping="showTaskForm" />
        <notify listener="task" method="processSomeTask" />
    </event-handler>

In TaskListener (please excuse my abbreviated code):

    <cffunction name="processSomeTask">
        <cfargument name="event" />

        <cfset var result = getTaskService().processTask(arguments.event.getArg("taskBean")) />

        <cfif result.isSuccess()>
            <cfset redirectEvent("success", true) />
        <cfelse>
            <cfset redirectEvent("fail", true) />
        </cfif>
    </cffunction>

Comment by anonymous on Tue 24 Feb 2009 02:51:57 PM EST

Hey Peter. Brian Hendel here, the anonymous on Mon 23 Feb 2009 09:10:01 PM EST. You're right that I didn't realize that the redirectEvent method would obey event-handlers, so that is good.

To be honest, given this structure, my listeners would still have some awareness of the flow of the application, since the listener programmer would have to choose between announceEvent and redirectEvent. In my ideal world, the listener would know nothing at all about flow and would simply announceEvent the "flag" events like "success" without any contemplation to if it needs to be redirected at all. Even with peter's comment, it still feels like we're pushing flow into the listeners.

So would I use the redirectEvent method in my listeners to solve this problem if it was introduced? Yes probably. But would I much rather define this in my mach-ii.xml. Absolutely. Just feels right.

Just my 2c.

-Brian

Comment by peterfarrell on Wed 25 Feb 2009 05:01:55 AM EST

Just to play devil's advocate - you could say that using event-mappings and announceEvent() within a listener is hiding the flow of your application as well. Maybe I'm missing something in your point right now.

Really, redirectEvent() is getting by state-less nature of the HTTP paradigm and in essence is redirecting to continue the request on the other side (with a refresh of the URL in the browser).

Interesting idea about mixing in the redirect attributes into the event-mapping command however to me personally. It mixes what event-mapping is supposed to do with what redirect is supposed to do. Definitely something to thing about.

Comment by anonymous on Wed 25 Feb 2009 01:05:37 PM EST

Hey Peter. Brian here again.

"you could say that using event-mappings and announceEvent() within a listener is hiding the flow of your application as well". Well it is hiding it from my lisener layer, yes, but it is quite clearly defined in the XML, so yes that is exactly what I am looking for.

My first large M2 app used no exit-events, and my listeners themselves announced hardcoded events. Then for my next M2 app I started using exit-events, and passing in events to listeners dynamically. Now I am making use of exit-events for everything, and using event-mappings to handle the flow, and I am very happy with the result so far. The only think that feels dirty to me is when listeners have to announce events at all (other than flag events). That is why I would always push for flow definition to exist solely in the XML.

As a rule, I know that I never need to dive into the listeners. If I decided that I needed to do a redirect, I'd simply make a change in the XML and leave the listeners clean and dumb. I also use resultArg to keep the liseners from having to populate the event object whenever possible, which is great abstraction too.

So, even though it might dilute what the event-mapping command is, I still vote for some kind of redirection to be defined there. Or a new redirect-event-mapping command would be fine too.

Cheers!

-Brian

Comment by anonymous on Wed 25 Feb 2009 01:06:31 PM EST

Weird that this site always shows my posts as Anon even though I have a value in the "Your email or username" field. Maybe I need to be logged in.

-Brian

Comment by brianfitzgerald on Tue 03 Mar 2009 05:07:00 PM UTC

Hey Brian, you say that you're ok with your listeners announcing flag type events (based on event-mappings) like "success" or "fail" right?

announceEvent("success");

With that said, what's the difference between that and doing a redirect the same way?

redirectEvent("success");

Either way, the next event in the application flow is abstracted out of the listener and into the .xml where it belongs.

Clone this wiki locally