65.9K
CodeProject 正在变化。 阅读更多。
Home

Sitecore 404 重定向到自定义项(当项未找到时)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2018 年 3 月 13 日

CPOL

8分钟阅读

viewsIcon

18658

本文介绍如何在 Sitecore 中实现自定义项未找到页面。

引言

本文介绍如何在 Sitecore 中将 404 页面实现为一个项。在 Sitecore 中创建 404(项未找到)页面后,需要修改一些内容才能获得理想的结果。本文展示的实现仅供教育目的。读者应根据自己所知的最佳实践来实施。

谁可以阅读本文?

本文适合所有具备 Sitecore MVC 基本知识的开发人员。开发人员应已安装 Sitecore 并部署了网站。

背景

我们将介绍当 URL 出现错误时如何重定向到 Sitecore 中的一个项。我们还将考虑为保持网站可见性和在搜索引擎优化中获得良好排名而需要采取的有效措施。

首先,我们将在 Sitecore 中创建一个项来处理“项未找到”页面。然后,我们将详细介绍成功完成“项未找到”页面所需实现的缺失元素。

Using the Code

实现

我们将简要浏览一下我们将要处理的项目。

安装 Sitecore EXE 或 ZIP 后,将有一个默认的“主页”站点可用。我们将使用同一个主页站点作为我们的项目。在上图中,您会看到“布局”已更改为“NewWebSiteLayout”。这个布局是在“Layouts -> SumitPOC”文件夹下创建的(您可以根据需要创建)。我们还看到在“控件”选项卡下,我们选择了一个名为“Simple Sunil Hi”的渲染器。

NewWebSiteLayout 如下所示:

渲染器如下所示:

上述内容的输出如下所示:

现在我们对事物的工作方式有了一个概念,让我们尝试看看输入错误 URL 时会发生什么。

上述 URL 的结果如下所示:

这是 Sitecore 设置的默认页面。我们得到这个页面是因为这个静态页面在 website\App_config\ 目录下的 Sitecore.config 文件中进行了配置。

我们的目的是显示一个自定义的“项未找到”页面,内容编辑者可以随时修改。

让我们开始实施。

与其他项的创建一样,我们将为“项未找到”创建一个项。为了实现这一点,只需在“Sitecore -> Content -> Home”下创建一个项。选择“Standard Template”并将其命名为ItemNotFound

结构将与下面显示的屏幕类似:

我们只需要为这个项提供一个渲染器,该渲染器将被调用以显示“项未找到”页面。

“Renderings -> SumitPOC -> Item Not Found”下创建一个渲染器。

结构将与下面显示的屏幕类似:

下面是名为 ItemNotFoundController 的 Controller 的实现:

using System.Web.Mvc;
namespace SumitPOC.Controllers
{
    public class ItemNotFoundController : Controller
    {
       // GET: ItemNotFound
        public ActionResult Index()
        {
           return View();
        }
    }
}

视图如下所示:

<div><string>The Item you are Searching is not found....</string></div>

我们已成功创建了处理“项未找到”的项。我们需要配置 Sitecore 调用我们的页面而不是 Sitecore 提供的静态页面。因此,我们选择 ItemNotFound 项的路径并修改 Sitecore.config,如下所示:

    <setting name="ItemNotFoundUrl" value="/sitecore/service/notfound.aspx"/>

to

    <setting name="ItemNotFoundUrl" value="/sitecore/content/Home/ItemNotFound"/>

现在,当我们发布网站并运行时,在“项未找到”时会看到下面的页面。

现在的输出如下所示:

这表明我们已经成功地在 Sitecore 中实现了自定义的 ItemNotFound 页面。

观察:尽管我们在 Sitecore 中实现了自定义的 ItemNotFound 页面,但有几点需要考虑。

1. 检查 URL。

我们传入的 URL 是“http://sumitpoc/11111”。

执行后我们收到的 URL 是 http://sumitpoc/sitecore/content/Home/ItemNotFound?item=%2f111111&user=extranet%5cAnonymous&site=website

考虑一个实际情况

让我们用一些真实网站进行实验。

输入 https://stackoverflow.com/questions/49098720/fatal-error-when-hiding-collectionview

然后按 Enter。您将获得一个包含某些信息的页面。

让我们观察一下当我们拼错某些内容时会发生什么,例如,我们将“questions”拼错为“quest”。

我们得到下面的页面:

观察 URL。它没有改变。

原因在于,如果用户拼错了,他只需要纠正错误。

在我们的例子中,用户需要重新输入整个 URL。

我们需要在我们的自定义 ItemNotFound 页面实现中解决这个问题。

2. 检查重定向

有多种方法可以检查重定向跟踪。最常见的方法是按功能键“F12”,转到网络选项卡并检查重定向。在本文中,我们使用了 Chrome 扩展程序“Link Redirect trace”。

我们的 URL 被重定向到错误 URL,状态码从302变为200。我们创建的自定义页面存在于 Sitecore 中,并且 URL 找到了该页面,因此状态码是200。重定向的行为在技术上是正确的,但从逻辑上讲,应该是404,因为这是在项未找到时到达的页面。

为什么我们关注减少重定向?为什么我们要尝试将 200 改为 404?

搜索引擎优化会非常差地对网站进行排名,如果它陷入无尽的循环。在我们的例子中,302 到 200 是一个无尽的循环,因为从逻辑上讲,页面是“项未找到”,但我们收到 200 表示可以继续抓取。

我们还将看到 302 到 404 结束了循环,但搜索引擎优化仍然会进一步查找 404。所以我们也需要消除这个 302。

再次,让我们回到之前的例子并观察行为。

让我们输入相同的 URL“https://stackoverflow.com/quest/49098720/fatal-error-when-hiding-collectionview

这里显示的页面仍然是一个自定义页面,但状态码是404。我们需要在我们的自定义 ItemNotFound 页面实现中解决这个问题。

结论

  1. 实施成功完成,需要 2 项重大修改。
  2. 客户端输入的错误 URL 不应更改。
  3. 应有一个“404”页面而不是“302”到“200”。

重大修改的实施

为了满足重大修改的要求,我们将不得不使用服务器端重定向,即 301(服务器传输)。使用服务器传输,URL 不会改变。要获得 404 状态,我们将不得不实现一些管道下的东西。

让我们开始实施。只是为了有一个概览,这段代码将是应用程序的通用代码,因此;我们将把代码写在Foundation文件夹下。

为了实现 301 重定向,我们需要进入 sitecore.config 文件并将 RequestErrors.UseServerSideRedirect 的值更改为 True。如下所示:

      <setting name="RequestErrors.UseServerSideRedirect" value="true"/>

我将所有 C# 类的代码放在这里。

1) CustomExecuteRequest.cs
   using Sitecore;
   using Sitecore.Configuration;
   using Sitecore.Pipelines.HttpRequest;
   using Sitecore.Web;
   using System.Web;
   
   namespace SumitPOC.Foundation.Pipeline.HttpRequest
   {
       public class <code>CustomExecuteRequest</code> : <code>ExecuteRequest</code>
       {
           protected override void PerformRedirect(string url)
           {
               if (   Context.Site == null 
                   || Context.Database == null 
                   || Context.Database.Name == "core")
                   {
                       return;
                   }
                   //need to retrieve not found item to account 
                   //for sites utilizing virtualFolder attribute
                   //site this under <setting name="ItemNotFoundUrl" 
                   //value="/sitecore/content/Home/ItemNotFound"/>
                   //the value "/sitecore/content/Home/ItemNotFound" is the Custom item you have created.
                   var notFoundItem = Context.Database.GetItem(Settings.ItemNotFoundUrl);
                   
                   if (notFoundItem == null)
                   {
                        return;
                    } 
                   //This code works along with the commented constructor. 
                   //BaseSiteManager, BaseItemManager and BaseLinkManager are
                   // are present in SItecore Kernel 10.0. We are using 8.1 so not of any use for us.
                   //var notFoundUrl = _baseLinkManager.GetItemUrl(notFoundItem);
                   if (string.IsNullOrWhiteSpace(Settings.ItemNotFoundUrl))
                   {
                      return;
                   }
                   //under Sitecore.config, make this true. 
                   //<setting name="RequestErrors.UseServerSideRedirect" value="true"/>
                if (Settings.RequestErrors.UseServerSideRedirect)
               {
                   HttpContext.Current.Server.TransferRequest(Settings.ItemNotFoundUrl);
               }
               else
               {
                   WebUtil.Redirect(Settings.ItemNotFoundUrl, false);
               }
           }
       }
   }

using 语句是故意添加的。

准备好 CustomExecuteRequest 文件后,我们需要将其附加到管道。

要将其添加到管道,我们需要了解更多新知识。我们必须在App_Config -> Include下创建一个文件夹,在此文件夹下创建一个以Z.(大写 Z 和点)开头的自定义文件夹。

我们需要这样做是因为,在运行时 Sitecore 会合并“Include -> Z.”类型文件夹下的所有配置,并将它们与主 Web.config 合并。

我们通过这个实现了什么?

如果我们想将代码从 Sitecore 一个版本迁移到更高版本,Include 文件夹下的任何内容都不会被替换。因此,即使在迁移后,我们的自定义逻辑也不会中断。

下面是我们如何在 Include 文件夹下实现的:

(如果您的 MVC 应用程序中不存在 include 文件夹,只需复制该文件夹并添加到您的解决方案中。将其包含在您的项目中。)

CustomErrorPipelines.config 如下所示:

    <?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
   <pipelines>
     <httpRequestBegin>
        <!-- Reads updated "RequestErrors.UseServerSideRedirect" 
             value and transfers request to LayoutNoutFoundUrl or ItemNotFoundUrl, 
             preserving requested URL -->
        <processor type="SumitPOC.Foundation.Pipeline.HttpRequest.CustomExecuteRequest, 
         SumitPOC" resolve="true"
                   patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.ExecuteRequest, 
                   Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>
2) Set404StatusCode.cs
using Sitecore.Configuration;
using Sitecore.Pipelines.HttpRequest;
using SumitPOC.Common;
using System;
using System.Web;

namespace SumitPOC.Foundation.Pipeline.HttpRequest
{
    public class <code>Set404StatusCode</code> : <code>HttpRequestBase</code>
    {
        protected override void Execute(HttpRequestArgs args)
        {
            // retain 500 response if already set
            if (HttpContext.Current.Response.StatusCode >= 500 || args.Context.Request.RawUrl == "/")
                return;

            // return if request does not end with value set in ItemNotFoundUrl, i.e. successful page
            if (!args.Context.Request.Url.LocalPath.EndsWith
               (Settings.ItemNotFoundUrl, StringComparison.InvariantCultureIgnoreCase))
                return;

            HttpContext.Current.Response.TrySkipIisCustomErrors = true;
            HttpContext.Current.Response.StatusCode = SiteCoreRouteValues.PageNotFoundCode;
            HttpContext.Current.Response.StatusDescription = 
                             SiteCoreRouteValues.PageNotFoundDescription;
        }
    }
}
3) HttpRequestBase.cs
using Sitecore;
using Sitecore.Pipelines.HttpRequest;
using System;
using System.Linq;

namespace SumitPOC.Foundation.Pipeline.HttpRequest
{
    public abstract class HttpRequestBase : HttpRequestProcessor
    {
        /// <summary>
        /// allowedSites and disallowedSites are mutually exclusive, use one or the other
        /// in the event both are used, disallowedSites is enforced
        /// </summary>
        public string allowedSites { get; set; }
        public string disallowedSites { get; set; }
        public string ignoredPaths { get; set; }
        public string ignoredModes { get; set; }
        /// <summary>
        /// allowedDatabases and disallowedDatabases are mutually exclusive, use one or the other
        /// in the event both are used, disallowedDatabases is enforced
        /// </summary>
        public string allowedDatabases { get; set; }
        public string disallowedDatabases { get; set; }

        private const string EditMode = "Edit";

        /// <summary>
        /// Overridden HttpRequestProcessor method
        /// </summary>
        /// <param name="e;args"e;></param>
        public override void Process(HttpRequestArgs args)
        {
            if (IsValid(args))
            {
                Execute(args);
            }
        }

        protected abstract void Execute(HttpRequestArgs args);

        protected virtual bool IsValid(HttpRequestArgs hArgs)
        {
            return SitesAllowed()
                && PathNotIgnored(hArgs)
                && ModeNotIgnored()
                && DatabaseAllowed();
        }

        private bool SitesAllowed()
        {
            // httpRequest processors should never run without a context site
            if (Context.Site == null)
                return false;

            var contextSiteName = Context.GetSiteName();

            if (string.IsNullOrWhiteSpace(contextSiteName))
                return false;

            // disallow checked first to trump an allowance
            if (!string.IsNullOrWhiteSpace(disallowedSites))
            {
                return !disallowedSites
                    .Split(',')
                    .Select(i => i.Trim())
                    .Any(siteName => string.Equals
                    (siteName, contextSiteName, StringComparison.CurrentCultureIgnoreCase));
            }

            if (!string.IsNullOrWhiteSpace(allowedSites))
            {
                return allowedSites
                    .Split(',')
                    .Select(i => i.Trim())
                    .Any(siteName => string.Equals(siteName, 
                         contextSiteName, StringComparison.CurrentCultureIgnoreCase));
            }

            return true;
        }

        private bool PathNotIgnored(HttpRequestArgs hArgs)
        {
            if (string.IsNullOrWhiteSpace(ignoredPaths))
                return true;

            var ignoredPath = ignoredPaths
                .Split(',')
                .Select(i => i.Trim())
                .Any(path => hArgs.Context.Request.RawUrl.StartsWith
                    (path, StringComparison.CurrentCultureIgnoreCase));

            return !ignoredPath;
        }

        private bool ModeNotIgnored()
        {
            if (string.IsNullOrWhiteSpace(ignoredModes))
                return true;

            var modes = ignoredModes.Split(',').Select(i => i.Trim());

            var isEditor = Context.PageMode.IsExperienceEditor;

            return !modes.Any(mode =>
              (mode == "Edit" && isEditor) ||
              (mode == "Preview" && Context.PageMode.IsPreview)
            );
        }

        private bool DatabaseAllowed()
        {
            // httpRequest processors should never run without a context database
            if (Context.Database == null)
                return false;

            var contextDatabaseName = Context.Database.Name;

            // disallow checked first to trump an allowance
            if (!string.IsNullOrWhiteSpace(disallowedDatabases))
            {
                return !disallowedDatabases
                    .Split(',')
                    .Select(i => i.Trim())
                    .Any(database => string.Equals
                    (database, contextDatabaseName, StringComparison.CurrentCultureIgnoreCase));
            }

            if (!string.IsNullOrWhiteSpace(allowedDatabases))
            {
                return allowedDatabases
                    .Split(',')
                    .Select(i => i.Trim())
                    .Any(database => string.Equals(database, contextDatabaseName, 
                                                   StringComparison.CurrentCultureIgnoreCase));
            }

            return true;
        }
    }
}

我们需要将 Sitcore404StatusCode.cs 附加到管道。

CustomeErrorPipeline404Status.config 的代码如下所示:

    <?xml version="e;1.0"e;?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestEnd>
        <!-- Sets a 404 status code on the response -->
        <processor type="SumitPOC.Foundation.Pipeline.HttpRequest.Set404StatusCode, 
                   SumitPOC" resolve="true"
            patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.EndDiagnostics, Sitecore.Kernel']"/>
      </httpRequestEnd>
    </pipelines>
  </sitecore>
</configuration>

发布您的代码。检查事项是否如预期工作。

现在,我们应该已经处理了所有重大修改。

结果集将如下所示:

即使在成功完成重大修改后,我们仍然有一个问题,那就是图像渲染。

如果我们有一个类似 http://sumitpoc/~/media%20library/Images/SumitPOC/testimonial_video 的 URL。

如果用户拼错了 URL,他会收到 302 到 404。

在大多数情况下,图像是通过 Sitecore GUI 和代码插入的。没有人会在 URL 中添加它们。尽管如此,我们还是有解决方案,稍后将讨论。

关注点

注意

错误处理。

启动您的网站。尝试在 Visual Studio 中附加调试器。按 CTRL + ALT + P,然后找到 w3wp.exe,附加进程。

即使附加后,如果调试器仍未附加,请执行以下操作:

  1. 如果您将代码发布到 Website bin,则不要这样做。其他开发人员,只需复制您更改的 DLL 以及该 DLL 的 PDB 文件,然后粘贴到网站的 bin 文件夹中。运行网站,然后添加调试断点并尝试附加 W3Wp.exe
  2. 即使遵循了第一种方法,您的调试器仍未附加;请转到您网站的应用程序池。

    您的应用程序池如上所示:

    将“启用 32 位应用程序”设置为 True。现在启动您的网站,并在您的 DLL 中设置调试器,然后尝试附加 W3WP.exe

  3. 即使遵循了第二点,您的调试器仍未附加;然后转到 Visual Studio 2010。转到工具 -> 选项 -> 调试 -> 启用“仅在我的代码中”并取消选中它,如下所示:

  4. 如果您的代码开始工作,意味着 DLL 来自发布模式而不是调试模式。您的生成发布目标正在发布来自发布模式的文件(不知道为什么?)。只需手动生成代码,并选择调试,如下所示:

    复制 DLL 并将其粘贴到您的网站文件夹中。附加 W3WP 进程。现在它应该可以附加并按要求工作。

    为什么复制粘贴?原因是,我们不知道发布的设置是否按预期工作。

    在诊断或故障排除期间,我们应该避免复杂的步骤。

历史

  • 2018 年 3 月 13 日:初始版本
© . All rights reserved.