I have the following URL on my webpage upon pagination
http://localhost:9000/employee?p=2
I need to prompt to not found page whenever the parameter "p" is change. example:
http://localhost:9000/employee?b=2
It need the controller to input a notFound. what kind of condition will i do to do this?
Reference:
Controller:
#Transactional(readOnly=true)
public static Result list(int pageNum, int listSize) {
employeeMap.clear();
Page page = appModel.page(pageNum, listSize);
employeeMap = ListUtil.getEmpMap(employeeMap, page.getEmpList());
AppModel employees = new AppModel(employeeMap, searchMap);
/* if statement initiate a notFound page if pageNum us not the expected value */
if (pageNum < 0 || pageNum > page.getPage().get("intLastPage")) {
return notFound("<h1>Page not found</h1>").as("text/html");
}
/* if statement that put a not search found message if no employee is found */
if (page.getEmpList().size() == 0) {
flash("success", "There is no search results for the specified conditions");
}
return ok(index.render(appModelForm.fill(employees),page));
}
Routes:
# Employee list (look at the default values for pagination parameters)
GET /employee controllers.Application.list(p:Int ?= 1,l:Int ?= 125)
You could prevent people from switching that name of the parameter overall by changing your routing. But to achieve all the possibilities outlined by what you want to do, you could do the following:
GET /employee/:p/:l controllers.Application.list(p:Int ?= 1,l:Int ?= 125)
GET /employee/p/:p controllers.Application.list(p:Int, 125)
GET /employee/l/:l controllers.Application.list(1, l:Int)
It depends on how you handle the URL calling in the template, but if you can have that auto-generate the default parameters into the URL if the user does not put them in, you could just keep the first one by itself.
The URL to summon your controller will now instead be:
http://localhost:9000/employee/p/2
http://localhost:9000/employee/l/4
http://localhost:9000/employee/2/4
And then you can route anything else to a not found controller method:
GET /employee/--String, empty or whatever else--- controllers.Application.returnNotFound
#Transactional(readOnly=true)
public static Result returnNotFound() {
return notFound("<h1>Page not found</h1>").as("text/html");
}
Related
I have a controller that uses #Secured() to restrict access to an action based on the specific domain object being processed:
#Secured('#permissionChecker.isLocationAdmin()')
public edit(Location location) {
// [code]
}
Based on a previous answer, I know I can't pass the location object to the permission checker. When using the proposed workaround (in the permissionChecker.isLocationAdmin code),
import org.springframework.web.context.request.RequestContextHolder
...
def params = RequestContextHolder.requestAttributes.params
I can get access to the id parameter if I use the url https://.../edit?id=42.
However, if I use https://.../edit/42, params object is empty. When I look at the params in the controller, controller, action, and id are set. The params object I get in the controller is also not the same (according to the debugger).
My UrlMappings are pretty straight forward, with the relevant entry:
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
What seems to work so far is pulling the id out of the url by hand:
def params = RequestContextHolder.requestAttributes.params
String idStr = params.id
if (!idStr) {
String url = ((ServletRequestAttributes) RequestContextHolder.requestAttributes).request.getRequestURI()
int idx = url.lastIndexOf('/')
if (url && idx >= 0) {
idStr = url.substring(idx + 1)
}
}
but this seems wrong and fragile. Is there a better way?
In my website I have the following route defined:
routes.MapRoute(
name: "Specific Product",
url: "product/{id}",
defaults: new { controller = "", action = "Index", id = UrlParameter.Optional }
);
In that way I want customers to be able to add the ID of the product and go to the product page.
SEO advisors have said that it would be better if we could add a description of the product on the URL, like product-name or something. So the URL should look something like:
/product/my-cool-product-name/123
or
/product/my-cool-product-name-123
Of course the description is stored in the db and I cannot do that with a url rewrite (or can I?)
Should I add a redirection on my controller (this would seem to do the job, but it just doesn't feel right)
On a few sites I checked they do respond with a 301 Moved Permanently. Is that really the best approach?
UPDATE
As per Stephen Muecke's comment I checked on what is happening on SO.
The suggested url was my own Manipulate the url using routing and i opened the console to see any redirections. Here is a screenshot:
So, first of all very special thanks to #StephenMuecke for giving the hint for slugs and also the url he suggested.
I would like to post my approach which is a mix of that url and several other articles.
My goal was to be able to have the user enter a url like:
/product/123
and when the page loads to show in the address bar something like:
/product/my-awsome-product-name-123
I checked several web sites that have this behaviour and it seems that a 301 Moved Permanently response is used in all i checked. Even SO as shown in my question uses 301 to add the title of the question. I thought that there would be a different approach that would not need the second round trip....
So the total solution i used in this case was:
I created a SlugRouteHandler class which looks like:
public class SlugRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var url = requestContext.HttpContext.Request.Path.TrimStart('/');
if (!string.IsNullOrEmpty(url))
{
var slug = (string)requestContext.RouteData.Values["slug"];
int id;
//i care to transform only the urls that have a plain product id. If anything else is in the url i do not mind, it looks ok....
if (Int32.TryParse(slug, out id))
{
//get the product from the db to get the description
var product = dc.Products.Where(x => x.ID == id).FirstOrDefault();
//if the product exists then proceed with the transformation.
//if it does not exist then we could addd proper handling for 404 response here.
if (product != null)
{
//get the description of the product
//SEOFriendly is an extension i have to remove special characters, replace spaces with dashes, turn capital case to lower and a whole bunch of transformations the SEO audit has requested
var description = String.Concat(product.name, "-", id).SEOFriendly();
//transform the url
var newUrl = String.Concat("/product/",description);
return new RedirectHandler(newUrl);
}
}
}
return base.GetHttpHandler(requestContext);
}
}
From the above i need to also create a RedirectHandler class to handle the redirections. This is actually a direct copy from here
public class RedirectHandler : IHttpHandler
{
private string newUrl;
public RedirectHandler(string newUrl)
{
this.newUrl = newUrl;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext httpContext)
{
httpContext.Response.Status = "301 Moved Permanently";
httpContext.Response.StatusCode = 301;
httpContext.Response.AppendHeader("Location", newUrl);
return;
}
}
With this 2 classes i can transform product ids to SEO friendly urls.
In order to use these i need to modify my route to use the SlugRouteHandler class, which leads to :
Call SlugRouteHandler class from the route
routes.MapRoute(
name: "Specific Product",
url: "product/{slug}",
defaults: new { controller = "Product", action = "Index" }
).RouteHandler = new SlugRouteHandler();
Here comes the use of the link #StephenMuecke mentioned in his comment.
We need to find a way to map the new SEO friendly url to our actual controller. My controller accepts an integer id but the url will provide a string.
We need to create an Action filter to handle the new param passed before calling the controller
public class SlugToIdAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var slug = filterContext.RouteData.Values["slug"] as string;
if (slug != null)
{
//my transformed url will always end in '-1234' so i split the param on '-' and get the last portion of it. That is my id.
//if an id is not supplied, meaning the param is not ending in a number i will just continue and let something else handle the error
int id;
Int32.TryParse(slug.Split('-').Last(), out id);
if (id != 0)
{
//the controller expects an id and here we will provide it
filterContext.ActionParameters["id"] = id;
}
}
base.OnActionExecuting(filterContext);
}
}
Now what happens is that the controller will be able to accept a non numeric id which ends in a number and provide its view without modifying the content of the controller. We will only need to add the filter attribute on the controller as shown in the next step.
I really do not care if the product name is actually the product name. You could try fetching the following urls:
\product\123
\product\product-name-123
\product\another-product-123
\product\john-doe-123
and you would still get the product with id 123, though the urls are different.
Next step is to let the controller know that it has to use a special filer
[SlugToId]
public ActionResult Index(int id)
{
}
This is probably a very simple problem but for the life of me I can't get it to work.
I need to redirect google requests for ajax generated code to return a html template for indexing
I have the following in my urlmappings.conf
"/?_escaped_fragment_=$id"(controller:"google",action:"getOfferDetails")
However if I enter mysite?_escaped_fragment_=200 in the browser the controller is not called
If however I enter mysite_escaped_fragment=200 the controller is called and the action executed.
Any suggestions would be greatly appreciated.
Thanks
Ash
You can not use '?' char in the route matching i.e. it will be ignored.
Use this filter instead (put this class in the config folder w/ fileName CrawlerFilters.groovy):
class CrawlerFilters {
def filters = {
google(controller: '*', action: '*') {
before = {
boolean isCrawler = webRequest.params._escaped_fragment_ != null
if (isCrawler && !request._alreadyForwarded) {
request._alreadyForwarded = true
forward controller: 'google', action: 'getOfferDetails'
}
}
}
}`
I have a page with links. These links all end in the same way. For example www.site.com/fruit/apples, www.site.com/fruit/bananas, www.site.com/fruit/oranges, etc. I want all these links to call the same Dart app and have the app do some processing and then redirect you wherever you need to go (the bananas page vs. the oranges page). This way, I avoid having an actual HTML file for every single fruit. I can instead have a single landing template that gets populated with variable fruit data.
The part I'm hung up on is passing the url into the Dart app so it can do the handling. I understand main() cannot receive arguments, so what's another way?
You can use the route package to handle the URL's for you.
For example:
import 'package:route/client.dart';
final fruitUrl = new UrlPattern(r'/fruit/(\w+)');
main() {
var router = new Router()
..addHandler(fruitUrl, showFruit)
..listen();
}
void showFruit(String path) {
var fruit = fruitUrl.parse(req.path)[0];
// Display the page according to the fruit type
}
If you don't need to handle actual routes, and you just want to handle any query parameters passed of the form ?fruit=apple you don't have to use the routes package and can instead manually parse the URL:
Map params = {};
// If arguments were provided, decode them into params map
if(window.location.search.length > 1) {
// Break all arguments into form: fruit=apple
List<String> queryPairs = window.location.search.substring(1).split('&');
for(String queryPair in queryPairs) {
// Add arguments into params map: key=fruit, value=apple
List<String> queryPairList = queryPair.split('=');
params[queryPairList[0]] = queryPairList[1];
}
}
// Handle the proper action based on the fruit
switch(params['fruit']) {
case 'apple':
// ...
break;
// ...
case 'orange':
// ...
break;
}
I want to give admin the option to change the URL identifier of MyCustomModule from backend.
E.g.: www.mydomain.com/identifier
What I did is the following:
In etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
**<backend_model>press/config_identifier</backend_model>** // edited after answer
<sort_order>1</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment>(eg: domain.com/identifier)</comment>
</identifier>
In helper/data.php
public function getUrl($identifier = null)
{
if (is_null($identifier)) {
$url = Mage::getUrl('').self::getListIdentifier();
} else {
//$url = Mage::getUrl(self::getListIdentifier()).$identifier;
**$url = Mage::getUrl(self::getListIdentifier(), array('identifier' => $identifier,'_use_rewrites'=>true)); //edited
}**
return $url;
}
after that i created a model file identifier.php :
class FME_Press_Model_Config_Identifier extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('press/'.$store.'/'.$identifier)
->setRequestPath($path.'/'.$identifier)
->save();
}
}
}
in config.xml i wrote this:
<events>
<controller_front_init_routers>
<observers>
<press>
<type>singleton</type>
<class>FME_Pres_Controller_Router</class>
<method>initControllerRouters</method>
</press>
</observers>
</controller_front_init_routers>
</events>
and also this is present in my file, m not sure whether it is relevant :
<adminhtml>
<args>
<modules>
<FME_Press_Override before="Mage_Adminhtml">FME_Press_Override_Admin</FME_Press_Override>
</modules>
</args>
</adminhtml>
NOTE: I had been told to make some changes in Controller/Router.php but I don't know what changes to make.
If you want I can add that code also?
Now, what else should I do?
I feel changing the application's router is entirely the wrong approach to take. It is messy and can be easily broken if another module overrode it for a similar purpose. The clean way is with URL rewrites.
You want it to be alterable so you cannot use a fixed XML based rewrite. Instead let's look at the built in rewrite system.
First in your module's etc/config.xml file set up a normal controller.
<frontend>
<routers>
<MyCustomModule>
<use>standard</use>
<args>
<module>Example_MyCustomModule</module>
<frontName>customlist</frontName>
</args>
</MyCustomModule>
</routers>
</frontend>
Here the front name used is customlist, that will always work and shouldn't conflict with any other front name, the rewritten name shall be in addition to this. Now whenever you generate an URL (perhaps in a helper function) you do so to this apparently fixed front name.
$url = Mage::getUrl('customlist', array(
'id' => $id, // 'id' will get used in the "target path" later
'_use_rewrites' => true
));
Note that the variable identifier ($id) is passed to the getUrl function rather than simply appending to it's result. If the function returns an URL with a query (&) or fragment (#) the identifier could have been appended to the wrong part.
The next step is to create rewrite records for every possible combination of identifier and store. You probably have a finite number of lists so this is possible, perhaps identifiers are particular to stores so only need to be defined once each. Either loop through all your lists in an installer script or have each list create rewrites when it is saved.
$path = Mage::getStoreConfig('custom/config/identifier', $storeId);
// Change 'custom/config/identifier' to match the path used in system.xml
$rewrite = Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id);
if ($rewrite->getId()) {
// A rewrite already exists, you might want to skip creating another
continue;
}
Mage::getModel('core/url_rewrite')
->setStoreId($storeId)
->setIsSystem(true) // set to false to allow admin to edit directly
->setOptions('RP') // Redirect Permanent 301
->setIdPath('customlist/'$storeId.'/'.$id) // should never change
->setTargetPath('customlist/index/index/id/'.$id) // what gets used
->setRequestPath($path.'/'.$id) // the path used in the browser
->save();
So now if the admin sets the URL path to be "foo/bar" and requests the page "www.mydomain.com/foo/bar/3" it will be rewritten to "customlist/index/index/id/3" and the method Example_MyCustomModule_IndexController::indexAction() will be called. The file containing that will of course be app/code/local/Example/MyCustomModule/controllers/IndexController.php and the 3 value is retrieved there:
public function indexAction()
{
$id = $this->getRequest()->getParam('id'); // 'id' was specified in getUrl()
// use $id here...
}
It should work by now but what if a list is removed? The rewrites need to be updated for each store. Models have a _beforeDelete method, override it for your list objects.
protected function _beforeDelete()
{
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$storeId.'/'.$this->getId())
->delete();
return parent::_beforeDelete();
}
Similarly they need to be updated to match changes in configuration.
etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
<backend_model>myCustomModule/config_identifier</backend_model>
...
</identifier>
Model/Config/Identifier.php
class Example_MyCustomModule_Model_Config_Identifier
extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id)
->setRequestPath($path.'/'.$id)
->save();
}
}
}