Routing all HTTP errors to "NotFound" page in CMS with 404, 500 status code

Vote:
 

I have a Optimizely CMS 11 + Commerce 13 website on .NET Framework 4.7.2. Currently there is a "NotFound" (404) and "Error" (500) pages created in the CMS that display a user-friendly 404 page, except due to this and the pre-existing configuration, anything that actually results in reaching that page like an invalid URL like http://localhost:5000/productproduct will still return a 200 status code in the Network dev tools. I have tried all documentation I've found so far but no luck, and I prefer to accomplish it as close to this method as possible.

Here is the web.config configuration - I have tried PassThrough, Replace, Auto - in the existingReponse. 

<!-- 'PassThrough' enables developer exception page, however, NotFound page will not be displayed correctly.
         'Replace' fixes the NotFound page but the exception message will not be displayed.
         Keep 'PassThrough' unless you want to debug NotFound page locally. Revert before committing. -->
    <httpErrors errorMode="Custom" existingResponse="PassThrough">
      <remove statusCode="404"/>
      <error statusCode="404" path="/NotFound" responseMode="ExecuteURL"/>
      <!-- <error statusCode="500" path="/Error" responseMode="ExecuteURL"/> -->
    </httpErrors>

NotFound.aspx - I know this displays when "Replace" is set as existingResponse, but this is the code there is - the onLoad was the logic for handling applying 404 error. (also tried 

<%-- <%@ Page Language="c#" AutoEventWireup="false" Inherits="BVNetwork.NotFound.Core.NotFoundPage.NotFoundBase" %> --%>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="NotFound.aspx.cs" Inherits="BVNetwork.NotFound.Core.NotFoundPage.NotFoundBase" %>
<%@ Import Namespace="System.Diagnostics" %>
<%--
    Note! This file has no code-behind. It inherits from the NotFoundBase class. You can
    make a copy of this file into your own project, change the design and keep the inheritance
    WITHOUT having to reference the BVNetwork.EPi404.dll assembly.

    If you want to use your own Master Page, inherit from SimplePageNotFoundBase instead of
    NotFoundBase, as that will bring in what is needed by EPiServer. Note! you do not need to
    create a page type for this 404 page. If you use the EPiServer API, and inherit from
    SimplePageNotFoundBase, this page will run in the context of the site start page.

    Be very careful with the code you write here. If you reference resources that cannot be found
    you could end up in an infinite loop.

    The code behind file might do a redirect to a new page based on the redirect configuration if
    it matches the url not found. The Error event (where the rest of the redirection is done)
    might not run for .aspx files that are not found, instead it redirects here with the url it
    could not find in the query string.

    Available properties:
        Content (BVNetwork.FileNotFound.Content.PageContent)
            // Labels you can use - fetched from the language file
            Content.BottomText
            Content.CameFrom
            Content.LookingFor
            Content.TopText
            Content.Title

        UrlNotFound (string)
            The url that cannot be found

        Referer (string)
            The url that brought the user here
            It no referer, the string is empty (not null)

    If you are using a master page, you should add this:
        <meta content="noindex, nofollow" name="ROBOTS">
    to your head tag for this page (NOT all pages)
 --%>

<script runat="server" type="text/C#">
    protected override void OnLoad(EventArgs e)
    {
        // Calls the base class OnLoad method.
        // base.OnLoad(e)
        Response.StatusCode = 404; // Set the status code to 404.
        // Preserve the original behavior where the server returns the content of the NotFound page.
        Response.TrySkipIisCustomErrors = true;
        Response.End();
    }
</script>

<%@ Register TagPrefix="EPiServer" Namespace="EPiServer.WebControls" Assembly="EPiServer" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
    <title><%= Content.Title %></title>
    <meta content="noindex, nofollow" name="ROBOTS" />
</head>
<body>
<h1>Andrew!!!!!</h1>
    <form id="FileNotFoundForm" method="post" runat="server">
    <div class="logo">
        Company Logo Here
    </div>
    <h1>
        <%= Content.Title %></h1>
    <div style="width: 760px">
        <div style="padding-left: 10px; float: left; width: 550px">
            <%= Content.TopText %>
            <%= Content.LookingFor %>
            <div class="notfoundbox">
                <%= HttpUtility.HtmlEncode(UrlNotFound.ToString()) %>
                <%= Referer.Length > 0 ? Content.CameFrom : "" %>
                <%= Referer.Length > 0 ? HttpUtility.HtmlEncode(Referer) : "" %>
            </div>
            <%= Content.BottomText %>
        </div>
        <div style="padding-right: 10px; padding-left: 10px; float: right; width: 200px">
            &nbsp;
        </div>
    </div>
    <div class="floater">
        404
    </div>
    </form>
</body>
</html>

I turned on Error handling, but still the same issues.

#312131
Nov 07, 2023 19:09
Vote:
 

Are you using any packages like BVN.NotFound handler. I remember of having it one of the CMS 11 projects where it will show 200 in network tab in case of 404 errors. Also any reason why you want to have 200 network tab for 404 errors.

#312179
Nov 08, 2023 15:01
Vote:
 

I don't want a 200 error, I want to make sure the 404/500 error status code is passed along. Currently, since the error page I want to use exists in CMS. The web.config points to these pages, and at least the 404 error is sending a statusCode of 404, but the site is obviously not passing that status along to the browser.

#312180
Nov 08, 2023 15:13
Vote:
 

Let me make sure I understand correctly. If you need error code passed on to browser, In that case I believe, you can remove any not found handlers (in your project like BVN etc).

  • Create a PageType ErrorPage with properties that can reflect your current error screen.

    public class ErrorPage : SitePage
    {
        public virtual ContentArea ErrorContentArea{ get; set; }

        public virtual int StatusCode { get; set; }
    }

  • Create a PageController and a view. In the page controller action method ensure Response.StatusCode is assigned to 404. Something like this.

    public class ErrorPageController : PageControllerBase<ErrorPage>
    {
        public ActionResult Index(ErrorPage currentPage)
        {
            PageViewModel<ErrorPage> model = null;

            if (currentPage != null)
            {
                model = PageViewModelHelper.Create(currentPage);
            }

            var code = (int)(HttpStatusCode)currentPage.StatusCode;
            Response.StatusCode = code;

            return View(model);
        }
    }

  • Create the error page in CMS
  • Wire the page created in CMS in the httpErrors section in the web.config
        <httpErrors errorMode="Custom" existingResponse="Auto">
            <remove statusCode="404" />
            <error statusCode="404" path="/404-error" responseMode="ExecuteURL" />
        </httpErrors>
    #312184
    Nov 08, 2023 15:42
    Vote:
     

    I have most of that code in and it is an improvement. However, when I actually type in an invalid URL it still just goes to the default browser 404 page, instead of routing to the NotFound page (/notfound). It also doesn't let me even view the page from the CMS admin view either.

    public class ErrorPageController : PageControllerBase<ErrorPage>
    	{
    		public ActionResult Index(ErrorPage currentPage)
    		{
    			PageViewModel<ErrorPage> model = null;
    
    			if (currentPage != null)
    			{
    				model = PageViewModel.Create(currentPage);
    				PageViewModel.Create(currentPage);
    			} 
    			
    			// Validate that currentPage.StatusCode is a valid enum value before casting
    			// if (Enum.IsDefined(typeof(HttpStatusCode), currentPage.StatusCode))
    			// {
    			// 	Response.StatusCode = currentPage.StatusCode;
    			// }
    			// else
    			// {
    			// 	// Handle the case where currentPage.StatusCode is not a valid status code
    			// 	// You might log this issue and set a default status code
    			// 	Response.StatusCode = (int) HttpStatusCode.InternalServerError;
    			// }
    
    			var code = (int) (HttpStatusCode) currentPage.StatusCode;
    			Response.StatusCode = code;
    
    			return View(model);
    		}
    	}
    [ContentType(
    		DisplayName = "Error Page",
    		GUID = "1286779b-d4cb-4053-96ba-68622cc796c1",
    		Description = "Used for various error messages")
    	]
    	public class ErrorPage : SitePageData
    	{
    		// Define properties here that you want to include on your error pages
    		// For example, a property for a custom error message
    		[CultureSpecific] public virtual ContentArea ErrorContentArea { get; set; }
    
    		public virtual int StatusCode { get; set; }
    	}
    @using ErrorPage = Page.ErrorPage
    @using EPiServer.Web.Mvc.Html
    @model CMS.Ellsworth.ViewModels.PageViewModel<ErrorPage>
    
    @if (Model.CurrentPage.DisplayPageTitle)
    {
        <div class="row header">
            <div class="col-xs-12">
                <h1>@Model.CurrentPage.PageTitle</h1>
            </div>
        </div>
    }
    
    @Html.PropertyFor(x => x.CurrentPage.ErrorContentArea)
    
    <httpErrors errorMode="Custom" existingResponse="Auto">
                <remove statusCode="404" />
                <error statusCode="404" path="/not-found" responseMode="ExecuteURL" />
            </httpErrors>
    #312192
    Nov 08, 2023 21:16
    Vote:
     

    At this point I am assuming there is package like BVN which is handling your 404's , you might want to check and remove them to validate this. Also I assume you had created a page in CMS which is accessible at /not-found path.

    #312266
    Nov 09, 2023 18:18
    Vote:
     

    Make a basic NotFoundController which can that serve up your 404Page 

    e.g. 

        public class NotFoundController : Controller
        {
            #region Fields
    
            //private readonly ILanguagePreferenceResolver languagePreferenceResolver;
    
            private readonly ISiteSettingsProvider settingsProvider;
    
            #endregion
    
            #region Constructors and Destructors
    
            public NotFoundController()
            {
                // User service locator pattern as this class will be instantiated from BVNetwork library not from StructureMap
                //languagePreferenceResolver = ServiceLocator.Current.GetInstance<ILanguagePreferenceResolver>();
                settingsProvider = ServiceLocator.Current.GetInstance<ISiteSettingsProvider>();
            }
    
            #endregion
    
            #region Public Methods
    
            public ActionResult Index()
            {
                // set the response headers
                Response.Headers["Server"] = string.Empty;
                Response.StatusCode = 404;
    
                if (Path.HasExtension(Request.RawUrl))
                {
                    return new EmptyResult();
                }
    
                var loader = ServiceLocator.Current.GetInstance<IContentLoader>();
    
                var urlRewriteContext = new UrlRewriteContext(new UrlBuilder(Request.RawUrl));
    
                var currentCulture = urlRewriteContext.Language ?? ContentLanguage.PreferredCulture;
    
                /*
                if (!languagePreferenceResolver.IsValidLanguage(currentCulture.Name))
                {
                    var browserLanguages = Request.UserLanguages?.ToList() ?? new List<string>();
    
                    var preferredLanguage = languagePreferenceResolver.PreferredLanguageBranch(browserLanguages);
                    currentCulture = new CultureInfo(preferredLanguage.LanguageID);
                }
                */
    
                ContentLanguage.PreferredCulture = currentCulture;
    
                var error404Page = settingsProvider.Current?.Error404Page;
    
                if (!ContentReference.IsNullOrEmpty(error404Page))
                {
                    // Load 404 page content and pass to view
                    var notFoundPage = loader.Get<NotFoundPage>(
                        error404Page,
                        LanguageSelector.Fallback(ContentLanguage.PreferredCulture.Name, true));
    
                    Request.RequestContext.SetContentLink(error404Page);
    
                    if (notFoundPage != null)
                    {
                        return View(Constants.ViewPath.NotFoundPage, new NotFoundPageViewModel(notFoundPage));
                    }
                }
    
                return new EmptyResult();
            }
    
            #endregion
        }

    Your web.config should than look like 

        <httpErrors errorMode="Custom">
          <remove statusCode="404" />
          <error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
          <remove statusCode="410" />
          <error statusCode="410" path="/NotFound" responseMode="ExecuteURL" />
          <remove statusCode="500" subStatusCode="-1" />
          <error statusCode="500" path="/500.aspx" responseMode="ExecuteURL" />
        </httpErrors>

    Just for absolute completeness my Not Found Page (Your 404 Page) Controller than looks like 

        /// <summary>
        ///     Not Found Page Controller
        /// </summary>
        /// <seealso cref="NotFoundPage" />
        public class NotFoundPageController : PageControllerBase<NotFoundPage>
        {
            #region Public Methods
    
            /// <summary>
            ///     Indexes the specified current page.
            /// </summary>
            /// <param name="currentPage">The current page.</param>
            /// <returns></returns>
            public ActionResult Index(NotFoundPage currentPage)
            {
                var urlRewriteContext = new UrlRewriteContext(new UrlBuilder(Request.RawUrl));
                var currentCulture = urlRewriteContext.Language;
                ContentLanguage.PreferredCulture = currentCulture;
                var currentculture = ContentLanguage.PreferredCulture;
    
                var page = ServiceLocator.Current.GetInstance<IContentLoader>()
                    .Get<NotFoundPage>(currentPage.PageLink, currentculture);
    
                // This removes the server from the response, it isn't handled by the URL rewriter.
                Response.Headers["Server"] = string.Empty;
    
                var viewModel = new NotFoundPageViewModel(page);
                return View(viewModel);
            }
    
            #endregion
        }
    #312319
    Edited, Nov 10, 2023 16:49
    Vote:
     

    There are some issues with the code you provided, in the ErrorController.cs. I'm not sure what .Error404Page variable should be, since that specific one doesn't exist and I'm not sure where that is set.

    #312821
    Nov 20, 2023 14:33
    Dileep D - Nov 24, 2023 14:34
    Could you post your controller, view, web.config for httperrors section and the error page created in CMS. Hopefully we can find the missing link.
    * You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.