To avoid possible infinite redirection issues I'm using static html files for 404 and 500:
<httpErrors errorMode="Custom" defaultResponseMode="File" existingResponse="Replace">
<clear />
<error statusCode="404" path="Static\404.html" />
<error statusCode="500" path="Static\Error.html" />
</httpErrors>
I'm also looking for a solution to this problem. Of course I could use static html files as Mari suggests, but that's not really a "solution".
So, anyone got a solution for this?
I have this in my config:
<httpErrors errorMode="Custom" existingResponse="Replace">
<clear/>
<error statusCode="404" responseMode="ExecuteURL" prefixLanguageFilePath="en" path="/system/notfound/" />
<error statusCode="500" responseMode="ExecuteURL" prefixLanguageFilePath="en" path="/system/error/" />
</httpErrors>
Then I just created the pages in the CMS as any other page (so, my path is /system/nofound/)
So, you don't have anything in Global.asax? Do you get the 404 status code for a non-existant page in Fiddler?
I added nothing to my Global.asax page.
It wasn't initally returning a 404 status code, but I just added the following to the top of the templates .cshtml page
Response.StatusCode = 404;
I have a standard template, so just used logic on the page title in the CMS to dertmine if I should include the status code.
I thought I have tried Danny's solution yesterday, I think I'll have to give it one more try.
Anyway, I think I've come up with another working solution. But I still need to test it a bit more, but so far it seems to be working fine. I get 404 status code, it shows information from a epi page, it doesn't redirect and it doesn't get stuck in a redirection loop.
I'm gonna test it a bit more before I post it here, but in short:
* Add a routing rule for the path "404" in global.asax to a controller and a NotFound-action in that controller.
* Add httpErrors in web.config with a 404 error rule to redirect to "/404" (as in Dannys solution)
* Write logic in the NotFound-action to get a ContentReference to the epi 404-page and return it as a model to the view.
I don't have time to try this now, but I think if you just add
Response.StatusCode = 404;
to the cshtml, you would get a 404 for the page /404 and you need to get a 404 status code for the page that doesn't exist. If a page doesn't exist, you would want a search engine to get 404 for it and not 301 or 302 or 200.
You know, I think you're probably right thinking about it.
I'll do some more testing on it, to be sure.
:|
I'm pretty confident my solution works so here is some sample code,
In global.asax
protected override void RegisterRoutes(System.Web.Routing.RouteCollection routes)
{
// Route to handle 404's
routes.MapRoute(
"404",
"404",
new {controller = "DefaultPage", action = "NotFound"});
base.RegisterRoutes(routes);
}
In web.config
<httpErrors errorMode="Custom" existingResponse="Replace">
<clear/>
<error statusCode="404" responseMode="ExecuteURL" prefixLanguageFilePath="en" path="/404" />
</httpErrors>
In DefaultPageController.cs
public ActionResult NotFound(SitePageData currentPage)
{
// Get StartPage
var startPage = ContentReference.StartPage.GetPage<StartPage>();
// Get ErrorPage
var notFoundPage = startPage.NotFoundPage.GetPage<ErrorPage>();
if (notFoundPage == null)
throw new NullReferenceException("No 404 page has been set on the StartPage");
var model = CreateModel(notFoundPage);
// Set RoutingConstants.NodeKey so EPi's extension method RequestContext.GetContentLink() will work
ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = startPage.NotFoundPage;
Response.StatusCode = 404;
return View("~/Views/ErrorPage/Index.cshtml", model);
}
And finally add a property on the StartPage-model,
public virtual PageReference NotFoundPage { get; set; }
As I said I'm pretty sure this code works, but it's more a proof of concept and is not production ready code!
EDIT:
Some of the code is using extension methods and methods that's part of my solution, but I'll hope you'll get the idea by looking at the code. Feel free to let me know if anything is unclear.
I tested Daniel's solution and it worked like a charm!
I made my controller like this:
public ActionResult NotFound(SitePageData currentPage)
{
StartPage startPage = (StartPage)DataFactory.Instance.GetPage(PageReference.StartPage);
var errorPage = DataFactory.Instance.GetPage(startPage.NotFoundPage);
// Set RoutingConstants.NodeKey so EPi's extension method RequestContext.GetContentLink() will work
ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = startPage.NotFoundPage;
Response.StatusCode = 404;
return RedirectToAction("Index");
}
That will send me to my specified EPiServer page. For example if i choose my About page I will be sent to www.domain.com/About . This also works with ViewModels and such.
David and Daniel, Thank you so much for your solution. A combination of the two has worked perfectly for me, just what I was looking for.
Thank you!!
Daniel, just trying to implement your soltuon.
I can't seem to get your example working. Is "CreateModel" a custom method you have?
Edit: Sorry, spotted you mention your extension methods.. I'll try and work out what's to do.
Danny, yeah as you already noticed that's my method. It's actually code from Alloy MVC exampl,
/// <summary>
/// Creates a PageViewModel where the type parameter is the type of the page.
/// </summary>
/// <remarks>
/// Used to create models of a specific type without the calling method having to know that type.
/// </remarks>
private static IPageViewModel<T> CreateModel<T>(T page) where T : SitePageData
{
var type = typeof(PageViewModel<>).MakeGenericType(page.GetOriginalType());
return Activator.CreateInstance(type, page) as IPageViewModel<T>;
}
I have a working implentation, but on my staging server, it takes 1-2 minutes to display the not found page when requesting a page that doesn't exist.
On my local build, it loads instantly.
Any one got any ideas what could be causing this to happen?
Can you compare web.config files for differences? Perhaps you are missing <clear /> in <httpErrors>?
Also, check if more redirections occur with Fiddler than only one, perhaps it'll tell you what's going on.
Strange, I haven't hade problems witha request taking minutes to load. Another way to debug things like this is using Glimpse, but I haven't tested Glimpse whit EpiServer so I'm not sure it'll work.
But start with Fiddler, just like Marija suggested.
Cheers for the quick response guys.
So, using fidler doesn't seem to show up any further redirects. It eventaully completes, and shows as 404, but this takes several minutes.
Just hitting the /404 page loads right away, with fiddler correctly showing the header s 404.
Not used glimpse before (although I've seen a demo of it), May take a look and see if it can help, although not sure what I'll be looking for,
Very strange, errmm.. I'm not quite sure what to look for either.
Have you disabled debug and compiled in release mode?
yep, debug disabled, and compiled in release mode..
All very strange.. Guess I'll have to open a support ticket.
Hey Danny,
Did you solve the problem with the slow error page? I have the same problem right now when running the site in stage. On our dev machines it works as expected (fast).
Hi Björn,
Yes, I did solve it. Strangely, all I had to do is install the URL Rewrite module..
See http://weblogs.asp.net/scottgu/archive/2010/04/20/tip-trick-fix-common-seo-problems-using-the-url-rewrite-extension.aspx for details.. Not sure why it solved it, but it did!
Good luck
Danny
Thanks for a fast reply. Unfortunately, that didn't solve it for me. Think you did any other changes on the server or enabled something when you installed URL Rewrite?
Very weird, this might actually be a bug in IIS? When the request is made locally (IsLocal on Request), it's fast. The stage environment is fast if browsed locally. I get the same error on my dev machine when making requests from a remote computer. Seems application_error is hit and then nothing happens for 1-2 minutes before the configured path is hit.
I ended up rolling a somewhat different solution. Configured PassThrough on existingResponse and made a module that handle 404 and errors. Works fine.
I had the same issue, the problem is the route mapping:
protected override void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("404", "404",
new { controller = "ContentPage", action = "NotFound" });
base.RegisterRoutes(routes);
}
For some reason the 404 page is called in an infinite loop till the execution timeout kicks in. I removed that route and added a catch all route (just in case, I think it isn't neccesary):
protected override void RegisterRoutes(RouteCollection routes)
{
base.RegisterRoutes(routes);
routes.MapRoute("Error", "{*url}",
new { controller = "ContentPage", action = "NotFound" });
}
And now it works fast and great
Erwin G,
Strange! That shouldn't be needed. We're not seeing that problem on any of our ~10 sites. But thanks for the heads up! :)
Another thing to check regarding the timeout-issue on remote calls to the site is to disable EPiServer's own global error handling in episerver.config. It is set to 'RemoteOnly' by default. Set it to 'Off'.
This solved it for us.
/David
I've added the suggested code to an Alloy site. This is my NotFound method in the controller:
public ActionResult NotFound(SitePageData currentPage) { StartPage startPage = (StartPage)DataFactory.Instance.GetPage(PageReference.StartPage); var errorPage = DataFactory.Instance.GetPage(startPage.NotFoundPage) as SitePageData; var model = CreateModel(errorPage); // Set RoutingConstants.NodeKey so EPi's extension method RequestContext.GetContentLink() will work ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = startPage.NotFoundPage; Response.StatusCode = 404; return View("~/Views/StandardPage/Index.cshtml", model); }
I get the following error in PageViewContextFactory.cs:
The provided content link does not have a value.
Parameter name: contentLink
Line 55: public virtual IContent GetSection(ContentReference contentLink)
Line 56: {
Line 57: var currentContent = _contentLoader.Get<IContent>(contentLink);
Line 58: if(currentContent.ParentLink != null && currentContent.ParentLink.CompareToIgnoreWorkID(ContentReference.StartPage))
Line 59: {
I'm obviously missing something, any ideas?
I've added the suggested code to an Alloy site. This is my NotFound method in the controller:
public ActionResult NotFound(SitePageData currentPage) { StartPage startPage = (StartPage)DataFactory.Instance.GetPage(PageReference.StartPage); var errorPage = DataFactory.Instance.GetPage(startPage.NotFoundPage) as SitePageData; var model = CreateModel(errorPage); // Set RoutingConstants.NodeKey so EPi's extension method RequestContext.GetContentLink() will work ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = startPage.NotFoundPage; Response.StatusCode = 404; return View("~/Views/StandardPage/Index.cshtml", model); }
I get the following error in PageViewContextFactory.cs:
The provided content link does not have a value.
Parameter name: contentLink
Line 55: public virtual IContent GetSection(ContentReference contentLink)
Line 56: {
Line 57: var currentContent = _contentLoader.Get<IContent>(contentLink);
Line 58: if(currentContent.ParentLink != null && currentContent.ParentLink.CompareToIgnoreWorkID(ContentReference.StartPage))
Line 59: {
I'm obviously missing something, any ideas?
Hi Mark,
I recently had a similiar problem with this. In my MenuList-helper the code helper.ViewContext.RequestContext.GetContentLink() returned a null value. My site is an epi 7-site upgraded to 7.5. It seems that the original code
ControllerContext.RouteData.DataTokens[RoutingConstants.NodeKey] = startPage.NotFoundPage;
no longer gives the desired result. I replaced this with a new (?) method that works:
ControllerContext.RequestContext.SetContentLink(startpage.NotFoundPage);
You might need to set the startpage as the rootResolve, like this:
var urlSegmentRouter = context.Locate.Advanced.GetInstance<IUrlSegmentRouter>(); urlSegmentRouter.RootResolver = (s) => s.StartPage; routingParameters.UrlSegmentRouter = urlSegmentRouter;
See more here:
http://world.episerver.com/Forum/Developer-forum/-EPiServer-75-CMS/Thread-Container/2014/6/GetVirtualPathSegment-in-custom-segment-is-never-called/
Thanks @David,
This worked for us also,
<episerver> <applicationsettings globalerrorhandling="Off"> </applicationsettings></episerver>
I want to have a 404 page, that is an EPiServer page.
This is my web.config
This is my Controller:
and finally, my Global.asax part:
What I saw online is that MVC should have a different 404 error handling, such as this: http://stackoverflow.com/questions/619895/how-can-i-properly-handle-404-in-asp-net-mvc, however, it's related to non-CMS MVC.
My code gives me indefinite redirection and if I ommit Response.StatusCode = 404; than I don't get the 404 status code in the browser.
Has anyone got ahead of this?