Sitecore、自定义 MVC 控制器和路由

Sitecore, custom MVC controllers and routes

我在 Sitecore 的网站定义中定义了一个网站。它的路径是/localhost/mysite/home。并且有效。

我需要创建一个自定义控制器来提交带有 API 绕过 Sitecore 的表单。所以我有 FormsController(从 MVC 控制器继承)和一个名为 "Test" 的动作,不带任何参数。

我在初始化管道中定义了这样的路由:

public class Initialize
{
    public void Process(PipelineArgs args)
    {
        MapRoutes();
        GlassMapperSc.Start();
    }

    private void MapRoutes()
    {
        RouteTable.Routes.MapRoute(
                "Forms.Test", 
                "forms/test", 
                new
                {
                    controller = "FormsController",
                    action = "Test"
                },
                new[] { "Forms.Controller.Namespace" });
     }
}

路由正确添加到路由table,我调试的时候也有。 现在,当我尝试调用方法 "test" 时,找不到路由,调试器也没有在操作中命中断点。

我正在尝试不同的路线:

但到目前为止运气不好。

----更新---

深入研究,我注意到 Sitecore 的行为有问题。 TransferRoutedRequest 处理器应该中止 httpRequestBegin 管道,将控制权交还给 MVC,以防上下文项为空(简化)。它发生在一些检查之后,其中一个是 RoutTable 数据。但是对 RouteTable.Routes.GetRouteData return 的调用始终为 null,这使得处理器 return 不会中止管道。我覆盖它以使其正确中止管道,但是,即使我调用方法 args.AbortPipeline(),管道也没有中止并且路由没有解析。

原来的 TransferRoutedRequest 是这样的:

public class TransferRoutedRequest : HttpRequestProcessor
{
  public override void Process(HttpRequestArgs args)
  {
    Assert.ArgumentNotNull((object) args, "args");
    RouteData routeData = RouteTable.Routes.GetRouteData((HttpContextBase) new HttpContextWrapper(HttpContext.Current));
    if (routeData == null)
      return;
    RouteValueDictionary routeValueDictionary = ObjectExtensions.ValueOrDefault<Route, RouteValueDictionary>(routeData.Route as Route, (Func<Route, RouteValueDictionary>) (r => r.Defaults));
    if (routeValueDictionary != null && routeValueDictionary.ContainsKey("scIsFallThrough"))
      return;
    args.AbortPipeline();
   }
}

这就是我覆盖它的方式:

public class TransferRoutedRequest : global::Sitecore.Mvc.Pipelines.HttpRequest.TransferRoutedRequest
{
    public override void Process(HttpRequestArgs args)
    {
        if (Context.Item == null || Context.Item.Visualization.Layout == null)
            args.AbortPipeline();
        else
            base.Process(args);
    }
}

这是为您创建路线的代码。在 global.asax.cs 中,您将从 App_Start 事件处理程序调用 RegisterRoutes:

    protected void Application_Start()
    {
        RouteConfig.RegisterRoutes(RouteTable.Routes);
    }

然后您将路线指定为:

    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.MapRoute(
             name: "test",
             url: "mvc/Forms/{action}/{id}",
             defaults: new { controller = "Forms", action = "Test", id = UrlParameter.Optional }
           );
    }

在这种情况下,您将拥有 /mvc/ 前缀,它将处理您指定控制器的路由,因此您将其命名为:

/mvc/Forms/Test/{you_may_pass_some_optional_GUID_here}

这将路由到 FormsController class 操作方法 Test(string id) 但您可以省略 id 参数

这是我的一个项目中的一个工作示例。

自定义路由注册:

namespace Test.Project.Pipelines.Initialize
{
    public class InitRoutes : Sitecore.Mvc.Pipelines.Loader.InitializeRoutes
    {
        public override void Process(PipelineArgs args)
        {
            RegisterRoutes(RouteTable.Routes);
        }

        protected virtual void RegisterRoutes(RouteCollection routes)
        {
            routes.MapRoute(
                "Test", // Route name
                "api/test/{controller}/{action}/{id}", // URL with parameters
                 new { id = UrlParameter.Optional }
                );
        }
    }
}

初始化管道配置:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
     <pipelines>
         <initialize>
            <processor type="Test.Project.Pipelines.Initialize.InitRoutes, Test.Project"
         patch:after="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" />
        </initialize>
     </pipelines>
  </sitecore>
</configuration>

我终于让它正常工作了。正如我所写 TransferRoutedRequest 没有按预期工作,所以我不得不覆盖它。尽管如此,即使它按预期工作,路线也没有得到解决。问题出在管道配置中。感谢一位同事,我打开了 SitecoreRocks 管道工具,它显示管道注册的位置离它应该在的位置太远,所以它从未被击中(我在 ItemResolver 之后注册了它,因为它是原始配置)。我在 LayoutResolver 之前对其进行了修补,结果成功了。路线解决了。 不过,Sitecore 无法创建该类型的实例,因为它在另一个程序集中。即使指定控制器的命名空间也不能解决问题。所以我不得不进行一些修改并重写 ControllerFactory class 的 CreateController 方法并重写 InitializeControllerFactory 处理器(我已经对其进行了修改以能够使用 DI 容器),编写一个新的 ControllerFactory 和一个新的SitecoreControllerFactory.

最终代码如下所示:

public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        var controller = SC.Context.Item == null || SC.Context.Item.Visualization.Layout == null
                        ? base.GetControllerType(requestContext, controllerName)
                        : TypeHelper.GetType(controllerName);

        return GetControllerInstance(requestContext, controller);
    }

如果我正在处理 Sitecore 项目,我会使用 TypeHelper 到 return 来自控制器渲染或布局的当前控制器类型。否则我使用 DefaultControllerFactory.GetControllerType 来解析自定义路由。

唯一要注意的是:如果 2 个或更多命名空间有一个具有相同名称和操作的控制器,则必须添加一个命名空间以识别它们。