I'm trying to create an extendible cms. I have URLs in my database like "/menu-item/content" for each of these urls there is an action specified in format "AcmeDemoBundle:Default:index".
I would like to resolve this action and call the appropriate controller based on the matched URL.
My main goal is to call the controller stored in the db directly. Before i used a method when i put a route in the end of the routings which matched all urls associate a controller with it that found out the target controller from the db, than used Controller::forward() to get the expected results. The main problem with this approach is that i need to create an unnecessary subrequest for each page load. The other problem is passing post variables to this new subrequest.
So far i've tried to listen on kernel.request event and modify $request->attributes to contain the matching _controller value, but it seems like it has no effect.
Any advice in the subject is appreciated.
I think you mixed up the forward and redirect calls. Redirect performs 301 or 302 redirects (permanent and temporary respectfully), which creates the subrequest, as you call it. Forward performs internal call (same request) to the new action. As for your second problem, as forward is an internal call within the same request, everything global stays the same (including POST variables). In my opinion, using forward might be a good solution for you (if you don't have any other low-level requirements or if you want to be even more flexible than the usual MVC operation allows).
Hope this helps.
Related
i'd like to forward a request to a controller to some specific action without redirecting to another url.
so, for example, the following url:
/home/peter
should internally become
/home/people/peter
i know i could catch the action name ('peter') in the 'init' function, then do a Controller::redirect, but i'd like to stay on the same url, just have the request internally forwarded to the 'people' action.
2 things to note:
'peter' is a dynamic string, so i can't just hardcode a route for this
any other action of the controller shall still work, so only a couple of strings should get forwared (eg. 'peter', 'ann' get forwarded to the 'people' action, whereas 'otherAction' is still callable)
i already found the 'handleAction' and 'handleRequest' methods in the Controller class, but just can't imagine how make use of them for my task.
SilverStripe version: 3.0.3
I suggest the use of static $url_handlers described here, that allows you to define specific routes to actions. Your use case could be achieved by using a catch-all url-handler.
But therefore we have to pay attention to the order of each route. The catch-all route has to be defined at the end. All other actions need a seperate url handler before:
public static $url_handlers = array(
'otherAction' => 'otherAction',
// ...
'people/$Name' => 'people',
'$Name' => 'people' // catch-all
);
This should fit to your explanation. The last route catches all dynamic routes which weren't handled before.
But as Ingo mentioned, both routes would deliver the same content and that could be bad for SEO. You could target the second route people/$Name to a different action which does a 301 redirect to the url with $Name only. I would recommend this if you already have public content and you want to replace the existing URLs with the short version.
I hope this solves your problem (temporarily).
First of all, I assume you have good reasons for making duplicate URLs (bad for SEO) :)
I've tried around a bit with Director::direct(), which would be the cleanest, but didn't get anywhere. Director::test() resets most request state, so doesn't qualify either.
Calling handleRequest() or handleAction() on a new controller instance is tricky, because the Director has built up a lot of state by that time already (for example pushed to the controller stack).
So, unfortunately the SilverStripe routing isn't that flexible, anything you do will go deep into system internals, and potentially break with the next release.
my site has many many routes. since the users can add or remove pages at will, i need a way to reregister the routes from time to time
at reregister i do not want to clear the whole route cache ("RouteTable.Routes.Clear"), but i would rather go thru the table route by route, and using a certain regex on its name, decide if to remove it or not.
after that i will reregister the specific pages that i need to
heres my code meanwhile
For Each r In RouteTable.Routes
If CType(r, Route).DataTokens("ConfigID") = ConfigID Then RouteTable.Routes.Remove(r)
Next
after the first remove it throws an error "Collection was modified; enumeration operation may not execute."
thank you very much for your help!!
It's not possible to get the route name of the route because the name is not a property of the Route. When adding routes to the RouteTable, the name is used as an internal index for the route and it's never exposed.
There's one way to do this.
When you register a route, set a DataToken on the route with the route name and use that to filter routes.
The easiest way to do #1 is to probably write your own extension methods for mapping routes.
I have an action-method in a controller that takes requests coming from a variety of different views.
It is somewhat of a utility method and I simply want it to accept the parameters it is given - do something - and then refresh the view that sent the request.
Right now, the only way I see to do this is by having the method figure out what view sent it the info and do a:
return RedirectToAction("method", "controller");
For each possibility (or something similar to that).
Is there a more general way I can make my method just re-render the current view without having to explicitly identify it?
-Thanks
Your best bet is to use jQuery to post the data then utilize the results as you see fit. Otherwise you can pass in the action/controller name in the post and use them dynamically to redirect.
I have created a controller that does some business logic and creates a model. If I pass this model directly to view by returning ModelAndView with view name and model - everything working great. But now I want to display results at another page. So I use "redirect:" prefix to redirect to another controller, but the model is lost.
What Im missing?
Regards,
Oleksandr
you can use the forward: prefix which ultimately does a RequestDispatcher.forward()
insted of The redirect: prefix.
Option 1 :
You might put the model in session and get it back in the controller and nullify it in session.
Option 2 :
You said, you are having two controllers, first one would retrieve the user input and do some business logic and redirect to other one. My suggestion is to move the business logic which is placed in both controllers to a class and have only one controller which would return the model and view to the user.
During redirect: request is sent using GET method with parameters appended to the url. You can right a new method in the controller to receive the request parameters with #RequestParameter annotation.
While redirecting the request simple add all the parameters as string
e.g ModelAndView mv = new ModelAndView();
mv.setViewName(redirect:/your/url);
mv.addObject("param1", string_param);
.
.
.
This way you can redirect successfully.
Since the redirect: prefix runs another controller, you will lose whatever you had in the ModelAndView in the previous controller. Perhaps the business logic controller should not be a controller at all -- maybe you should just make it a regular class, and have the results controller call it. Then the results controller could save the data to the model.
i would not use #SessionAttributes as it may change in future releases of spring. I would stick with the old fashioned way that Oleksandr showed you above
somehow an old post but maybe someone else will find it usefull
I recently want to have a special routing rule : {*whatever}/details/{Id}/{itemName}
I know an exception will be thrown once I run the application. In my application, for example my url pattern is www.domain.com/root/parent/child/.../child/details/30/itemname
but the current routing doesnot support this. How can custom the routing handler to make it work?
A class has been written that supports this
I've written such a class that can handle catch-all segment anywhere in the URL. There's quite some code to it, but it works as expected and I've used it on a real life project.
Check it out yourself and see if it fulfils your needs.
The problem is... how will it know when to stop?
the {*whatever} segment will match:
/foo/
/foo/bar
/foo/bar/details/4/moreFoo
/foo/bar/andmore/details/4/moreFoo
Because the catch-all parameter includes anything, it will never stop.
The only way to implement this would be to create a different route for each place you use details...
eg:
games/details/{id}/{itemName}
widgets/details/{id}/{itemName}
books/details/{id}/{itemName}
Of course, that is already provided in the default {controller}/{action}/{id} route
I think you may want to look at extending the System.Web.Routing.RouteBase class and override the GetRouteData() method. With it you can look at the requested url and decide if matches your pattern and if so construct and return a new instance of RouteData that points to the controller and action that you want to handle the request. Otherwise if you don't match the requested url you return null.
See the following for examples:
Pro ASP.NET MVC Framework
By Steve Sanderson
Custom RouteBase