0

I tried to set up StatusCode of the Response but everytime I got exception: Server cannot set status after HTTP headers have been sent. I attached part of my code, I tried various solutions to fix this but without success. Thank you for your help.

public class Custom404Page : Sitecore.Pipelines.HttpRequest.ExecuteRequest
{
    protected override void RedirectOnItemNotFound(string url)
    {
        try
        {
            SiteContext siteContext = Context.Site;
            Database database = Context.Database;
            RedirectTo404Page(siteContext, database);
        }
        catch (Exception ex)
        {
            LogManager.LogError(string.Format("Custom404Page throws exception. Redirection from {0} to custom 404 page failed.", url), ex);
        }
    }

    public void RedirectTo404Page(SiteContext siteContext, Database database)
    {
        try
        {
            if (String.IsNullOrEmpty(siteContext.StartPath))
            {
                return;
            }

            var startPage = database.GetItem(siteContext.StartPath);

            var paths = startPage.Paths;

            var parentPath = paths.ParentPath;

            var templateId = Page404Item.TemplateId;

            Item page404Item = database.SelectSingleItem("fast:/" + parentPath + "//*[@@templateid = '" + templateId + "']");

            if (page404Item != null)
            {
                if (page404Item.Versions.Count == 0)
                {
                    Language contentLanguage;
                    if (Language.TryParse(siteContext.Language, out contentLanguage))
                    {
                        Context.Language = contentLanguage;
                        page404Item = page404Item.Database.GetItem(page404Item.ID);
                    }
                }

                if (0 < page404Item.Versions.Count)
                {
                    string page404Url = GetUrlFromPage404Item(page404Item);
                    if (!string.IsNullOrEmpty(page404Url))
                    {
                        var context = HttpContext.Current;

                        context.Response.Clear();
                        context.Server.ClearError();

                        context.Response.BufferOutput = true;
                        context.Response.TrySkipIisCustomErrors = true;
                        context.Response.StatusCode = (int)HttpStatusCode.NotFound;

                        string html404Page = WebUtil.ExecuteWebPage(page404Url);
                        context.Response.Write(html404Page);

                        context.Response.Flush();
                        context.Response.SuppressContent = true;
                        context.ApplicationInstance.CompleteRequest();
                    }
                }
            }
        }
        catch (ThreadAbortException ex)
        {
        }
        catch (WebException ex)
        {
            LogManager.LogError(string.Format("Page404Provider throws WebException."), ex);
        }
        catch (Exception ex)
        {
            LogManager.LogError(string.Format("Page404Provider throws exception."), ex);
        }
    }

    private string GetUrlFromPage404Item(Item page404Item)
    {
        string url = string.Empty;

        Page404Item data = (Page404Item)page404Item;
        if (data.LinkTo404Page != null)
        {
            url = ItemUtility.CreateItemUrl(data.LinkTo404Page.Item, true);
        }

        return url;
    }
}
Krpo
  • 59
  • 1
  • 7
  • What happens if you set `StatusCode` before using `Response.Write()`? – NineBerry May 02 '16 at 13:19
  • It is the same problem. It doesn t help. – Krpo May 02 '16 at 13:26
  • Have you tried first doing a `context.Response.Clear();`? – bastos.sergio May 02 '16 at 13:40
  • I tested more deeply and this also doesnt help. – Krpo May 02 '16 at 14:22
  • You obviously already add output before the code you have shown us here. – NineBerry May 02 '16 at 22:03
  • Sorry I dont understand I added code in accordance yours proposal so code is updated and this version ... on one side works because I got custom 404 page but also exception occurs as described above ... – Krpo May 02 '16 at 22:17
  • Is it possible that can be some thread issue ? Because I didnt mentioned but what I want to do is to override funcionality of the Sitecore ItemResolver class behavior when user tried to request items which are not defined ... – Krpo May 02 '16 at 22:25
  • When is that code run? You need to show more code... – bastos.sergio May 03 '16 at 09:20
  • I added all code which handle my custom 404 page. Funny is that almost identical code works and problems with headers doesnt occur in the class which do almost similar thing and this class derived from Sitecore.Pipelines.HttpRequest.HttpRequestProcessor and override Process method. – Krpo May 04 '16 at 07:32
  • *Something*, before any of the code you're showing us, has started sending the response to the client. That's what the error message is saying. It's *too late*, by the time any of the code you're showing us runs, to address the issue. You need to locate *where* you start sending a response to the client and address the issue there. – Damien_The_Unbeliever May 04 '16 at 07:33
  • Probably is the sitecore default ItemResolver which handle items in sitecore when they are not defined and I want to override this behavior with my custom page. – Krpo May 04 '16 at 07:42

1 Answers1

0

You just need to add a patch after the ItemResolver to handle items that cannot be found.

config:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <settings>
            <!-- ID of the page that will be displayed when the requested url does not resolve to an actual page -->
            <setting name="Namespace.PageNotFound.ID" value="{017424DE-DB4F-4D9E-9AA1-5326527CC6A3}" />
        </settings>
        <pipelines>
            <httpRequestBegin>
                <processor type="Namespace.Global.Pipeline.Custom.Custom404Resolver, Namespace.Global" patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" />
            </httpRequestBegin>
      <preprocessRequest help="Processors should derive from Sitecore.Pipelines.PreprocessRequest.PreprocessRequestProcessor">
        <processor type="Sitecore.Pipelines.PreprocessRequest.FilterUrlExtensions, Sitecore.Kernel">
          <param desc="Allowed extensions (comma separated)">*</param>
          <param desc="Blocked extensions (comma separated)">woff,eot,ttf,svg,gif,png,ico,jpg,jpeg,js,css</param>
          <param desc="Blocked extensions that stream files (comma separated)">*</param>
          <param desc="Blocked extensions that do not stream files (comma separated)"></param>
        </processor>
      </preprocessRequest>
    </pipelines>
    </sitecore>
</configuration>

Processor:

public class Custom404Resolver : HttpRequestProcessor
{
    public override void Process(HttpRequestArgs args)
    {
        Assert.ArgumentNotNull(args, "args");

        // Do nothing if the item is actually found
        if (Sitecore.Context.Item != null || Sitecore.Context.Database == null)
            return;

        // all the icons and media library items for the sitecore client need to be ignored
        if (args.Url.FilePath.StartsWith("/-/"))
            return;

        // Get the page not found item in Sitecore.

        var notFoundPagePath = Sitecore.IO.FileUtil.MakePath(Sitecore.Context.Site.StartPath, "Errors/page-not-found");
        var notFoundPage = Sitecore.Context.Database.GetItem(notFoundPagePath);

        if (notFoundPage == null)
        { 
            var notFoundPageId = Sitecore.Configuration.Settings.GetSetting("Namespace.PageNotFound.ID", "{017424DE-DB4F-4D9E-9AA1-5326527CC6A3}");
            notFoundPage = Sitecore.Context.Database.GetItem(notFoundPageId);
        }
        if (notFoundPage == null)
            return;

        // Switch to the 404 item
        Sitecore.Context.Item = notFoundPage;
    }
}

Controller for a rendering on the "404" page in sitecore:

public class ErrorController : BaseController
{
    public ActionResult PageNotFound()
    {
        Response.StatusDescription = "Page not found";
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        Response.TrySkipIisCustomErrors = true;

        return Content(string.Empty);
    }
}

Create a Controller Rendering that uses this action and place it in the presentation details of the sitecore page that will be displayed when a 404 occurs. This process will preserve the url but show the 404 page content instead, along with the appropriate 404 status code.

Nathan Hase
  • 649
  • 4
  • 11
  • Will be good solution to use RedirectPermanent method ? And set then only StatusCode to 404 because as one of many solutuions I thought it works ? As one of my solution I tried to set up directly Context.Item to my 404 item but it doesn t work. And I am working with web forms. – Krpo May 08 '16 at 15:56
  • The Controller Rendering can also be done as a webforms user control through a sublayout. RedirectPermanent will set a status code of 301 (Moved Permanently) which is appropriate if the requested url is now served by a different url. Your question was about items that cannot be found, which need a 404 code. Assigning the context item to the page that serves the 404 content does in fact work as I do it all the time. Perhaps you have other patches in the httpRequestBegin pipeline? http://stackoverflow.com/questions/17517318/redirect-vs-redirectpermanent-in-asp-net-mvc – Nathan Hase May 09 '16 at 13:02
  • thank you, and all of you. So as you described above what I need is to create user control through sublayout and code from yours ErrorController and assign it to my custom 404 page in presentation details as control ? I have defined 4 control for this page (Header, Footer etc). For now as solution I choose to used Server.TransferRequest method. Firstly my problem was about LayoutResolver and handle items which have no layout defined for this I think I can use Server.TransferRequest() because item exists but has no layout assign. And then I check code for non-existing items and there. – Krpo May 09 '16 at 18:54
  • There were bugs so I decided to fix it. And more and more problems occured with (ThreadAbortException) exception with header ... And Server.TransferRequest works. But for non-existing items StatusCode is not correctly set as you described it should be 404. – Krpo May 09 '16 at 18:57
  • Items without Layout are not pages and are not expected to be served individually so the 301, 404, etc status codes do not apply. – Nathan Hase May 09 '16 at 19:40
  • I handled it because of sitecore default ugly NoLayout page and handle similar way as non-existing item with custom 404 page will be displayed. So it is not good solution to use Server.TransferRequest for items without layout ? It returns 301 and I thought about items or item which content editor forgot to assign layout publish and so on.. and handle this ... – Krpo May 09 '16 at 19:52
  • It is reasonable to treat a page that cannot be displayed as a page that cannot be found. You can also look at Publish Related Items as this will ensure that all items that the requested page item depend on are available in the Web database. Workflow publish actions can do this automatically. Manual publish has a checkbox that will enable it. Here's a more thorough article on the 404 subject. https://laubplusco.net/handling-404-sitecore-avoid-302-redirects/ – Nathan Hase May 09 '16 at 20:49