Custom 404

Topics: General, Writing themes
Dec 7, 2012 at 7:05 PM

OK - I know this has been hammered on several times; but I can't seem to find anything that would help.

I want to use a content item (page) in the CMS as my 404 handler.

Therefore technically I'd like to redirect all 404 to /pagenotfound (which is the path to my content item).

using customErrors does not seem to be working (using 1.6).  Therefore I assume that some other handler is hooking this.  I removed the httphandlers for this as well and still it renders the orchard default.

I can't use the orchard default because it uses a layer that I can't create a rule to filter out (since the 404 could be any url) - therefore I must redirect to that content item - to ensure the url is controlled.

Any ideas?

Dec 9, 2012 at 8:01 PM

Take a look at how 404s are initiated in the UnhandledExceptionFilter.cs.  It looks for the HttpNotFoundResult and then hijacks the request and replaces it with a ShapeResult of name NotFound.  

I imagine there are a couple of ways to override this, but I would probably try creating a new orchard filter, look for the HttpNotFoundResult AND the ShapeResult of NotFound (in case your filter comes into play after built-in unhandled filter) and then redirect to your custom orchard page.  I haven't tried this, FYI...

Keep us updated on your progress!

Andy

Dec 10, 2012 at 3:57 AM

I certinaly think that's in the right direction; I found that the check for custom code to handle exceptions (which I belive is supposed to verify if there are any customErrors in the web.config) is not detecting that I've configured this.  So I'm trying to figure out why; it's just some simple settings within the system.web section - correct?

I'm going to see if a custom filter will catch it.

thanks

Dec 10, 2012 at 4:44 AM
Edited Dec 10, 2012 at 5:10 AM

Anything I try - doesn't seem to work; I know we can override the theme NotFound.cshtml - but that's just a shape; I want the error to redirect to a page (since that's the only way for me to prevent my breadcrumb from showing up on the 404).

Since it seems that nothing I do can get the <system.web><customErrors> section to work; is there something else that maybe I'm missing?

I know it can't be that hard to provide your own 404 handler in Orchard; so there must be something simple that I'm overlooking?

Any insight would be appreciated.

Should be noted: running Orchard 1.6 and I'm trying to redirect to a page is managed within Orcahrd. All I've done is create the page called pagenotfound and it has a path of /pagenotfound; so I've created a <customErrors redirectMode="ResponseRewrite"> section <error statusCode="404" redirect="/pagenotfound" />

When the code is looking for an HandleErrorAttribute - is it looking for this? or must I add an error handling attribute to a controller that would then redirect?  If I have to-do this in the controller - a few lines of example would help?

thanks!

 

Dec 10, 2012 at 3:52 PM
Edited Dec 10, 2012 at 3:52 PM

Hi fruber:

The UnhandledExceptionFilter provides two important bits of functionality a) handling uncaught exceptions and returns the "ErrorPage" shape and b) look for HttpNotFoundResult (MVC Action Result) and then returns the "NotFound" shape.  I'm not aware of any part of the code that checks for the "customErrors" configuration.

Within the code chunk for the exception handling, it will look for an existing HandleErrorAttribute and defer to that if it exists, but that doesn't really help with what you're trying to do...

Here's a sample implementation of a filter that will redirect to the specified page.  I tested this and it works.  Note: this isn't a rewrite, it's a full redirect.

public class CustomErrorsFilter : FilterProvider, IActionFilter {

    public void OnActionExecuting(ActionExecutingContext filterContext) { }

    public void OnActionExecuted(ActionExecutedContext filterContext) {
        if (IsNotFoundResponse(filterContext)) {
            // now act on our determination
            filterContext.HttpContext.Response.Redirect("/your-custom-404-page");
            filterContext.HttpContext.Response.SuppressContent = true;
        }
    }

    private static bool IsNotFoundResponse(ActionExecutedContext filterContext) {
        // catch the "NotFound" result set by the built-in UnhandledExceptionFilter.
        var shapeResult = filterContext.Result as ShapeResult;
        if (shapeResult != null) {
            dynamic model = shapeResult.Model;
            if (model.Metadata.Type == "NotFound") {
                return true;
            }
        }

        // catch the HttpNotFoundResult directly in case our filter fires before the built-in UnhandledExceptionFilter.
        // todo: determine if this is really necessary.
        if (filterContext.Result is HttpNotFoundResult) {
            return true;
        }

        // if we made it this far, it's not a 404 AFAWK (as far as we know).
        return false;
    }
}

It just occurred to me that you could also create a route provider that adds a "catch all" route with priority greater than -9999 and send that to a custom controller action that does the redirect behavior above.  (-9999 is the priority of the built-in catch all that redirects to the core not found action.)

As far as performing a rewrite, I'd have to think about how to accomplish that...  Anyone from the orchard team have any insight on rewriting?

Andy

Dec 10, 2012 at 5:46 PM

Thanks! - I was going down this path and ended up using the "filterContext.ActionDescriptor.ActionName.Equals("NotFound")" - which I assume is safe; although I like yours better; so I'll modify accordingly.

Instead of redirecting; I assume I could search the contentitems for a tag like (HttpNotFoundPage) - and then set that tag to a content item that I'd like to use as the handler and then query for that item and then return that shape?

thanks - great info.

Dec 10, 2012 at 5:51 PM
As an FYI since I've trained myself since the c/c++ days to only have one exit per function; I modified the check function accordingly.

private static bool _isNotFoundResponse(ActionExecutedContext filterContext)
{
    var bResult = (filterContext.Result is HttpNotFoundResult);
    if (!bResult)
    {
        var shapeResult = filterContext.Result as ShapeResult;
        if (shapeResult != null)
        {
            dynamic model = shapeResult.Model;
            bResult = (model.Metadata.Type == "NotFound");
        }
    }
    return bResult;
}

Dec 10, 2012 at 6:58 PM
fruber wrote:

Instead of redirecting; I assume I could search the contentitems for a tag like (HttpNotFoundPage) - and then set that tag to a content item that I'd like to use as the handler and then query for that item and then return that shape?

Yeah, I thought of something similiar, such as querying for a specific content (i.e. page) and rendering it.  The only hiccup is that depending on what the content type actually is, the code to replicate the building of the shape/model may vary.  I'm looking to the orchard team for any direction on how to dispatch that.  It's almost like we would need the ability restart the request execution after performing a path rewrite...

Glad the "hack" works for you! :)

Andy

Dec 10, 2012 at 7:37 PM

It should also be noted that there are some items (mentioned in my first request) that are being rendered due to the "default" layer that I don't want to render.  Since this second method is not a redirect; I can't just build a layer rule that would prevent it; (I could create a new rule provider hmmm.)

But instead I could also place code in my layout.cshtml to detect that it's a 404 response and not render certain zones.

I might take a look at a new rule provider (since I already have one for regex support) - and then add that to my layer rule for items that I don't want to display when rendering a page with some statuscode or something like that..

thanks!

Dec 10, 2012 at 10:52 PM

Just as an update; I was able to easily create a rule that would allow me to use regex on the Response.Status to deal with page rendering issues where I didn't want certain content items to render on a page for some reason or another.  (in this case - only render my breadcrumb if the status is 200 (or OK).

thanks!