Back Forum Reply New

Redirect after post

Hi there.

I've just started working with WebFlow.  So far I am very happy with it, but I've run into one problem.  The sites that my company work on generally need to have very simple interfaces that serve users with limited understanding of the internet.  Redirect after post has been a necessity to allow users to navigate these sites using the back button on their browser.  I've already got continuations working, so my question becomes... is redirect after post possible with webflow after an action is executed?  As far as I can tell, if I respond with a RedirectView my choices are to return a JSP outside of the flow, or redirect back into the FlowController, and I'm not exactly sure how to do either while maintaining my _flowExecutionId.  Can anybody help me out with this?  I would greatly appreciate it.

From the recently updated spring-webflow.dtd :-)

lt;!--
The name of the view to to render when this view state is entered.

This value may be a static view name or even a direct pointer to a view template.  For example:   quot;priceFormquot;, or quot;/WEB-INF/jsp/priceForm.jspquot;

This value may also be qualified with a prefix to denote a specific (possibly custom)
ViewSelector strategy.  Specifically:

Use of the quot;redirect:quot; prefix triggers creation of a RedirectViewSelector which will request
a redirect to a specific ucl.  For example:   quot;redirect:/home.htmlquot;

Redirect query parameters may also be specified using ${expressions} that evaluate against
the request context.  For example:   quot;redirect:/thankyou.htm?confirmationNumber=${flowScope.order.  confirmation.id}quot;

Use of the quot;bean:quot; prefix will plug in the custom ViewSelector implementation with that bean id:   quot;bean:myCustomViewSelectorquot;

The exact semantics regarding the interpretation of this value are determined by the
installed TextToViewSelector converter.

Note when no view name is provided, this view state will act as a marker state. A marker
view state does not request the rendering of a view, it only pauses the flow and returns control
the client. Use a marker view state when another state is expected to generate the response.
--gt;

Okay, that definitely helps, but I still have some issues with the ability to redirect.

First off, it seems to me that the best way to handle this is to redirect back to the controller, passing it the current _flowExecutionId.  Just wondering if anyone else has figured out a better way to handle this.

Secondly, I am currently using an XmlViewResolver, so my views look something likeCode:
lt;view-state id=quot;listquot; view=quot;view.listquot;/gt;
I don't believe that I can pass request parameters to the redirect view that view.list represents this way.  If I'm not mistaken I've tried this before and something along the lines ofCode:
lt;view-state id=quot;listquot; view=quot;view.list?_flowExecutionId=${requestScope.flowExecutionId}quot;/gt;
Would give me an error that the view could not be found.  Am I wrong there?

And lastly; I have a lot of flows that are conceptually similar.  They start by presenting a list of items to a user.  Users then select items to modify them or click a button to create a new item.  After an item is created or modified (the relevant post requests) the user is returned to the list (start-state).  What I'm getting at here is that what makes the most sense to me would be to do something along the lines of:Code:
lt;transition on=quot;successquot; to=quot;redirect:listquot;/gt;
That way I wouldn't have to redefine the list state.  I'm getting the feeling that I need to look into the ability to define a CustomViewSelector, but I'd appreciate any insight from those more knowledgeble than myself.

Thanks for getting back to me.

You should *never* have to pass the _flowExecutionId parameter during a request for redirect from a view state-- SWF will do that for you automatically (or there is a bug).

In the case of a redirect from a end-state it doesn't get passed, obviously, as a end-state terminates the flow execution.

As an separate issue, a redirect is *not* a pointer to a view.  It's a request to have the browser send another request at a specific ucl.  So view resolution doesn't apply here.

Now you could have the view state select a standard view that gets resolved using your XmlViewResolver.  But if you use a RedirectViewSelector it doesn't forward to a view at all...

I'd just do something like this:Code:

lt;webflow id=quot;itemsquot; start-state=quot;getItemsquot;/gt;   lt;action-state id=quot;getItemsquot;gt;       lt;action bean=quot;itemDaoquot; method=quot;findItemsquot;/gt;       lt;transition on=quot;successquot; to=quot;displayItemsquot;/gt;   lt;/action-stategt;
   lt;view-state id=quot;displayItemsquot; view=quot;itemListquot;gt;       lt;transition on=quot;newquot; to=quot;editItemquot;/gt;       lt;transition on=quot;selectquot; to=quot;editItemquot;/gt;   lt;/view-stategt;
   lt;subflow-state id=quot;editItemquot; flow=quot;editItemquot;gt;       lt;attribute-mappergt;lt;input value=quot;${sourceEvent.parameters.id}quot; as=quot;itemIdquot; from=quot;stringquot; to=quot;longquot;/gt;       lt;/attribute-mappergt;       lt;!-- start this conversation over (one option) --gt;       lt;transition on=quot;finishquot; to=quot;getItemsquot;/gt;       lt;!-- or ... lt;transition on=quot;finishquot; to=quot;endquot;/gt; --gt;   lt;/subflow-stategt;
   lt;!-- launch a new conversation (one option) --gt;   lt;end-state id=quot;endquot; view=quot;redirect:/items.htm?_flowId=itemsquot;/gt;

lt;/webflowgt;

lt;webflow id=quot;editItemquot; start-state=quot;displayItemFormquot;/gt;
   lt;view-state id=quot;displayItemFormquot; view=quot;itemFormquot;gt;       lt;entry-actiongt;lt;action bean=quot;formActionquot; method=quot;setupFormquot;/gt;       lt;/entry-actiongt;       lt;transition on=quot;submitquot; to=quot;saveItemquot;gt;lt;action bean=quot;formActionquot; method=quot;bindAndValidatequot;/gt;       lt;/transitiongt;   lt;/view-stategt;
   lt;action-state id=quot;saveItemquot;gt;       lt;action bean=quot;itemDaoquot; method=quot;saveItem(${flowScope.item})quot;/gt;       lt;transition on=quot;successquot; to=quot;finishquot;/gt;   lt;/action-stategt;
   lt;end-state id=quot;finishquot;/gt;
   lt;bean id=quot;formActionquot; class=quot;example.ItemFormActionquot;gt;       lt;constructor-arg ref=quot;itemDaoquot;/gt;   lt;/beangt;

lt;/webflowgt;public class ItemFormAction extends FormAction {
   private ItemDao dao;
   public ItemFormAction(ItemDao dao) {       this.dao = dao;       setFormObjectClass(Item.class);       setValidator(new ItemValidator());   }
   protected Object loadFormObject(RequestContext context) {       if (context.getFlowScope().containsAttribute(quot;itemIdquot;)) {Long itemId = (Long)context.getFlowScope().getAttribute(quot;itemIdquot;);return itemDao.getItem(itemId);       } else {return new Item();       }   }
}
Note this example relies some on some CVS head featuers awaiting release later this week, but hopefull this gets the jist accross!

Keith

Keith,

First off thanks a ton for the help.  I think I'm missing something from your example so please bear with me one more time.

I'm making an assumption that in your example /items.htm is the ucl mapped to the FlowController.  If that's the case and the _flowExecutionId query parameter is automatically appended to the redirect, then it seems to me the flow would resume in an end state.  Is that valid?  Will the flow transition to the start state in that case?

If so, is there any way to issue a redirect into an existing flow that transfers state to a view state that is not the start state?  I imagine I could break my flows up into small fragments so that each view I redirect to is a start state, but I'd rather keep similar functionality (like managing a particular item) together, if there is no need for reuse elsewhere.

I've noticed that there is one way to do this, but it's really ugly.  You can transition to a view state like this one:Code:
lt;view-state id=quot;redirectToListquot;  view=quot;redirect:/items.htm?_eventId=redirectquot;gt;   lt;transition on=quot;redirectquot; to=quot;listquot;/gt;
lt;/viewgt;
Creating a duplicate view for each view I want to redirect to doesn't make me happy, but it seems to me what would be really nice would be a way to issue a redirect to the flow controller while placing the flow in an quot;intermediaryquot; state that will transition to the view state when the flow resumes.  Maybe along the lines ofCode:
lt;transition on=quot;eventquot; to=quot;listquot; redir=quot;/items.htmquot;/gt;
Where if the redir attribute was set, rather than making an immediate transition to the next state (list) it would set list as the next state to be entered.  Think along the lines of how a start state is entered when a flow is created.  Or then again, maybe I'm just making this too complicated.  I can already hear the objections about mixing views and transitions   Either way, thanks again for your patience.

-Dave

I'm making an assumption that in your example /items.htm is the ucl mapped to the FlowController.

Yes.

If that's the case and the _flowExecutionId query parameter is automatically appended to the redirect, then it seems to me the flow would resume in an end state. Is that valid? Will the flow transition to the start state in that case?

No.  In this example the flow reaches the end state and the FlowExecution terminates.  As a part of terminating, it redirects to items.htm where it should pass in the flowId to launch a new flow execution like this:Code:
lt;end-state id=quot;endquot; view=quot;redirect:/items.htm?_flowId=itemsquot;/gt;
I forgot to do that in my example, but the _flowId is what tells the FlowController to launch a new items FlowExecution (conversation) in its start state.  If you wanted to change the execution the start state you could do this:Code:
lt;end-state id=quot;endquot; view=quot;redirect:/items.htm?_flowId=itemsamp;_startStateId=whateverquot;/gt;
That _startState parameter is not currently supported but could be easily added.

Keith

Doesn't it seem like a limitation to you that a redirect must be issued either from an end state, in which case you lose your flow scope, or from a view state, in which case the redirect itself must contain an event as a query parameter to trigger the ONLY transition elsewhere?  

Using an end state won't work at all for me.  Aside from my example, there are lots of situations where I would want to have a form page submit to an action, which redirects to another form page/action while using the flow scope throughout the transaction.  It's basically the same scenario as any multy page wizard, except my views are displayed after redirects for UI reasons.

Using a view state will work, but like I said, it's ugly.  I mean look at the transition from before:Code:
lt;view-state id=quot;redirectToListquot;  view=quot;redirect:/items.htm?_eventId=redirectquot;gt;   lt;transition on=quot;redirectquot; to=quot;listquot;/gt;
lt;/viewgt;
You can't tell me that's a good way to handle this.  Is no one out there doing redirect after post?

Why do you need to redirect after each view state post?

The FlowController ucl is always the same here, for good reason, to make it impossible for users to start a flow mid flow, but be guided through a controlled navigation (conversation) that the server is in control of and the client simply participates in.

The most compelling case for a redirect I see is a quot;redirect after flow executionquot;.  I assume you need to support stable back/forward/new window/refresh button use here: you can do that by turning on flow execution continuations and enabling page caching (see the sellitem sample for an example).

Correct me if I'm missing something, I'm certainly open minded here.  Flows really shouldn't be requesting to do a redirect often--and even if they did that behaivior definitely shouldn't be muddying up your flow definition view state definitions like that.

Keith

I do have continuations enabled and that seems to be working fine.  Basically, I need to support back and refresh for people with very little web knowledge.  

These people know that the back button takes them to the last page they saw and if they are presented with a popup message telling them data needs to be reposted they get confused.  They are also often on slow connections and will repeatedly hit the refresh button if a page doesn't appear immediately.  This should not ever cause data to be reposted.  I looked at the example and I imagine by page caching you mean setting cache control headers?  I've never tried to combat these problems that way before, but I imagine that if the previous page is in the cache the browser will just display it on back/refresh without resending the request, correct?  The biggest problem I have with that is security.  Some of the data on these sites is sensitive and many of the users are accessing computers in public places.  If a user logs out the following user shouldn't be able to review the previous users session by using the back button.  Plus, I'm not sure if those work when SSL is involved.  Classic redirect after post solves all my problems and I guess it seems like less of a hack.  Does that make more sense?

I was looking into the usage of a FlowExecutionListener and it seems like it almost supplies the functionality I want.  I could use the stateEntering(RequestContext, State) method to check if the execution was entering a view state after a post, but I can't find any way to override the State that is being entered.  If I could do that, I'd imagine I could inject a cutom ViewState that would issue the redirect and then transition to the original ViewState.  Would this be possible?  If so any ideas on how to go about intercepting a State to replace it with a different one?
¥
Back Forum Reply New